对于排序算法,相信你一定不陌生吧!如我们常见的几大经典排序:
「冒泡排序」、「插入排序」、「选择排序」、「快速排序」、「归并排序」、「堆排序」等等。
这些排序算法中,性能、内存消耗各有优劣,同时也各有各的适用场景,而我们也不能仅仅从时间复杂度和空间复杂度的优劣去评断一个排序算法的好与坏,比如「冒泡排序」和「插入排序」,两者的时间复杂度均是O(n²),空间复杂度也都是O(1),但是插入排序却更常用,而且在很多实际场景中,执行效率更高,至于原因,你可以思考一下,此文暂不做讨论。
而排序算法中,除了时间复杂度和空间复杂度,还有着一个我们比较常关注的性质:「稳定性」。
我们这里简单描述一下什么是排序算法的稳定性:
❝
对于某一种排序算法,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序保持不变,那么就说这个排序算法是稳定的。
❞
而上面所提到的几种排序算法中,冒泡排序、插入排序、归并排序属于稳定排序,选择排序、快速排序、堆排序属于不稳定排序。
简单举个例子:
假设对数组[
3, 2, 2, 5, 1, 4
]进行递增排序,数组中有两个元素是相等的即
2
,为方便标识,我们先用
2'
表示第二个
2
,即原数组为:[
3, 2, 2', 5, 1, 4
]。
- 如果使用稳定排序,那么排序后有且仅有一种结果:[
]。1, 2, 2', 3, 4, 5
- 如果使用不稳定排序,那么排序后,可能会出现这种结果:[
]1, 2', 2, 3, 4, 5
排序的稳定性很好去理解,也很容易去分析。但是你有没有想过:
❝
「既然所有的排序算法排出来的结果都是有序的,为什么还要区分稳定性和不稳定性的算法呢?」
❞
我们用下面一个场景来说明。
假设有一万条订单记录,要对这些订单进行一个排序,要求按照金额由小到大的顺序排列,如果订单金额相同,则按照下单时间的先后进行排列。
假设订单数据为:
在开始进行排序时,有一点先要弄明白:像这种存在需要对多个关键字进行排序的,每一次排序只能基于其中某一个且仅一个关键字进行排序,无法对多个关键字同时排序。之所以提这一点,是为了避免有人拿Java的比较器Comparator做对比,那根本是两码事。
而对于需要针对多个关键字排序的情况,优先级最高的要放在最后排才行,比如这里先按照金额大小排序,在金额大小相同的情况下,再按照下单时间排序,优先级最高的就是金额,那么就应该最后再排金额。如果先排金额,在排序后,如果有金额相同的,再排下单时间,那么最后的结果就是以下单时间的先后为准了,你好好想一想,是不是这么回事?
好,那我们进行排序,先对下单时间进行排序,得到的结果为:
得到这个结果后,我们再对金额进行一次排序就OK了,但是这里为了达到需求要求的效果,「即金额相同的,下单时间早的要排在前面」,则第二次排序只能用稳定排序。如果使用不稳定排序,第二次按照金额排序后,得到的结果虽然在金额上是有序的,但下单时间上就不一定有序了,换一种说法就是:第一次按照订单时间排序的结果,没有被第二次按照金额排序利用上,先看图:
使用稳定排序后的结果:
这样子你应该明白了吧,如果使用不稳定排序,那么第二次排序后的结果,就可能不符合要求,「不稳定排序会改变相同元素的原有顺序」。在这个场景中,不稳定排序就可能会把金额均是30的原有顺序改变了(注意哦,这个顺序是已经按照下单时间排好序的结果),从而导致最终的排序结果对于金额相同的,没有按照时间先后来排序。但是这种问题,使用稳定排序就不会出现。
所以,现在你明白了吧,之所以区分排序的稳定性和不稳定性,是有它的实际应用场景的,它的意义在于:
❝
在需要对多个(也意味着多次)具有优先级的关键字进行排序的场景下,稳定排序能利用上一次排序的结果服务于本次排序,从而保证对于值相同的元素的两次排序结果相同。
❞
用这个场景来解释就是:
第一次排序先对下单时间进行排序,得到的排序结果中,两个金额为30的记录在下单时间上已经是有序的了;然后第二次排序对金额进行排序,由于使用了稳定排序,两个金额为30的记录的顺序就不会被改变了,这就相当于第一次排序的结果被第二次排序利用上了。
如果你还是不能够完全理解,再举一个实际场景:
对考生的成绩进行排名,如果总分相同的,则按照语文分数高低排,如果语文分数还相同的,继续按照数学分数高低排。
看到这个例子,你是否恍然大悟了呢?
在这个例子中,排序的优先级是:总分 > 语文 > 数学,如果使用稳定排序,我们就能得到预期且是正确的结果;如果使用不稳定排序,就有可能会出现,对于总分相同的,语文分数较低的就排在了更前面,这个结果对于成绩排名,就不是正确的。
这就是稳定排序的实际应用场景,对于某些场景,不稳定排序得到结果可能是不正确的。
「THE END」
【你的关注和支持,是我创作的最大动力】