什么是排序算法的稳定性,为什么它很重要?

Posted

技术标签:

【中文标题】什么是排序算法的稳定性,为什么它很重要?【英文标题】:What is stability in sorting algorithms and why is it important? 【发布时间】:2010-12-03 19:39:12 【问题描述】:

我很好奇,为什么稳定性在排序算法中重要还是不重要?

【问题讨论】:

用于并行化目的? eg:归并排序是稳定的,可以很好地并行化,快速排序也是如此。 经典快速排序不稳定 稳定排序算法 - IBM (Insertion, Bubble, Merge) 给像我这样可能误解概念的人的注意事项:保证相等元素的顺序。表示:如果认为稳定排序中的元素相等,然后他们将遵循先前的顺序。 这不是我以前的想法:如果之前顺序中的元素被认为是相等的,那么在接下来的稳定排序中,它们将遵循之前的顺序。尽管您可能会发现后一种理解在许多情况下也很有意义。 【参考方案1】:

一种稳定的排序算法是一种以相同的元素在输入中出现的顺序对它们进行排序的算法,而不稳定的排序可能不满足这种情况。 - 感谢我的算法讲师 Didem Gozupek 提供了对算法的见解

由于某些人不了解演示文稿的逻辑的一些反馈,我再次需要编辑问题。 它说明了 w.r.t 的排序。第一个元素。 另一方面,您可以考虑由键值对组成的插图。

稳定的排序算法:

插入排序 合并排序 冒泡排序 时间排序 计数排序 块排序 四分法 库排序 鸡尾酒调酒器排序 侏儒排序 奇偶排序

不稳定的排序算法:

堆排序 选择排序 Shell 排序 快速排序 Introsort(以快速排序为准) 树排序 循环排序 平滑排序 比赛排序(以 Hesa​​psort 为准)

【讨论】:

您的价值观不相等。您比较 9,7 和 9,8,但根据稳定性检查,您需要相同的值,例如 9,7 或 9,8。在稳定的算法中,相同的值应该以相同的顺序排列。 不,要检查稳定性,您的值应该相同。我的意思是假设您使用两个 9,7 并将其命名为节点 A 和节点 B。如果每个排序操作顺序都像 A,B (而不是它们相等),请理解排序算法是稳定的(如归并排序)。如果 A,B 顺序在多次排序时发生变化(1. 排序 A,B 然后 B,A 再 A,B 等),请了解排序算法不稳定(如快速排序)@snr @snr [9, 6] 在输入数组中不存在。我认为您的意思是 [9, 8] 在最后一个数组条中。 @erhun 我相信他只按第一个数字(逗号前的那个)排序,并使用第二个数字作为参考,让您看到第一个 9 与第二个 9 不同. @erhun 元素相同的定义是什么?这正是使用的排序标准!它可以是你想要的任何人。我的标准是所有能被 10 整除的数字都是相等的,无论是 20 还是 500【参考方案2】:

更多需要稳定排序的例子。数据库是一个常见的例子。以交易数据库为例,包括姓氏、购买日期、购买时间、商品编号、价格。假设数据库通常按日期|时间排序。然后进行查询以按姓氏|名字制作数据库的排序副本,因为稳定的排序保留了原始顺序,即使查询比较只涉及姓氏,每个姓氏的事务也会|名字按数据|时间顺序。

一个类似的例子是经典的 Excel,它一次将排序限制为 3 列。要对 6 列进行排序,首先对最不重要的 3 列进行排序,然后对最重要的 3 列进行排序。

稳定基数排序的一个经典示例是卡片排序器,用于按以 10 为基数的字段进行排序。卡片从最低有效位到最高有效位排序。每次通过时,都会读取一副纸牌,并根据该列中的数字将其分成 10 个不同的箱子。然后将 10 张纸牌按顺序放回输入槽(“0”牌在前,“9”牌在后)。然后下一列完成另一遍,直到对所有列进行排序。实际卡片分拣机有超过 10 个垃圾箱,因为一张卡片上有 12 个区域,一列可以是空白的,并且有一个误读垃圾箱。要对字母进行排序,每列需要 2 遍,第 1 遍用于数字,第 2 遍用于 12 11 区域。

后来(1937 年)出现了卡片整理(合并)机器,可以通过比较字段来合并两副卡片。输入是两副已经分类的牌,一个主牌和一个更新牌。整理者将两个卡片组合并为一个新的主库和一个存档库,该库可选地用于主副本,以便新主库仅在出现重复时才有更新卡。这可能是原始(自下而上)合并排序背后的想法的基础。

【讨论】:

【参考方案3】:

我知道这有很多答案,但对我来说,this answer,Robert Harvey 总结得更清楚:

稳定排序是一种保留输入集原始顺序的排序,其中 [unstable] 算法不区分两个或多个项目。

Source

【讨论】:

【参考方案4】:

