本文是对http://blog.csdn.net/libinjlu/article/details/10948049中排序算法代码实现的分析。
1.一些简单排序算法的下界。
以数为成员的数组的逆序是指具有性质 i<j 但 a[i]>a[j] 的序偶(i,j)。交换两个不按顺序排列的相邻元素恰好消除一个逆序,而一个排序的数组没有逆序。由于简单插入算法中还有O(N)项其他工作,因此运行时间是O(R+N),其中R为原是数组中的逆序数。
N个互异元素的数组的平均逆序数是N(N-1)/4.
证明: 在任意元素表L和它的反序表Lr中,两个表的序偶总个数为N(N-1),逆序数为N(N-1)/2,因此平均每表有一半,即N(N-1)/4个逆序。
通过交换相邻元素进行排序的任何算法平均需要Ω(N^2)时间。
证明:初始逆序数为N(N-1)/4=Ω(N^2),而每次交换只减少一个逆序。这个下界告诉我们为使排序算法以subquadratic或o(N^2)运行,则必须每次交换删除多个逆序。
2.希尔排序
使用希尔增量的希尔排序每一趟排序是插入排序,因此最坏是O(N^2)的。
使用Hibbard增量的希尔排序最坏是O(N^(3/2))的。
实践中最好的增量序列是(1,5,19,41,109),该序列号中或者是9×4^i ~ 9×2^i + 1 或者是 4^i - 3×2^i + 1。
3.堆排序
经验指出,堆排序是一个非常稳定的算法:它平均使用的比较只比最坏情形界指出的略少。、
4.归并排序
归并排序是用于分析递归例程技巧的经典实例:必须给运行时间写出一个递推关系:
T(1)=1
T(N)=2T(N/2)+N
求解该递推关系得T(N)=N log N+N=O(N log N)。
虽然归并排序的运行时间是O(N log N),但是它很难用于主存排序,主要问题在于合并两个排序的表需要线性附加内存,在整个算法中还要花费将数据复制到临时数组再复制回来这样的附加操作,严重减慢了速度。在所有流行的排序算法中,归并排序使用最少次数的比较。Java标准库一般排序使用的是归并排序(移动对象非常快)。
5.快速排序
快速排序是在实践中最快的已知排序算法,它的平均运行时间是O(N log N)。该算法之所以比较快,主要是由于非常精炼和高度优化的内部循环。它的最坏情形的性能为O(N^2),但稍加努力就可避免这种情形。通过将堆排序与快速排序结合起来,就可以在堆排序的O(N log N)最坏运行时间下,得到对几乎所有输入的最快运行时间。
i和j遇到等于分划枢纽的元素时,应该停止。
对于很小的数组(N<=20),快速排序不如插入排序好,采用10较好(5~20)。
快速选择
最坏情形是O(N^2),使用三数中值选取枢纽元的方法使得最坏情形发生的机会几乎是微不足道的。平均为O(N)。
6.间接排序
最好的情况是没有Comparable复制,此时有N个长为1的循环(每个元素都已经在正确的位置了)。最坏的情况为有N/2个长度为2的循环,每循环一次是复制3次,共3N/2次。
7.排序算法的一般下界
只用到比较的任何排序算法在最坏情形下都需要log(N!)取天棚次比较,并平均需要这么多次比较。
决策树是用于证明下界的抽象过程。只使用比较进行排序的每一种算法都可以用决策树表示。由排序算法所使用的比较次数等于最深的树叶的深度。
对N个元素排序的决策树必然有N!片树叶,深度至少是log L取天棚,因此至少需要log(N!)取天棚次比较,大于NlogN,因此需要Ω(N log N)次比较。
8.桶排序
适用于输入数据只由小于M的正整数组成。可以使用一个大小为M的count数组,初始化为全0。读入一个数A,则count[A]++。扫描count数组即可得已排序数据。如果有N个元素,那么算法用时O(M+N)。
9.外部排序
基本的外部排序算法使用归并排序中的合并算法。