做者:waterxihtml
原文连接node
一年一度的换工做高峰又到了,HR大概天天都塞几份简历过来,基本上一天安排两个面试的话,当天就只能加班干活了。趁着面试别人的机会,本身也把一些基础算法和一些面试题整了一下,能够阶段性的留下些脚印——没办法,平时太忙,基本上没有时间写博客。面试测试开发的话,这些也许能帮得上一些。python
这篇是关于排序的,把常见的排序算法和面试中常常提到的一些问题整理了一下。这里面大概有3个须要提到的问题:程序员
- 虽然专业是数学,可是本身仍是比较讨厌繁琐的公式,因此基本上文章全部的逻辑,我都尽量的用大白话说,但愿能说明白;
- 语言使用的是Python,缘由是写的快一些,固然会尽量的抛开一些Python的特色,好比数组处理的时候尽量的不使用一些tuple交换等方式;
- 测试算法的时候会用到一些Python编程的技巧,这里只是简单的提一下,不作深刻介绍;
经常使用的排序算法(主要指面试中)包含两大类,一类是基础比较模型的,也就是排序的过程,是创建在两个数进行对比得出大小的基础上,这样的排序算法又能够分为两类:一类是基于数组的,一类是基于树的;基础数组的比较排序算法主要有:冒泡法,插入法,选择法,归并法,快速排序法;基础树的比较排序算法主要有:堆排序和二叉树排序;基于非比较模型的排序,主要有桶排序和位图排序(我的认为这两个属于同一思路的两个极端)。面试
对于上面提到的这些排序算法,我的认为并无优劣之分,主要看关注点,也就是需求。综合去看待这些算法,咱们能够经过如下几个方面(不彻底)判断:时间复杂度,空间复杂度,待排序数组长度,待排序数组特色,程序编写复杂度,实际程序运行环境,实际程序可接受水平等等。说白了就是考虑各类需求和限制条件,程序快不快,占得空间,排序的数多很少,规律不规律,数据重合的多很少,程序员水平,运行的机器高配仍是低配,客户或者用户对运行时间的底线等等。算法
抛开主观的这些由于,从技术上讲,时间复杂度和空间复杂度,是最为关心的,下面是这些排序算法的一个总结和特色——分类和总结彻底是我的体会,请不要拿教科书上的东西较真。编程
冒泡法:对比模型,原数组上排序,稳定,慢windows
插入法:对比模型,原数组上排序,稳定,慢数组
选择法:对比模型,原数组上排序,稳定,慢bash
归并法:对比模型,非原数组上排序,稳定,快
快速法:对比模型,原数组上排序,不稳定,快
堆排序:对比模型,原数组上排序,不稳定,快
二叉树排序:对比模型,非数组上排序,不稳定,快
桶排序:非对比模型,非原数组上排序,不稳定,快
位图排序:非对比模型,非原数组上排序,不稳定,快
如今开始正经的东西,逐一讨论一下这些排序算法;事实上,理解了算法自己的意义,伪代码很容易写出来,可是写代码是另一回事——算法忽略常量,忽略对于复杂度影响不大的东西,可是写代码的时候,却必须关心这些:
入门级算法,可是它的思路很具备特色:循环,两两向后比较。具体方法是针对循环中的每一元素,都对它后面的元素循环比较,交换大小值,每次循环“冒”一个最大值(或最小值)放在里层循环初始的地方;python中的代码以下:
def bubbleSort(L): assert(type(L)==type([''])) length = len(L) if length==0 or length==1: return L for i in xrange(length): for j in xrange(length-1-i): if L[j] < L[j+1]: temp = L[j] L[j] = L[j+1] L[j+1] = temp return L pass
冒泡法的优势是稳定,不须要大量额外的空间开销,并且容易想到。不少面试人员知道快速排序,可是不知道冒泡法——大部分是培训学校出来的,快速排序稍微改动一些就不知道怎么办了,可是冒泡法,虽然不知道,可是解释和优化起来,确很容易。毕竟对于编程来讲,嵌套一个循环和判断,是最基本的。
选择法也算是入门的一种排序算法,比起冒泡法,它的方法巧妙了一些,它的出发点在于“挑”,每次挑选数组的最值,与前置元素换位,而后继续挑选剩余元素的最值并重复操做。我的认为选择排序的意义不在于排序自己,而在于挑选和置换的方法,对于一些问题颇有帮助。先看一下选择排序的python实现:
def selectSort(L): assert(type(L)==type([''])) length = len(L) if length==0 or length==1: return L def _max(s): largest = s for i in xrange(s,length): if L[i] > L[largest]: largest = i return largest for i in xrange(length): largest = _max(i) if i!=largest: temp = L[largest] L[largest] = L[i] L[i] = temp return L pass
和冒泡排序同样,稳定,原位排序,一样比较慢。可是它的挑选和置换的方法,确实巧妙的,好比另外一个面试提:0~100的已经排序的序列,如何随机打乱它的顺序,固然也能够变成如何最快的生成0~100的随机数。一种比较好的方法,就是随机在数组里挑选元素,而后置换这个数据和最后一个元素的位置,接下来在不包含最后一个元素的数组里继续找随机数,而后继续后置。
这个shuffle的方法,事实上难倒了不少面试的同窗,甚至有些链表和树什么的都已经用到了。选择排序的思惟能够轻松搞定这个问题。
冒泡,选择和插入,在排序算法中算最为入门的,虽然简单,可是也都各自表明着经常使用的编程方法。插入法和以前两个排序对比,并不在于如何按顺序的“取”,而在于如何按数序的“插”。具体方法是,顺序地从数组里获取数据,并在一个已经排序好的序列里,插入到对应的位置,固然,最好的放置已经排序的数据的容器,也是这个数组自己——它的长度是固定的,取了多少数据,就有多少空位。具体Python实现以下:
def insertSort(L): assert(type(L)==type([''])) length = len(L) if length==0 or length==1: return L for i in xrange(1,length): value = L[i] j = i-1 while j>=0 and L[j]<value: L[j+1] = L[j] j-=1 L[j+1] = value return L
前面这三个排序方法,冒泡,选择和插入,在比较模型中速度很慢,它的缘由是这样的,这三种方法,都不可避免的两两排序,也就是任意两个元素都相互的作过对比,因此它们无论给定的数组的数据特色,都很稳定的进行对比,复杂度也就是NXN。
可是有没有方法,不进行两两的对比呢?咱们知道有些递归算法中,重复的操做能够经过迭代进行传递,或者使用容器将以前重复计算的部分存储起来,对于对比模型中的比较,也是有一些办法去除一些对比操做。好比去传递比较的结果,或者隔离的进行比较等等。一种经典的方法,就是分治法。
分治法并非一种特定的算法,就像动态算法同样,只是一个解决问题的思路,并非解决具体问题的方法。它使用在那种不断的重复去处理同一个小问题的状况下,也就是“分而治之”,大事化小,小事化无。经典的分治法包括归并排序和快速排序,它们的方法,都是先分,再合。
伟大的计算机先驱冯诺依曼提出来的一种办法,说到这里不得不感叹一下早起这些科学家的智慧了。归并排序的“分”和“合”的核心,就是将两个已经排序好的数组,合成一个排序的数组;如何构造两个已经排序好的数组呢?既然一样是排序,依然使用归并去递归处理。
具体的方法是,每次都将待排序的数组从中间分红两个数组,分别排序这两个数组,而后将它们再合并。因此归并排序的核心在于如何合并两个已经排序的数组——这貌似是一个面试题的原题,固然若是了解了归并算法,这道题也就无所谓了。解决合并的关键,通常的方法是准备一个新的空数组,而后须要三个指针,分别指向两个待合并数组和这个新数组,以后的操做,就是每次比较指向两个数组指针位置的指,选择大的那个放入新数组指针位置,而后被选择的数组指针后移,同时指向新数组的指针也后移。用指针来解释并非什么好办法,更确切的描述应该是一个索引位置。固然Python的语法中,是很好解释的:
def mergeSort(L,start,end): assert(type(L)==type([''])) length = len(L) if length==0 or length==1: return L def merge(L,s,m,e): left = L[s:m+1] right = L[m+1:e+1] while s<e: while(len(left)>0 and len(right)>0): if left[0]>right[0]: L[s] = left.pop(0) else: L[s] = right.pop(0) s+=1 while(len(left)>0): L[s] = left.pop(0) s+=1 while(len(right)>0): L[s] = right.pop(0) s+=1 pass if start<end: mid = int((start+end)/2) mergeSort(L,start,mid) mergeSort(L,mid+1,end) merge(L,start,mid,end)
归并排序在比较模型中,是速度较快的一种,因为每次都选择中间位置,因此它是稳定的,并且属于同一数组中的数据自己并不须要相互比较,它减小了比较的次数,只须要大约N次这样的比较,可是因为它须要不停的将数组等分,因此复杂度是Nlog2(N)。若是真的理解了归并排序,我想以前提到的那个面试题,确定不是问题,另外,若是每次并非两等分,而是在1/10的位置进行划分呢,它的复杂度又是多少呢?有时候我面试的时候会这么去问。下面继续另外一个典型的分治算法。
做为排序算法中老大级的快速排序,绝对是不少人的老大难。难就难在伪代码到代码的转换上——对与它的“分”和“合”,大部分人都能搞明白:选取待排序数组中的一个元素,将数组中比这个元素大的元素做为一部分,而比这个元素小的元素做为另外一部分,再将这两个部分和并。
若是不考虑空间的申请,也就是不在元素组就行排序的话,这个算法写起来就是基本的递归调用,在python中尤其突出,以下:
def quickSortPython(l): assert(type(l)==type([''])) length = len(l) if length==0 or length==1: return l if len(l)<=1: return l left = [i for i in l[1:] if i>l[0]] right = [i for i in l[1:] if i<=l[0]] return quickSortPython(left) +[l[0],]+ quickSortPython(right)
python的这种列表推导的写法,简化了代码书写,却牺牲了资源——这也就是快速排序难的部分,须要在原数组进行排序,也就是不使用额外的空间。
解决这个问题的关键,是在进行“分”的时候,不仅从数组的一边进行比较,而是从数组的两边同时进行比较,而后相互补位。代码以下:
def quickSort(l,s,e): assert(type(l)==type([''])) length = len(l) if length==0 or length==1: return l def partition(l,start,end): pivot = l[start] while start<end-1: while end>start and l[end]<pivot: end-=1 l[start] = l[end] while end>start and l[start]>pivot: start+=1 l[end] = l[start] l[start] = pivot return start pass #random pivot def random_partition(l,start,end): i = random.randint(start,end) temp = l[i] l[i] = l[start] l[start] = temp return partition(l,start,end) if s<e: m = partition (l,s,e) quickSort(l,s,m-1) quickSort(l,m+1,e) return l pass
上面的代码,有一部分并无使用,也就是random_partition这个函数。解释这个须要先讨论一下快速排序的特色。快速排序在原数组排序,因此空间复杂度很好,可是它的时间消耗呢?它在“分”的时候,和归并算法不一样的,是归并算法选取的是“位置”,而快速排序选取的是“值”。咱们能保证每次的位置都是中间位置,可是咱们不能保证每次递归的时候,每次的Pivot都是最中间的值。
这就致使了快速排序算法的不稳定性,咱们没法肯定给定的待排序数组,若是给定的是一个已经排序的数组,而每次“分”的时候又选取了它的最值,那么结果是极端的——不只每次“分”的时候须要对比N次,并且最终会被划分为N份,也就是最糟糕的状况——NxN的复杂度。还记得我最后在归并算法里提到的问题吗,若是按照1/10去分组的状况,其实这里一样适用,经过归并算法能够知道,最好的状况,是每次都正好分到了中间位置上,这时候的复杂度和归并算法同样,是Nlog2N。
因为咱们不可能去改变用户的输入,只能从程序角度进行优化,因此在每次选取pivot的时候,随机的进行选取,从总体的几率角度来将,它的复杂度趋于最优。
上面这几种,是比较模型中数组形式进行比较的,若是熟悉数据结构的话,固然会想到数组的另外一个表示方式——树。使用树的方法进行对比的排序,这里讨论两个方法,堆排序和二叉树排序。
对于没有学过数据结构的我来讲,第一次看到堆排序的时,各类定义和公式,让我感受脑壳疼。在这里讨论这种排序的时候,我也不想用那种让我脑壳疼的办法。
首先要知道的是,数组能够又一个二叉树来表示,既然是二叉树,它的表示也就是第一层一个节点,第二层两个节点,第三层四个节点,第四层八个节点。。。数组元素的放置位置就是挨着放,第一个元素放在第一层的惟一一个点,第二层的两个点放接下来的两个元素,即元素2和3,第三层的四个点,继续接下来的4个元素,即元素五、六、七、8。。。一直这么放下去,因为是二叉树,每次两分,因此树的深度是log2N。对于每个节点,它的根节点在它的下一层,数组上的位置,就是2倍。
这就是一个数组的二叉树形式的理解,这是堆排序的基础(事实上这并不须要代码完成)。接下来的任务,是要把这个二叉树改形成所谓的堆。堆能够这样去理解,也就是对于二叉树来讲,父节点的值大于子节点。在上面数组对应的二叉树中,咱们须要将它改形成一个父节点值大于子节点值的二叉树。办法是从后向前的遍历每一个父节点,每一个父节点和两个子节点进行对比,并进行调整,直到造成一个堆——这个时候,根节点的值是最大的。
将这个跟节点的值和数组最后一个值进行换位,后然继续上面的调整,造成堆,找到根节点,与倒数第二个值换位。。。以此类推,直到数组排序完毕。这就是所谓的堆排序,它的python代码以下:
def heapSort(L): assert(type(L)==type([''])) length = len(L) if length==0 or length==1: return L def sift_down(L,start,end): root = start while True: child = 2*root + 1 if child > end:break if child+1 <= end and L[child] > L[child+1]: child += 1 if L[root] > L[child]: L[root],L[child] = L[child],L[root] root = child else: break for start in range((len(L)-2)/2,-1,-1): sift_down(L,start,len(L)-1) for end in range(len(L)-1,0,-1): L[0],L[end] = L[end],L[0] sift_down(L,0,end-1) return L
因为堆排序的堆的高度为log2N,而它每次调整的时候须要对比的次数趋向于N,因此总体的时间复杂度是N*log2N,可是它并不稳定的一种算法,依赖于给定的待排序数组。另外,堆排序是在原来的数组(二叉树)上进行调整和换位,并无申请多余的空间。和冒泡一类两两相比的排序算法比较,堆排序主要是使用二叉树构建堆的方式,传递的排序结果。
可是事实上,每次根节点和后面元素置换的同时,二叉树其余节点并无改变,因此咱们可使用额外的空间来记录这些节点的排列状况,提升排序速度。
这是另外一个使用树进行排序的方法,和堆排序不一样的是,这种方法须要这正的构建二叉树,而不是使用数组的二叉树形式。它的核心在与构建二叉树时的顺序以及输入二叉树时的顺序。
具体方法是,依次读取待排序数组的元素,并将其添加为一个二叉树的节点;添加的时候,按值的大小放在节点的左右,若是左右节点已经被占用,则递归到子节点进行添加。二叉树输出的时候,采起前序遍历或者后序遍历的方式输出。具体的Python代码以下:
def binaryTreeSort(l): assert(type(l)==type([''])) length = len(l) if length==0 or length==1: return l class Node: def __init__(self,value=None,left=None,right=None): self.__value = value self.__left = left self.__right = right @property def value(self): return self.__value @property def left(self): return self.__left @property def right(self): return self.__right class BinaryTree: def __init__(self,root=None): self.__root = root self.__ret=[] @property def result(self): return self.__ret def add(self,parent,node): if parent.value>node.value: if not parent.left: parent.left = node else: self.add(parent.left,node) pass else: if not parent.right: parent.right = node else: self.add(parent.right,node) def Add(self,node): if not self.__root: self.__root = node else: self.add(self.__root, node) def show(self,node): if not node: return if node.right: self.show(node.right) self.__ret.append(node.value) if node.left: self.show(node.left) def Show(self): self.show(self.__root) b = BinaryTree() for i in l: b.Add(Node(i)) b.Show() return b.result
按以前提到的,咱们须要构建节点和二叉树的对象或者结构,而后进行遍历排序。自己须要构建二叉树和遍历输入,因此复杂度不如好的直接排序算法;若是不考虑空间开销和输出遍历,它总体的复杂度仍是N*log2N的。因此总体的复杂度介于冒泡算法等普通排序算法和快速排序等高级排序算法之间。
文中要讨论的基于比较模型的排序算法暂时只讨论这么多,最后讨论二叉树排序,是为了引深一个问题——比较模型的排序算法复杂度还能在优化吗?答案是不行的,纯比较模型的排序算法,最好的时间复杂度就是N*log2N了。咱们能够改造二叉树排序来证实这一点,固然仍是以大白话为主,我不喜欢繁琐的公式。
这个问题的证实,是须要一套模型理论的,即决策树。咱们抛开各类理论,能够简单的认为,这就是一个二叉树。这个二叉树的最终展开,就是全部的决策,在这里就是一个待排序数组的全部数序集合,一个N个元素的全部排序个数为N!个。也就是说,从这个二叉树的根节点开始,最终会有N!个子节点。那么这个二叉树的深度,也就是最终执行的次数。实际上,也就是2^h=N!,经过数学推导,能够获得h<N*log2N。推理过程就是两边同时取Log,但这不是这里的重点,重点是基于比较模型的排序算法,时间复杂度不会小于N*log2N。
若是想要在比较模型上继续提升排序速度,在模型自己上没有能够改进的空间,只能使用其余办法——好比刚才提到的空间换时间的方法,使用其余空间存储一些重复的对比,或者使用混合的比较模型。
事实上,大多数内置的排序算法都是混合型的,咱们的目的是加快排序的速度,而不是模型自己。一种普遍采起的排序算法,是在数据量很大的时候,采起快速排序的方式,而在当分组很小的时候,使用其余稳定的排序方法。这样的混合型算法,综合效果是最好的,也就是通常内置排序使用的方法。
除了创建在比较模型上的排序算法,还有一些其余的排序算法,它们并不是比较的去排序,而是其余的方法,基本上很难想到。其中一个比较简单的,是桶排序。
桶排序是一种计数排序方法,用标记过号码的桶,去装待排序数组中的数据,数组元素的值对应着桶的编号,最后按桶的标号取出。具体的方式是,获取待排序数组的最大值,以这个最大值创建数组,并将全部元素置为0,遍历待排序数组,若是元素的值和桶的编号相等,则桶的值自动加一。遍历完毕后,按照桶的编号倒序输入。具体pythono实现以下:
def countSort(l): assert(type(l)==type([''])) length = len(l) if length==0 or length==1: return l m = max(l) ret = [] storage = [0]*(m+1) def count(x): storage[x]+=1 def pop(x): tem = storage[x] while tem>0: ret.append(x) tem-=1 map(lambda x:count(x),l) map(lambda x:pop(x),xrange(m,0,-1)) return ret
这种计数排序的方法并非用于数序很大的状况,并且数据越紧凑排序效果越好。固然这样的算法还有能够提升的地方,那就是除了找到待排序数组的最大值之外,还能够找到它的最小值,以缩短申请的空间。可是提升的效果有限。这样的算法对环境要求很高,可是若是知足这样的环境,它的排序效果,很是高效。好比百度百科中的一个例子:
--------------------------------------------
一年的全国高考考生人数为500 万,分数使用标准分,最低100 ,最高900 ,没有小数,你把这500 万元素的数组排个序。
分析:对500W数据排序,若是基于比较的先进排序,平均比较次数为O(5000000*log5000000)≈1.112亿。可是咱们发现,这些数据都有特殊的条件: 100=<score<=900。那么咱们就能够考虑桶排序这样一个“投机取巧”的办法、让其在毫秒级别就完成500万排序。
方法:建立801(900-100)个桶。将每一个考生的分数丢进f(score)=score-100的桶中。这个过程从头至尾遍历一遍数据只须要500W次。而后根据桶号大小依次将桶中数值输出,便可以获得一个有序的序列。并且能够很容易的获得100分有***人,501分有***人。
实际上,桶排序对数据的条件有特殊要求,若是上面的分数不是从100-900,而是从0-2亿,那么分配2亿个桶显然是不可能的。因此桶排序有其局限性,适合元素值集合并不大的状况。
-------------------------------------------------
当给定的待排序数组没有重复数据,并且数据量很是大,即属于桶排序状况的一种极端特殊的状况,使用桶排序的话,咱们须要很大的空间消耗,而且桶中的计数,对于结果意义不大。
咱们能够将其改造和优化,优化的部分就是若是减小空间的消耗,而不关心桶的计数——数量只有0或者1,而不是>1。0或者1的特色,给了咱们启发——咱们可使用计算机的位进行存储。这种方法就是位图排序。
若是使用其余语言,可能很难想到这种方法。它使用位图的方法记录待排序数组中元素的数据,在一些高级的编程语言中,开辟二维数据空间很容易作到——可是在C中,位图是使用位操做实现的,这大大的提升了空间的使用率,并且,使用位运算,效率远大于除和余的操做。
白话的解释这种算法,就是在桶排序的大思路下,不在使用桶记录数据,而是使用bit位。若是申请一个int的二维数组,每一个位置上只放1和0,那么太浪费空间了——在32位的操做系统上,一个Int彻底能够存储32位的标记,算法的核心就是如何找到这个位置。为了更好的使用位图的方式,这里我采用位运算进行编写,对应的除和余能够达到一样的操做,可是性能很低,python的代码以下:
def setSort(L): assert(type(L)==type([''])) length = len(L) if length==0 or length==1: return L BIT = 32 SHIFT = 5 MASK = 0x1f N = 1+len(L)/BIT a = [0]*N ret = [] def clearmap(i): a[i>>SHIFT] &= ~(1<<(i & MASK)) def setmap(i): a[i>>SHIFT] |=(1<<(i & MASK)) def showmap(i): for i in xrange(N): for j in xrange(32): if a[i]&(1<<j): ret.append(32*i+j) map(lambda x: clearmap(x),L) map(lambda x: setmap(x),L) map(lambda x: showmap(x),xrange(N)) if ret: return ret
这种方法很巧妙,可是使用范围比较窄。《编程珠玑》有过相似的问题,书中就是用这种方法实现的:
假设整数占32位,1M内存能够存储大概250000个整数,第一个方法就是采用基于磁盘的合并排序算法,第二个办法就是将0-9999999切割成40个区间,分40次扫描(10000000/250000),每次读入250000个在一个区间的整数,并在内存中使用快速排序。
尽管这种排序方法使用范围比较小,可是在算法设计上,给了咱们很大的思考空间——好比哈希结构的设计,一些面试题可能用涉及到使用数组去构建哈希表或者字典,本质上都是用空间定位换取时间。固然这里不深刻讨论。
讨论完这些经常使用的排序算法后,须要对它们进行一下测试,python中的测试和分析仍是比较容易的,能够借用unittest直接编写。固然还须要一些准备:
乱序数组:
L = range(5000)
random.shuffle(L)
使用装饰器用来计算时间(语法糖),时间计算上尽量使用time.clock(),windows系统上它和时钟时间是一致的,更加精确:
def timeCount(func): def wrapper(*arg,**kwarg): start = time.clock() func(*arg,**kwarg) end =time.clock() print 'used:', end - start return wrapper
一个执行的类,用来invode方法,并打印信息:
class Executor: def __init__(self, func, *args, **kwargs): self.func = func self.args = args self.kwargs = kwargs self.do() @timeCount def do(self): print '-----start:',self.func,'-----' self.ret = self.func(*self.args, **self.kwargs) def __del__(self): print '-----end-----'
其余一些Python语法说明:
接下来的是对方法的调用:
class TestSort(unittest.TestCase): def test_01_bubbleSort(self): Executor(bubbleSort,L[:]) pass def test_02_selectSort(self): Executor(selectSort,L[:]) pass def test_03_insertSort(self): Executor(insertSort,L[:]) pass def test_04_mergeSort(self): Executor(mergeSort,L[:],0,len(L)-1) pass def test_05_heapSort(self): Executor(heapSort,L[:]) pass def test_06_binaryTreeSort(self): Executor(binaryTreeSort,L[:]) pass def test_07_quickSort(self): Executor(quickSort,L[:],0,len(L)-1) pass def test_08_quickSortPython(self): Executor(quickSortPython,L[:]) pass def test_09_countSort(self): Executor(countSort,L[:]) pass def test_10_setSort(self): Executor(setSort,L[:]) pass def test_11_builtinSort(self): Executor(sorted,L[:]) pass if __name__=="__main__": unittest.main()
对于5000的无序数据,咱们最终的结果以下:
-----start: <function bubbleSort at 0x01DDF2F0> ----- used: 1.84792233602 -----end----- .-----start: <function selectSort at 0x01DDF430> ----- used: 1.00796886225 -----end----- .-----start: <function insertSort at 0x01DDF470> ----- used: 1.07336808288 -----end----- .-----start: <function mergeSort at 0x01DDF4B0> ----- used: 0.0483045602555 -----end----- .-----start: <function heapSort at 0x01DDF4F0> ----- used: 0.0332463558075 -----end----- .-----start: <function binaryTreeSort at 0x01DDF530> ----- used: 0.114560597626 -----end----- -----start: <function quickSort at 0x01DDF570> ----- .used: 0.0272368204168 -----end----- .-----start: <function quickSortPython at 0x01DDF5B0> ----- used: 0.0161404992862 -----end----- .-----start: <function countSort at 0x01DDF5F0> ----- used: 0.00377434774631 -----end----- .-----start: <function setSort at 0x01DDF630> ----- used: 0.294515750811 -----end----- .-----start: <built-in function sorted> ----- used: 0.00143839472039 -----end-----
不考虑一些python调用上性能的问题,这样的结果咱们能够分析获得:
- 冒泡,插入和选择排序的时间消耗最大;
- 其余纯比较模型的排序中,快速排序最快;
- 使用空间换取时间的话,改造过的快速排序时间上优于初始快速排序,由于免去了数据交换的消耗;
- 本示例中,桶排序(计数排序)速度优于其余几种示例排序;
- 内置的排序方法速度最优
固然,每种方法都还能够优化,甚至优化到和内置排序算法同样的速度。