我在论坛中阅读了以下内容:
合并排序非常有效 不可变的数据结构,如链接 列表
和
快速排序通常比快 存储数据时合并排序 记忆。但是,当数据集是 巨大的,存储在外部设备上 比如硬盘,合并排序就是 在速度方面明显的赢家。它 最大限度地减少昂贵的读取 外部驱动器
和
在链表上操作时,合并排序只需要少量的辅助存储
有人能帮助我理解上述论点吗?为什么合并排序首选排序庞大的链表?它如何最大限度地减少对外部驱动器的昂贵读取?基本上我想理解为什么会选择合并排序来排序一个大的链表。
答案 0 :(得分:48)
快速排序适用于就地分类。特别是,大多数操作可以根据交换数组中的元素对来定义。但是,要做到这一点,通常使用两个指针(或索引等)“遍历”数组。一个从数组的开头开始,另一个在结束时开始。然后两者都向中间工作(当他们见面时你完成了特定的分区步骤)。这对于文件来说是昂贵的,因为文件主要面向从一个方向读取,从头到尾。从结束开始并向后寻求通常是相对昂贵的。
至少在其最简单的化身中,合并排序恰恰相反。实现它的简单方法只需要在一个方向上查看数据,但涉及将数据分成两个单独的部分,对部分进行排序,然后将它们合并在一起。
使用链接列表,可以轻松地(例如)在一个链接列表中使用交替元素,并操纵链接以从这些相同元素创建两个链接列表。使用数组,如果您愿意创建与原始数据一样大的副本,那么重新排列元素使交替元素进入单独的数组很容易,但在其他方面则更为重要。
同样,如果将源数组中的元素合并到一个新数组中并且数据按顺序合并,那么与数组合并很容易 - 但是在不创建数据的全新副本的情况下进行这样做是完全不同的故事。使用链接列表,将两个源列表中的元素合并到一个目标列表中是微不足道的 - 再次,您只需操作链接,而无需复制元素。
至于使用Quicksort为外部合并排序生成排序运行,它确实有效,但它(通常)是次优的。要优化合并排序,通常需要在生成时最大化每个排序“运行”的长度。如果您只是读入适合内存的数据,请将其编写并写出来,每次运行将限制为(略小于)可用内存的大小。
尽管如此,你可以做得比这更好。首先阅读一个数据块,但不是在其上使用Quicksort,而是构建一个堆。然后,当您将每个项目从堆中写入已排序的“运行”文件时,您将从输入文件中读取另一个项。如果它大于您刚写入磁盘的项目,则将其插入现有堆中,然后重复。
较小的项目(即,属于已经写入的项目之前),您保持独立,并构建到第二个堆中。当(并且仅当)第一个堆为空,并且第二个堆已经占用了所有内存时,您退出将项目写入现有的“运行”文件,然后开始新的。
这究竟有多有效取决于数据的初始顺序。在最坏的情况下(输入按逆序排序)它根本没有好处。在最好的情况下(输入已经排序),它允许您通过输入在一次运行中“排序”数据。在一般情况下(以随机顺序输入),它可以让你大约加倍每个排序运行的长度,这通常会提高速度大约 20-25%(尽管百分比取决于多大您的数据超过可用内存。
答案 1 :(得分:20)
Quicksort依赖于能够索引到数组或类似结构。如果可能的话,很难击败Quicksort。
但是你不能很快直接索引到链表。也就是说,如果myList
是链接列表,那么myList[x]
是否可以编写此类语法,将涉及从列表的头部开始并遵循第一个x
链接。对于Quicksort所做的每一次比较,都必须完成两次,而且这样做会很快变得昂贵。
在磁盘上也是如此:Quicksort必须寻找并阅读它想要比较的每个项目。
在这些情况下合并排序更快,因为它按顺序读取项目,通常使log2(N)通过数据。涉及的I / O要少得多,并且链接列表中的链接花费的时间要少得多。
当数据适合内存并且可以直接寻址时,Quicksort很快。当数据不适合内存或者到达项目的成本很高时,Mergesort会更快。
请注意,大文件排序通常会尽可能多地将文件加载到内存中,然后将其写入Quicksort并将其写入临时文件,然后重复执行直到它遍历整个文件。此时有一些块,每个块都被排序,然后程序进行N路合并以产生排序的输出。
答案 2 :(得分:3)
快速排序会将记录移至列表中间。为了将项目移动到索引X,它必须从0开始并一次迭代一条记录。
mergesort将列表拆分为几个小列表,并且只比较列表的项目头部。
合并排序的设置通常比快速排序所需的迭代更昂贵。但是,当列表足够大或读取很昂贵时(如从磁盘中读取),快速排序迭代所需的时间成为主要因素。