快速排序适用于原地排序。具体而言,大多数操作可以定义为在数组中交换一对元素。为此,通常需要使用两个指针(或索引等)“遍历”数组。一个从数组的开头开始,另一个从末尾开始。然后两者沿着数组向中间移动(当它们相遇时,您完成了特定分区步骤)。由于文件主要面向从头到尾的单向读取,因此这很耗费资源。反向从末尾开始查找通常比较昂贵。
至少在其最简单的形式中,归并排序几乎完全相反。实现它的简单方法只需要沿一个方向查看数据,但涉及将数据分成两部分、对这些部分进行排序,然后将它们合并在一起。
对于链表,很容易从一个链表中获取交替的元素,并操纵链接以从这些相同的元素创建两个链表。对于数组,如果愿意创建与原始数据一样大的副本,则可以轻松地重新排列元素,使交替元素进入不同的数组,否则就变得更加复杂。
同样,如果将源数组中的元素合并到按顺序排列的新数组中,则使用数组进行合并很容易,但要在原地进行而不创建整个新数据的副本,则完全是另一回事。对于链表,将两个源列表中的元素合并到单个目标列表中非常简单——您只需操纵链接,而无需复制元素。
至于使用快速排序来生成外部归并排序的排序运行,它确实有效,但通常不是最优选择。为了优化归并排序,通常希望在生成每个排序“运行”时最大化其长度。如果简单地读入适合内存的数据,然后进行快速排序并写出它,那么每个运行将受限于可用内存的大小(略小于可用内存的大小)。
一般情况下,你可以比这种方式做得更好。你首先读入一块数据,但不是用快速排序算法,而是建立一个堆。然后,当你从堆中把每个元素写到排序后的“运行”文件中时,你会从输入文件中再读取
另一个元素。如果它比你刚刚写入磁盘的元素大,你就将它插入到现有的堆中,然后重复该过程。
那些较小的元素(即属于已经写入的元素之前的元素),你要保留并构建成第二个堆。只有当第一个堆为空且第二个堆占据了所有内存空间时,你才停止向现有的“运行”文件写入元素,并开始新的操作。
这种方法的效果取决于数据的初始顺序。在最坏的情况下(输入按相反顺序排列),它根本没有作用。在最好的情况下(输入已经排序),它让你可以在单次输入中“排序”数据。在平均情况下(输入的顺序是随机的),它使每个排序后的运行时间增加约一倍,这通常会提高速度约
20-25%(尽管百分比取决于数据的大小与可用内存的差异)。