这取决于你做什么。

假设您有一些包含名字和姓氏字段的人员记录。首先,您按名字对列表进行排序。如果您随后使用稳定的算法按姓氏对列表进行排序,您将得到一个按名字和姓氏排序的列表。

【讨论】:

我认为您的意思是“姓和名”。姓氏通常是姓氏。【参考方案5】:

如果两个具有相同键的对象在排序输出中出现的顺序与它们在要排序的输入数组中出现的顺序相同,则称该排序算法是稳定。有些排序算法本质上是稳定的,如插入排序、合并排序、冒泡排序等。有些排序算法则不稳定,如堆排序、快速排序等。

背景:“稳定”的排序算法使具有相同排序键的项目保持有序。假设我们有一个由 5 个字母组成的单词列表:

peach
straw
apple
spork

如果我们只按每个单词的第一个字母对列表进行排序,那么稳定排序将产生:

apple
peach
straw
spork

不稳定排序算法中,strawspork 可以互换,但在稳定的排序算法中,它们保持相同的相对位置(即,由于 straw 出现在前面spork 在输入中,它也出现在输出中 spork 之前)。

我们可以使用这种算法对单词列表进行排序:按第 5 列、第 4 列、第 3 列、第 2 列和第 1 列进行稳定排序。 最后,它将被正确排序。说服自己。 (顺便说一下,那个算法叫做基数排序)

现在回答您的问题,假设我们有一个名字和姓氏的列表。我们被要求“按姓氏排序,然后按名字排序”。我们可以先按名字排序(稳定或不稳定),然后按姓氏稳定排序。在这些排序之后,列表主要按姓氏排序。但是,如果姓氏相同,则对名字进行排序。

你不能以同样的方式堆叠不稳定的排序。

【讨论】:

@user1416486:我们仅按第一个字母排序。有了这个假设,strawspork 比较相等。稳定排序将保留输入的顺序,而不稳定排序则不能保证。 “正确”取决于应用程序。大多数编程语言中的排序功能允许用户提供自定义排序功能。如果用户的函数将不同的项目视为相同的(例如,相同的名字,不同的姓氏),则有助于了解是否会保留原始订单。有关真实示例,请参阅 OCaml's array sorting functions。 我不明白 ..same 排序键这行?这里的钥匙是什么意思?请解释语句..相同的排序键 @saplingPro:通过“排序键”,我的意思是你排序项目的东西。所以当按首字母排序时,那么对于每个项目,其“排序键”就是它的首字母。 @JoeyAdams 您能否将评论中的信息添加到您的答案中。我正要对此投反对票,因为spork 确实在straw 之前,除非您只按第一个字母排序。对我来说,这不是对字符串进行排序的自然方式,应该明确说明。 示例 - 假设您有一个列表,其中每个项目都包含有关航班目的地和出发时间的信息。您首先根据时间对列表进行排序。然后我们根据目的地对其进行排序。如果第二个排序是稳定,我们现在将所有航班一起绑定到同一个目的地,并且按照起飞时间的递增顺序。如果它不稳定,它们就不会按时间递增。【参考方案6】:

如果两个具有相同键的对象在排序输出中出现的顺序与它们在输入未排序数组中出现的顺序相同,则称该排序算法是稳定的。有些排序算法本质上是稳定的,如插入排序、合并排序、冒泡排序等。有些排序算法则不稳定,如堆排序、快速排序等。

但是,任何给定的不稳定排序算法都可以修改为稳定的。可以有特定的排序算法使其稳定,但一般来说,任何基于比较的排序算法本质上不稳定,都可以通过更改键比较操作来修改为稳定,以便两个键的比较将位置视为具有相等键的对象的因子。

参考资料: http://www.math.uic.edu/~leon/cs-mcs401-s08/handouts/stability.pdf http://en.wikipedia.org/wiki/Sorting_algorithm#Stability

【讨论】:

【参考方案7】:

如果您假设您正在排序的只是数字并且只有它们的值可以识别/区分它们(例如具有相同值的元素是相同的),那么排序的稳定性问题就没有意义了。

但是,在排序中具有相同优先级的对象可能是不同的,有时它们的相对顺序是有意义的信息。在这种情况下,不稳定的排序会产生问题。

例如,您有一个数据列表,其中包含所有玩家在游戏中使用等级 [L] 清理迷宫的时间成本 [T]。 假设我们需要根据玩家清理迷宫的速度对他们进行排名。但是,还有一条规则适用:清理迷宫的玩家等级越高,无论时间成本有多长。

当然,您可以尝试使用某种遵循规则的算法将配对值 [T,L] 映射到实数 [R],然后使用 [R] 值对所有玩家进行排名。

但是,如果稳定排序是可行的,那么您可以简单地按 [T](速度更快的玩家优先)然后按 [L] 对整个列表进行排序。在这种情况下,玩家的相对顺序(按时间成本)在您按他们清理的迷宫级别分组后不会改变。

