第七章讲的第一个sorting algorithm是插入排序(insertion sort)。
Insertion sort分为几个phase,从i=1到i=N-1,每个phase将处于i的元素向前插入到合适的位置上,故称为插入排序。插入排序的运行时间为O(N^2),是比较基础的一种排序算法。
另外书中引入了反序对的概念,也即如果i < j, a[i] > a[j](假设排序结果为升序),那么i与j位置上的两个元素即为反序对。通过分析反序对可以得出,排序的过程就是为了将所有反序对排除。对于包含N个元素的随机数组,平均产生的反序对为N*(N-1)/4个,由于近邻交换(adjacent swap)只能一次消除一个反序对,故所有只包含近邻交换的排序算法,如bubble sort等,其平均运行时间都是O(N^2)。只有包含了far swap的排序算法才可能突破平方运行时间。
第二个sorting algorithm是shell sort,名字取自算法的发明人shell,该算法是世界上第一个成功突破平方运行时间的算法。
该算法的核心思想为分段排序,分为几个pass,在首先进行的pass中,进行比较大距离的far swap,即每个元素只和相隔一定距离的其他元素排序(实际上是分为N组插入排序),在之后pass中,距离逐渐缩小,最后一个pass中距离为0,即进行adjacent swap。可以看出在前几个pass中都在进行far swap,可以更有效的消除inversion。shell sort又分为不同的分支,区别主要是在各个pass的距离分配上。最早的shell sort的距离分配为N/2(round down),N/4...4,2,1,因为互相含有公因子,会导致对一些不必要的项之间重复排序,其效率较差。更好的方法是选用互质的距离,如2k-1,可以极大提高排序效率。
**有关shell sort的运行时间分析部分没有看很懂。
第三个sorting algorithm是heap sort,借用了第六章的数据结构heap也即priority queue。
由于heap中最小/最大值存放在根节点中,将一系列n个待排序元素存入heap中,再进行n次的DeleteMin或DeleteMax,将得到的元素依次存放在array中,即可获得相应的升序或降序排序结果。书中给出的代码巧妙利用数组实现heap并用存放heap的数组依次存储deleteMax后得到的排序好的元素,避免了额外引入heap ADT
来完成heap sort,从而降低了代码复杂度。
heap sort的运行时间非常稳定,对于已排序好的数组也好,亦或是反序的数组也好,其运行时间基本都在O(NlogN)左右。
第四个sorting algorithm为merge sort,使用了divide and conquer的递归思想,将数组分为多个分数组,各自合并来完成排序。
merge sort的基本原理是如果要将一个N元素数组排序,可以将其中分为两个近似等大的数组,然后将两个数组合并为一个新的数组,为了保证排序,将这个过程无限细分,只到数组分为N/2个只有一个元素的小数组,然后对每两个小数组合并,元素小的先填进新的数组,大的后填进去,如此得到的N/4个双元素数组继续两两合并,往复进行直到最后所有的数组合并为一个大的数组,也就是最后的排序好的结果。
通过分析可以很清楚地判断出(书上提供了两种方法,一种是凑值法,一种是暴力代换法),这是一个需要O(NlogN)运行时间的算法,并且无论原始数据的顺序性如何,其运行时间都不会变化,因为其中包括的元素比较次数与数据本身基本无关,只与总数据量有关。(虽然如果一个数组的元素全部小于另一个数组的元素的情况所需的比较数比两个数组的元素交叉递增的情况所需比较数少一半,但是其最终结果也只是一半,不到数量级的变化,因此不影响大O)
第五个sorting algorithm为quick sort,顾名思义,quick sort是速度非常快的一种排序算法,但是其快速仅限于数目比较大的排序(大于5到20个待排序元素),因此在实际运用中,quick sort常和insert sort等在小数目排序中比较有效的算法配合使用。
quick sort分为四大步骤:
1. 如果需要排序的元素数目小于cutoff,则使用insert sort来排序并返回结果。
2. 否则,在待排序集合S中选择一个元素,称为pivot(中心点)。
3. 将S中pivot以外剩余的元素划分为两个集合,一个集合S1中的元素都比pivot小,一个集合S2中的元素都比pivot大。
4. 将三个部分按S1,pivot,S2排序
其中涉及到pivot的选择,最佳的选择是采用3数中位数法,即取最左端元素,最右端元素,最中间元素,三数排序,选取中位数来作为pivot。pivot的选择不当将导致quick sort的速度大幅下降。
quick sort的运行时间基本为O(NlogN)。
而依托quick sort算法产生的求按从小到大排第K个的算法可以达到O(N)的平均运行时间。其唯一不同在于第四步中根据k的相对位置只对S1或S2进行进一步排序。
**O(N)的平均运行时间并没有分析出来,见exercise 7.24
第六个sorting algorithm为bucket Sort,是速度最快的一种排序算法,其运行时间为O(N),但是代价是额外的大量存储空间占用(以分配buckets)以及需要额外的信息(待排序元素的取值范围)
在此引入了决策树的概念,决策树的节点处为可能的大小关系,路径为相关的大小判断。以一定数量的元素大小关系为模型的决策树,其深度即为排序完成需要的比较数目,其叶节点数量即为可能的大小关系总数。若深度为d,则最多有2^d个叶节点,因而有L个叶节点则深度为logL(round up)。另又知N个元素排序的决策树其叶节点数目为N!。由此得出推论,在最坏情况下,至少需要log(N!)次比较才可以完成N个元素的排序,前提是只用关系比较来排序。
最后一个讨论的算法是用于external sorting的算法,由于前六个sorting都利用了可以直接获取待排序元素的优势,在进行external sorting时,获取元素可能会花费相当的时间,因此前六种sorting不再适用。
在external sorting中,依然使用Merge sort的基本算法,但是由于数据存储在外部,每次只能获取M个数据进行处理,称为一个run。若总数据量为N,则每个pass可以产生N/M个run,假设外部存储设备数量大于3个,则可对每个pass对各个run两两合并,则需要log(N/M)个pass。如果外部存储设备数量十分充足,大于4个的话,则可以进行multiway merge,一次对超过两个run合并,假设一次可以对k个run合并,则需要以k为底的log(N/M)个pass。如果外部存储设备数量不足,只有三个,则需要polyphase merge,即分配给两个存储设备有差量的run数目,然后渐次合并,该有差量的数目以斐波那契数列为佳,如果是multiway polyphase merge,则以k次斐波那契数列为佳。
在每个run的产生中,可以使用replacement selection。每次录入M个数据,将其存在heap里,之后每次进行一个deletemin操作,可将一个数据放入run中,同时对下一个外部存储设备中的数据进行录入,若其值大于放入run的上一个数据,则作为dummy数据存入heap之后,否则insert到heap中。如此,在待排序数据为随机排布的情况下,可将每个run的大小平均扩充至2M。