【问题标题】:why is merge sort preferred over quick sort for sorting linked lists为什么在对链表进行排序时,合并排序优于快速排序
【发布时间】:2011-07-10 12:18:51
【问题描述】:

我在论坛上读到以下内容:

归并排序对于 不可变的数据结构,如链接 列表

快速排序通常比 数据存储时的归并排序 记忆。但是,当数据集 巨大并存储在外部设备上 比如硬盘,归并排序是 速度方面的明显赢家。它 最大限度地减少昂贵的读取 外部驱动器

对链表进行操作时,归并排序只需要少量的固定辅助存储

有人可以帮我理解上述论点吗?为什么对大型链表进行排序时首选合并排序?它如何最大限度地减少对外部驱动器的昂贵读取?基本上我想了解为什么要选择合并排序来对大链表进行排序。

【问题讨论】:

    标签: algorithm quicksort mergesort


    【解决方案1】:

    快速排序适用于就地排序。特别是,大多数操作可以根据交换数组中的元素对来定义。但是,要做到这一点,您通常会使用两个指针(或索引等)“遍历”数组。一个从数组的开头开始,另一个在数组的末尾。然后两者都朝着中间前进(当他们相遇时,您完成了特定的分区步骤)。这对文件来说很昂贵,因为文件主要是面向一个方向的阅读,从头到尾。从头到尾,向后寻找通常是相对昂贵的。

    至少在其最简单的化身中,归并排序几乎是相反的。实现它的简单方法只需要在一个方向上查看数据,但是需要将数据分成两个单独的部分,对这些部分进行排序,然后将它们重新合并在一起。

    使用链表,很容易在一个链表中获取(例如)交替元素,然后操作链接以从这些相同元素创建两个链表。使用数组,如果您愿意创建与原始数据一样大的副本,则重新排列元素以使交替元素进入单独的数组很容易,但在其他方面则更不平凡。

    同样,如果您将源数组中的元素按顺序与数据合并到一个新数组中,那么与数组合并很容易——但是在不创建数据的全新副本的情况下就地进行合并是完全不同的故事。使用链表,将两个源列表中的元素合并到一个目标列表中非常简单——同样,您只需操作链接,无需复制元素。

    至于使用 Quicksort 为外部合并排序生成排序运行,它确实有效,但它(肯定)通常是次优的。为了优化合并排序,您通常希望在生成每个排序“运行”时最大化它的长度。如果您只是读入适合内存的数据,对其进行快速排序并写出,则每次运行都将被限制为(略小于)可用内存的大小。

    不过,通常情况下,您可以做得比这更好。您首先读取一个数据块,但不是在其上使用快速排序,而是构建一个堆。然后,当您将堆中的每个项目写入已排序的“运行”文件时,您会从输入文件中读取 another 项目。如果它比您刚刚写入磁盘的项目大,则将其插入现有堆,然后重复。

    较小的项目(即,属于已经写入的项目之前)你保持分开,并构建到第二个堆中。当(且仅当)您的第一个堆为空且第二个堆已占用所有内存时,您将停止将项目写入现有的“运行”文件,并开始一个新的。

    具体的效果取决于数据的初始顺序。在最坏的情况下(输入以相反的顺序排序)它根本没有用。在最好的情况下(输入已经排序),它可以让您在一次输入中“排序”数据。在平均情况下(以随机顺序输入),它可以让您将每个排序运行的长度大约增加一倍,这通常会将速度提高 大约 20-25%(尽管百分比会根据大得多而变化您的数据超出了可用内存)。

    【讨论】:

    • 所以基本上,在处理数组时,合并排序的空间效率很低,因为它需要辅助存储来进行拆分和合并,但是在处理链表时,辅助存储是最小的..
    • @maxpayne:更重要的是,当在链表上使用归并排序时,必要的辅助存储已经是数据结构的一部分
    • 只需一点,您可以使用从头开始一直向前移动的两个指针轻松实现快速排序中的分区例程,所以这根本不是问题。 Jim Mischel 在下面的回答中给出了一个很好的理由,为什么合并排序更适合对磁盘上的数据进行排序。
    【解决方案2】:

    快速排序依赖于对数组或类似结构的索引。如果有可能,就很难击败 Quicksort。

    但是你不能很快地直接索引到一个链表中。也就是说,如果myList 是一个链表,那么myList[x](如果可以编写这样的语法)将涉及从链表的头部开始并跟随第一个x 链接。对于 Quicksort 进行的每次比较,都必须进行两次,而且很快就会变得非常昂贵。

    磁盘上的情况相同:快速排序必须查找并读取它想要比较的每个项目。

    在这些情况下,合并排序更快,因为它按顺序读取项目,通常使 log2(N) 传递数据。涉及的 I/O 少得多,跟踪链接列表中的链接所花费的时间也少得多。

    当数据适合内存并且可以直接寻址时,快速排序速度很快。当数据不适合内存或获取项目的成本很高时,合并排序会更快。

    请注意,大文件排序通常将尽可能多的文件加载到内存中,快速排序并将其写入临时文件,然后重复直到遍历整个文件。此时有一定数量的块,每个块都经过排序,然后程序进行 N 路合并以产生排序后的输出。

    【讨论】:

    • 为什么我们说快速排序需要直接访问?是因为分区例程期间的反向迭代吗?如果是这样,使用双向链表就不能处理了吗?
    • @AyushChaudhary 我猜当时(使用双向链表时),这一切都是为了让该枢轴点执行快速排序算法。一些实现使用中间结构。一遍又一遍地计算可能会降低一些性能。但是话又说回来,一些合并排序实现也需要使用结构的中间部分。所以,我猜是一样的表现?
    【解决方案3】:

    快速排序会将记录移到列表的中间。为了将一个项目移动到索引 X,它必须从 0 开始并一次迭代一条记录。

    合并排序将列表拆分为几个小列表,并且只比较列表的头部。

    合并排序的设置通常比快速排序所需的迭代成本高。但是,当列表足够大,或者读取成本很高(例如从磁盘读取)时,快速排序迭代所需的时间成为主要因素。

    【讨论】:

      猜你喜欢
      • 2015-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-19
      • 2015-06-28
      • 2013-08-29
      • 1970-01-01
      相关资源
      最近更新 更多