当前位置: 首页 > 面试题库 >

为什么Collections.sort使用合并排序而不是quicksort?

法弘亮
2023-03-14
问题内容

我们知道快速排序是最快的排序算法。

JDK6 collections.sort使用合并排序算法而不是快速排序。但是Arrays.sort使用快速排序算法。

Collections.sort使用合并排序而不是快速排序的原因是什么?


问题答案:

极有可能从乔希布洛赫§:

我确实写了这些方法,所以我想我有资格回答。确实没有最佳的排序算法。与mergesort相比,QuickSort有两个主要缺陷:

  1. 它不稳定(如parsifal所述)。

  2. 它不能 保证 n log n的性能。在病理输入上,它可以降级为二次性能。

对于原始类型,稳定性是没有问题的,因为没有与(值)相等不同的身份概念。对于Bentely和McIlroy的实现(或随后的Dual Pivot
Quicksort
),实践中认为二次行为的可能性并不是问题,这就是为什么将这些QuickSort变体用于原始排序的原因。

排序任意对象时,稳定性至关重要。例如,假设您有代表电子邮件的对象,并且首先按日期对它们进行排序,然后再按发件人对它们进行排序。您希望它们在每个发件人中按日期进行排序,但是只有在排序稳定的情况下才是正确的。这就是为什么我们选择提供一个稳定的排序(合并排序)来对对象引用进行排序的原因。(从技术上讲,多个顺序稳定排序会导致键的字典顺序按排序的相反顺序进行:最终排序确定最高有效的子键。)

合并排序可以 保证 n log
n(时间)性能,无论输入什么,这都是一个很好的附带好处。当然有一个缺点:快速排序是“就地”排序:它仅需要登录n个外部空间(以维护调用堆栈)。另一方面,合并排序需要O(n)个外部空间。如果输入数组几乎已排序,则TimSort变体(在Java
SE 6中引入)需要的空间要少得多(O(k))。

另外,以下是相关的:

java.util.Arrays.sort和java.util.Collections.sort(间接)用于对对象引用进行排序的算法是一种“修改的mergesort”(如果低子列表中的最高元素小于,则忽略合并。高子列表中的最低元素)。”
这是一种相当快的稳定排序,可确保O(n log n)性能并需要O(n)额外空间。在当时(约书亚·布洛赫(Joshua
Bloch)于1997年撰写),这是一个不错的选择,但是今天,我们可以做得更好。

自2003年以来,Python的列表排序使用了一种称为timsort的算法(在Tim
Peters编写之后)。它是一种稳定的,自适应的,迭代的合并排序,在部分排序的数组上运行时,所需的比较少于n
log(n)个比较,而在随机数组上运行时,其性能可与传统的mergesort媲美。像所有适当的合并排序一样,timsort是稳定的,并且可以在O(n
log n)时间(最坏的情况)下运行。在最坏的情况下,timsort需要临时存储空间来存储n /
2个对象引用;在最佳情况下,它仅需要少量恒定的空间。与当前实现相反,当前实现始终需要额外的空间来存储n个对象引用,并且仅在几乎排序的列表上击败n log
n。

Timsort在此处进行了详细描述:http
://svn.python.org/projects/python/trunk/Objects/listsort.txt 。

蒂姆·彼得斯(Tim Peters)的原始实现是用C编写的。约书亚·布洛赫(Joshua
Bloch)将其从C移植到Java,并对其进行了最终测试,基准测试和广泛的调试。结果代码是java.util.Arrays.sort的直接替代。在高度排序的数据上,此代码的运行速度可高达当前实现(在HotSpot服务器VM上)的25倍。在随机数据上,旧的和新的实现的速度是可比的。对于非常短的列表,即使对随机数据,新的实现也比旧的实现快得多(因为它避免了不必要的数据复制)。

没有一个“最佳”选择。与许多其他事情一样,这是权衡的。



 类似资料:
  • 为什么只适用于s而不适用于s?有什么特别的原因吗?

  • 我使用的是JDK-8(x64)。对于<code>数组。sort</code>(原语)我在Java文档中找到了以下内容: 排序算法是弗拉基米尔·雅罗斯拉夫斯基、乔恩·本特利和约书亚·布洛赫的双轴快速排序。' 对于<code>集合。sort(对象)我找到了这个“Timsort”: 这个实现是一个稳定的、自适应的、迭代的合并…这个实现将指定的列表转储到一个数组中,对数组进行排序,并迭代列表,从数组中的相

  • 我读过这些话: 为了使动态规划适用,一个问题必须具有两个关键属性:最优子结构和重叠子问题。如果一个问题可以通过组合非重叠子问题的最优解来解决,那么这个策略就叫做“分而治之”。这也是为什么mergesort和quicksort没有被归类为动态规划问题的原因。 我有三个问题: 为什么合并排序和快速排序不是动态编程? 我认为合并排序也可以将小问题和小问题分开,然后做同样的事情等等。 Dijkstra算法

  • 问题内容: 我正在使用JDK-8(x64)。对于(原始),我在Java文档中发现了以下内容: 排序算法是Vladimir Yaroslavskiy,Jon Bentley和Joshua Bloch编写的Dual-Pivot Quicksort 。 对于(对象),我找到了“ Timsort”: 此实现是一个稳定的,自适应的,迭代的 mergesort 。此实现 将指定的列表转储到数组中,对数组进行排

  • 我第一次用一个辅助数组实现了合并排序,以尝试使用JavaScript实现可视化。这似乎应该是有效的,但它不是。任何帮助或提示将不胜感激。 编辑:我忘了包括它不起作用的情况。它们是: 输入:[4, 2, 5, 6, 7, 7]输出:[4, 2, 5, 6, 7, 7] 输入:[6,6,6,4,6,2]输出:[4,6,6,6,6,2] 输入:[6, 7, 3, 10, 7, 9, 6, 3, 4, 6

  • 我正在维基百科上阅读关于外部排序的文章,我需要理解为什么两阶段合并比一阶段合并更有效。 Wiki:但是,单次合并有一个限制。随着区块数量的增加,我们将内存分成更多的缓冲区,因此每个缓冲区都较小,因此我们必须进行许多较小的读取,而不是较少的较大读取。 因此,对于100 MB内存中的50 GB的排序,使用单个合并过程是没有效率的:磁盘需要用500个数据块中的每个数据块(我们一次从每个数据块读取100M