这里主要针对上篇的sort排序算法进行简单的总结,具体代码实现和分析见
排序sort(一):http://www.cnblogs.com/liuamin/p/6698307.html
先贴一张图:(网上找的)
上图分别是各个算法的时间复杂度和稳定性分析。下面我的总结顺序和其中的部分片段代码和上篇http://www.cnblogs.com/liuamin/p/6698307.html一样。
(1)交换排序
交换排序包括:冒泡排序和快速排序。
【1】首先冒泡排序是相邻的元素之间进行比较,当无序时进行交换,相等时不交换(所以是稳定的排序算法),直到有序。
时间复杂度分析:
最好的情况下是序列本身有序,则只要进行
for (int j = a.size() - 2; j >= i; j--)
这里的一轮比较,也就是n-1次比较,标志位为false,则说明元素有序,没有交换,所以时间复杂度为o(n)。
最坏情况下是元素逆序,这里需要进行
for (int i = 0; i<a.size() && change; i++) for (int j = a.size() - 2; j >= i; j--)
i等于每一个值都要进行一轮比较,直到 i<a.size(),也就是进行了(n-1)+........3+2+1=n(n-1)/2次比较,同时移动(交换)了等量的次数。所以时间复杂度为0(n^2)。
平均情况也是0(n^2)。
空间复杂度分析:
这里当元素无序时,要进行交换元素(swap函数),借助了一个辅助空间,所以空间复杂度为0(1)。
【2】快速排序主要是对序列的【low到high】选择pivotkey,使得它左边元素均小于它,右边的元素均大于它,返回pivotkey的下标pivot,然后再对这两部分
(【low到pivot-1】和【pivot+1到high】)重复进行快排,(递归)这里比较和交换是跳跃的,所以是不稳定的排序方法。
时间复杂度分析:
快排最坏情况下,pivotkey选择的不好,刚好是里面最大(最小)的元素,递归树是一个斜树(递归深度为n-1),也就是进行n-1次调用,第i次调用需要比较n-i次才能找到pivot, i从1到n-1,也就是比较(n-1)+........3+2+1=n(n-1)/2次,(当i=1时,比较n-1,i等于2时,比较n-2,.......,i等于n-1时,比较1次),所以时间复杂度为o(n^2)。
最好情况下,pivotkey选择的较好,是中间元素,所以每次划分的很均匀,递归树的深度为log(2n)+1,通过不等式推断,时间复杂度为o(nlog2n)。
平均情况下,数学归纳法证明,数量级为o(nlog2n)。
空间复杂度分析:
空间复杂度,主要是递归造成的栈空间,最坏是递归深度为n-1,空间复杂度为o(n),好的情况下为o(log2n),所以平均情况空间复杂度为o(log2n)。
(2)选择排序
选择排序包括简单选择排序和堆排序
【1】简单选择排序是每一轮比较后选择好最小(最大)的元素之后再进行交换,优点在于不进行频繁的交换,它是选择最小的元素进行交换,是跳跃式交换,所以是不稳定的排序算法。
for (int i = 0; i < a.size(); i++) for (int j = i + 1; j < a.size(); j++)
时间复杂度分析:
最好,最坏情况下都是比较(n-1)+........3+2+1=n(n-1)/2次,但最好情况下不交换(0次),最坏情况下也只交换n-1次。
因此简单选择排序最好,最坏,平均复杂度均为o(n^2)。
空间复杂度分析:
空间复杂度和冒泡排序类似,swap函数,借助辅助空间,所以空间复杂度为o(1)。
【2】堆排序是建立好大顶堆(小顶堆),交换(移走)最大(最小)的堆顶元素,再对剩下的n-1个元素重建堆,如此重复。。。
这里交换是跳跃的,也就是交换的是堆顶元素和堆底元素,所以是不稳定的排序方法。
swap(a[0], a[i]);
时间复杂度分析:
它所用时间主要消耗在开始建堆,和后面重建堆的时间上,开始建堆是针对非叶节点进行(约等于堆元素的一半),时间复杂度为o(n),推导见算法导论。
for (int i = (a.size() - 2) / 2; i >= 0; i--) HeapAdjust(a, i, a.size());
对重建堆,每次对剩下的i-1个元素进行重建,重建的复杂度为o(log2n),(完全二叉树的深度,n为剩下的节点数目,n=i-1,),需要取n-1次,所以时间复杂度为o(nlog2n)。
for (int i = a.size() - 1; i>0;i--) { swap(a[0], a[i]); HeapAdjust(a, 0,i-1); }
通过分析堆排序对原始记录的排序并不敏感,所以最好,最坏,平均时间复杂度都为o(nlog2n)。
空间复杂度分析:
空间复杂度也是swap函数(一个交换的暂存单元),所以空间复杂度为o(1)。
注:它的时间复杂度主要在堆的建立上,所以不适合排序元素少的序列。。
(3)插入排序
插入排序包括直接插入排序和希尔排序
【1】直接插入排序是将一个记录插入到已经排序好的有序表中,从而得到一个新的,记录数增1的有序表。i从1开始,每次将a[i]和a[i-1]进行比较,如果无序,则将元素后移,直到找出正确的位置插入a[i]。当插入的第i个元素如果与之前一个元素相等,插入的位置为相等元素a[i-1]的后面,所以是一种稳定的排序算法。
时间复杂度分析:
最坏的情况是i每为一个数,for循环要比较i-1次,i从1到n-1,所以比较了1+2+3......+(n-1)=n(n-1)/2次,交换次数也是n^2数量级的,所以时间复杂度为o(n^2),但是和冒泡和简单选择排序相比,直接插入排序性能好一点。
if (a[i] < a[i - 1]) for (j = i - 1; j >= 0 && temp < a[j]; j--)
最好的情况是比较了n-1次(a[i]和a[i-1]比较),不需要交换(0次)。
平均情况时间复杂度也是o(n^2)。
空间复杂度分析:
这里借助了一个temp的交换暂存空间,所以空间复杂度为o(1)。
【2】希尔排序是跳跃式的向前移动(a[i] 与a[i - increasment]比较),跳跃的距离取决于增量序列 increasment,但最后的增量值一定是1,这样和直接插入排列一样,但移动的次数大大减小,因为元素已经基本有序。因为是跳跃式的移动和比较,所以是一种不稳定的排序方法。
时间复杂度分析:
希尔排序时间复杂度分析很难,它比较次数与移动(交换)次数依赖于增量因子序列increasment的选取,目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,但是最后一个增量因子必须为1。大量的研究表明当取得合适的增量因子序列时,时间复杂度为o(n^3/2),效率高于n^2。
空间复杂度分析:
与直接插入排序一样,也是借助了一个temp的交换暂存空间,所以空间复杂度为o(1)。
(4)归并排序是借助辅助空间temp[ ],将待排序序列a分成若干个子序列,再进行归并为有序的temp[ ],之后再把有序的temp[ ]归并为整体有序序列a,因为它比较元素时如果相等,先将位于序列前面的元素归并在前面,所以不会改变相等元素的顺序,是一种稳定的排序方法。
空间复杂度分析:
归并排序有两种实现方式:第一种是递归方法实现,空间复杂度除了temp[ ],还有递归用到的栈空间,所以整体的空间复杂度为o(n+log2n);
另一种是非递归方法,只有辅助空间temp[ ],所以它的空间复杂度为o(n),
因此归并排序更推荐使用非递归归并排序方法。
时间复杂度分析:
所有元素扫描时间为n(MergePass函数),再进行归并排序要进行 o(log2n)次(由完全二叉树的深度确定,while循环),所以时间复杂度为o(nlog2n)。
注:归并排序是一种比较占内存的排序方式,(利用空间换取了时间),但很高效稳定。
综上:
像冒泡,简单选择排序和直接插入排序,适合待排序序列的元素个数不是很多的情况,他们的平均时间复杂度和空间复杂度都是一样的,且都是稳定的。
当待排序序列的元素个数不是很多时,选择直接插入排序,但是如果每个元素的关键字记录很大(占内存)时,则选择简单选择排序,因为它移动次数少,最多才n-1次。
当序列元素个数很多时,选择快速排序,但是要求稳定时,选择归并排序,但是归并排序又比较占内存。。
所以要根据自己需求和具体分析,选择合适的排序方法。。