PS:当然,两次排序的方法并不是解决特定问题的最佳解决方案,但要解释海报的问题就足够了。

【讨论】:

【参考方案8】:

稳定的排序总是会在相同的输入上返回相同的解(排列)。

例如 [2,1,2] 将使用稳定排序作为排列 [2,1,3] 进行排序(首先是索引 2,然后是索引 1,然后是排序输出中的索引 3)这意味着输出总是被打乱同样的方法。其他不稳定但仍然正确的排列是[2,3,1]。

快速排序是不稳定的排序,相同元素之间的排列差异取决于选择枢轴的算法。一些实现是随机选取的,可以进行快速排序,使用相同的算法对相同的输入产生不同的排列。

稳定的排序算法是确定性的。

【讨论】:

稳定性不是这个意思。见en.wikipedia.org/wiki/Sorting_algorithm#Stability 我应该更正最后一句,即使在相同的实现中,非稳定排序也可以输出不同的解决方案,任何稳定的排序输出相同的解决方案。 为什么是 -1 ?有人可以指出这里有什么问题吗?这不是稳定排序是什么,而是稳定排序有什么属性。 排序是否确定并不能确定是否稳定。我可以通过定义不同的平局行为(例如,通过对非关键部分进行子排序)来编写不稳定的确定性排序算法。稳定排序具体意味着在对关系进行排序时保留元素的预先排序的相对顺序。稳定排序的输出示例:sort([(5,3),(1,5),(3,3),(1,3)], x) => [(1,5),(1,3),(3,3),(5,3)]。我可以进行确定性排序,始终(确定性地)输出:[(1,3),(1,5),(3,3),(5,3)],但这不是一个稳定的排序。 @cowbert 这是关于每个稳定排序都具有的良好属性的更多声明。也就是说,无论使用稳定排序算法还是实现,每次都会有相同的结果。在不同的非稳定排序实现中很难维护这样的属性。【参考方案9】:

排序稳定性意味着具有相同键的记录在排序前后保持其相对顺序。

因此,当且仅当您要解决的问题需要保留该相对顺序时,稳定性才重要。

如果您不需要稳定性,您可以使用库中的快速、占用内存的算法,例如堆排序或快速排序,而无需考虑它。

如果您需要稳定性,那就更复杂了。与不稳定算法相比,稳定算法具有更高的 big-O CPU 和/或内存使用率。因此,当您拥有大型数据集时,您必须在 CPU 或内存之间做出选择。如果您在 CPU 和内存方面都受到限制,那么您就有问题了。一个好的折衷稳定算法是二叉树排序; Wikipedia article 有一个非常简单的基于 STL 的 C++ 实现。

您可以通过添加原始记录号作为每条记录的最后位置键,将不稳定的算法变成稳定的算法。

【讨论】:

像合并排序这样的稳定​​算法与快速排序具有相同的 O(NlogN) 复杂度;不过,努力的常数乘数更大。 是的,Merge Sort 的内存使用量是 O(N),而 Quicksort 的内存使用量是 O(log N)。我提到 Quicksort 的原因是 qsort() 是一个 C 标准库例程,所以它很容易使用。 最佳整体答案恕我直言。其他人提到的多键技术很有趣,但被高估了;它应用起来很简单,但往往比明显的替代方案慢得多(只需使用一个带有多键比较的排序;或按第一个键排序,然后识别并排序任何具有重复项的子列表)。稳定排序产生可预测结果的事实在某些应用程序中可能很重要。特别是如果您有两个相同的输入列表 A、B,除了列表 B 有一个额外的条目之外,稳定排序的输出将是相同的,除了 B 具有相同的额外条目。最后 pgph +1。 在最后一句中,我不明白您所说的“每条记录的最后一个键”是什么意思-您能解释一下吗?总体上非常好的信息性评论:) @augenss 如果两条记录都有键“foo”,那么在进行排序之前,将它们更改为“foo_00001”和“foo_00002”之类的内容。当您进行排序时,这将保留两个键的原始顺序。然后当你完成排序后,将两个键都改回“foo”。【参考方案10】:

稳定性之所以重要有几个原因。一个是,如果不需要通过交换两条记录来交换它们,则可能会导致内存更新,页面被标记为脏,需要重新写入磁盘(或另一个慢速介质)。

【讨论】:

记录交换与稳定性有什么关系? 如果您保留订单,那么对于某些输入,它可能会有更少的元素“流失”,这会导致额外的内存页面写入...FWIW

以上是关于什么是排序算法的稳定性,为什么它很重要?的主要内容,如果未能解决你的问题,请参考以下文章

第一部分算法基础之插入排序

排序算法总结

常见排序算法总结

常见排序算法总结

算法-稳定性

全面解析十大排序算法之二:选择排序