主要的排序算法有八种:直接插入排序,希尔排序(这两种统称为插入排序),冒泡排序,快速排序(这两种统称为交换排序),直接选择排序,堆排序(这两种统称为选择排序),归并排序,基数排序。今天我们就讨论一下它们各自的稳定性。如果对算法不熟悉,可以查看我的另外几篇博客,然后再来阅读。

一、什么是算法稳定性

考察排序算法的时候有一个很重要的特性,就是算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。

二、算法稳定性的重要性

算法稳定性为什么这么重要呢?

1)在实际的应用中,我们交换的不一定只是一个整数,而可能是一个很大的对象,交换元素存在一定的开销;

2)参照基数排序(后面会讲),不稳定排序是无法完成基数排序的,讲述完基数排序后,还会补充这里的原因。

三、八大算法的稳定性

1)直接插入排序@排序算法之插入排序(Insertion Sort)

其大致原理是:将数组分为无序区和有序区两个区,然后不断将无序区的第一个元素按大小顺序插入到有序区中去,最终将所有无序区元素都移动到有序区完成排序。

我们假设一个数组,元素已经排序为{1,5A,7,5B,9},其中前面三个已经排序完成,后面没有排序,即前面三个是有序区,后面两个是无序区,现在要将无序区的5B插入到有序区,则如果我们将元素插入到5A之前,我们需要往后移动两个元素,如果插入到5A之后,则需要移动一个元素,因此我们选择移动一个元素,而5A和5B也保持原来的顺序,因而直接插入排序是稳定的。

2)希尔排序@排序算法之希尔排序(Shell Sort)

其大致原理是:又称Gap缩小排序。先将序列按Gap划分为元素个数相同的若干组,使用直接插入排序法进行排序,然后不断缩小Gap直至为1,最后使用直接插入排序完成排序。希尔排序其实是直接插入排序的增强版。

我们来证明它是不稳定的,假设有一个数组{3,2A,2B,4},我们要升序排列,按照算法,第一次Gap=2,即可以分为{3,2B}和{2A,4}两组,然后对每一组进行插入排序,可以排序成{2B,2A,3,4},第二次Gap=1,由于插入排序是稳定的,所以2A和2B不会交换顺序了。由此可以看到,希尔排序是不稳定的。

3)冒泡排序@排序算法之冒泡排序(Bubble Sort)

其大致原理是:将序列划分为无序和有序区,不断通过交换较大元素至无序区尾完成排序。

熟悉冒泡排序的人一定知道,冒泡排序通过不断的交换元素,将无序区的最大(最小)元素往无序区搬运,因而和插入排序一样,为了减少其交换次数,冒泡排序是稳定的。

4)快速排序@排序算法之快速排序(Quick Sort)

其大致原理是:不断寻找一个序列的中点,将小于该中点的元素搬移到中点左边,大于该中点的元素搬移到中点右边,或者反过来。然后对中点左右的序列递归的进行排序,直至全部序列排序完成,使用了分治的思想。

关于算法的稳定性有一点本来是打算后面再讲的,但是讲到快速排序就一定要说了。读者肯定注意到了,前面的插入排序和冒泡排序完全可以实现为不稳定算法,只是在比较元素决定是否交换的时候,是否加上等于号而已。快速排序更加显示了这一点,解释如下:

在算法导论里面,快速排序选择都是元素序列的最后一个元素,假设元素序列如下{3,9,5A,6,8,5B},这种情况下,和上面的情况一下,稳不稳定还是看判断的时候是否出现等号,但是如果选择不是这样的,我们假设一种特殊状况:{3,9,5A,5B,6,8,5C},算法的实现是选择中间的5B作为中点,则不论等号与否,都是不稳定的。实际上,算法导论的选择是非常有意义的,了解其算法过程的人可以看到,这样的选择极大的降低了交换元素的复杂度和移动元素的次数。算法导论中是加了等号的,即≤最后一个元素的值被移到了左边,因而快速排序是稳定的。

5)直接选择排序@排序算法之选择排序(Selection Sort)

其大致原理是:将序列划分为无序和有序区,寻找无序区中的最小值和无序区的首元素交换,有序区扩大一个,循环最终完成全部排序。

我们还是假设一个序列{1,3,5,10A,10B,7},看这个数列,假设前面三个是有序区,后面三个是无序区,则无序区中最小的元素是7,和无序区的首元素交换10A交换,则可以看到序列变成了{1,3,5,7,10B,10A},然后继续,无序区就剩下{10B,10A},我们又可以看到,这里又是一个等号问题,同样,前面的交换是必然的,而后面的交换(如果等于也要交换)则不是必然的,为了减少元素交换,直接选择排序是不稳定的。

6)堆排序

其大致原理是:利用大根堆或小根堆思想,首先建立堆,然后将堆首与堆尾交换,堆尾之后为有序区。

考虑序列{9,5A,7,5B},按照堆排序的算法走一遍(算法导论中用的是最大堆,这个序列也是用最大堆来设计的),很快就可以发现,输出序列为{5B,5A,7,9},而且与等号无关,因此堆排序是不稳定的。

7)归并排序@排序算法之归并排序(Merge Sort)

其大致原理是:将原序列划分为有序的两个序列,然后利用归并算法进行合并,合并之后即为有序序列。

归并排序一样是稳定的,但是归并排序的稳定性并不是为了减少元素交换次数,因为它的算法实现中没有元素交换这一概念。

8)基数排序

其大致原理是:将数字按位数划分出n个关键字,每次针对一个关键字进行排序,然后针对排序后的序列进行下一个关键字的排序,循环至所有关键字都使用过则排序完成。具体请参见:算法总结系列之五: 基数排序(Radix Sort)

基数排序对多个关键字进行排序,并且这些关键字还是有优先级别的,对于整数来说,位数越高的数字优先级越高,而基数排序则是对优先级低的先排序,因此,基数排序对于整数是从个十百千万一个个去排序的。注意,这里必须使用稳定排序,否则,就会让原先的地位排序成果毁于一旦,最终的不到正确的排序结果。

基数排序不过是一种思想,其每一位的排序都需要稳定算法,否则无法得到正确的结果。

三、总结

算法稳定性到底为什么如此重要?上面提到的八种算法可以看到,其实很多算法都是可以实现稳定和不稳定两种情形的,那为什么选择稳定?一个基本原因就是减少元素交换次数,但是也有像归并排序这样的算法,与交换无关,那么稳定算法的意义在哪里呢?

稳定算法在单次排序的时候,意义并不显著,虽然上面提到减少元素交换,其实链表是可以避免这个消耗的,只不过操作比较复杂,其意义显示在基数排序中,即,我们要对多个关键词多次排序,这个时候,就一定要使用稳定算法。举一个现实的例子,比如排序的对象是人名,假设有以下两个人名:

Smith, Alfred
Smith, Zed

我们先按first name排序,再按照last name排序,按照first name排序完成以后,就是上面的样子,再去按照last name排序,如果算法不稳定,则顺序极就会颠倒,是不是?这里的last name和first name完全可以抽象成基数排序的不同位,不是稳定算法,就不能得到正确结果。

【DS】排序算法的稳定性的更多相关文章

  1. JS实现常用排序算法—经典的轮子值得再造

    关于排序算法的博客何止千千万了,也不多一个轮子,那我就斗胆粗制滥造个轮子吧!下面的排序算法未作说明默认是从小到大排序. 1.快速排序2.归并排序3.冒泡排序4.选择排序(简单选择排序)5.插入排序(直 ...

  2. 八大排序算法Java

    目录(?)[-] 概述 插入排序直接插入排序Straight Insertion Sort 插入排序希尔排序Shells Sort 选择排序简单选择排序Simple Selection Sort 选择 ...

  3. [Data Structure & Algorithm] 八大排序算法

    排序有内部排序和外部排序之分,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存.我们这里说的八大排序算法均为内部排序. 下图为排序 ...

  4. 排序算法 ----(转载::http://blog.csdn.net/hguisu/article/details/7776068)

    1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表.即:先将序列的第1个记录看成是一个有序 ...

  5. Java常用排序算法+程序员必须掌握的8大排序算法

    概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大, ...

  6. C++:主要几种排序算法及其复杂度

     常见排序算法稳定性和复杂度分析快速简记以及转载 分类: 算法 2012-02-07 22:18 399人阅读 评论(1) 收藏 举报 算法mergeshell http://blogold.chin ...

  7. C#排序算法的比较

    首先通过图表比较不同排序算法的时间复杂度和稳定性. 排序方法 平均时间 最坏情况 最好情况 辅助空间 稳定性 直接插入排序 O(n2) O(n2) O(n) O(1) 是 冒泡排序 O(n2) O(n ...

  8. 8种排序算法的C#实现

    排序是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列.排序根据涉及的存储器的不同分为内部排序和外部排序:内部排序是指待排序记录存放在内存进行的排序过程:外部排序是指待排序记录的数 ...

  9. 排序算法总结及Java实现

    1. 整体介绍 分类 排序大的分类可以分为两种,内排序和外排序.在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序.主要需要理解的都是内排序算法: 内排序可以分为 ...

随机推荐

  1. shell脚本--变量与数组

    Linux中的变量有环境变量和用户自定义变量,关于环境变量,可以查看这篇博客:linux环境变量 本文主要针对的是用户在shell脚本中定义的变量,但是环境变量也可以在shell脚本中使用. 普通变量 ...

  2. 安裝CentOS7后修復win7引导

    想尝试双系统的心情想必大家都能理解,但是安装了双系统之后的收尾工作也是必不可少的,由于对Linux并不算很熟悉,所以在这方面花了不少时间,这里将CentOS7下修復windows7引导的解决方案记录下 ...

  3. Ping命令的另一种使用方法

    今天实习结束休息的时候无聊,于是便想看看机房有多少机器,IP是什么,有没有什么小漏洞. 依次使用了netstat.ping.Telnet以后,不小心输入了这样一个东西 当时按下回车以后,心里想的是这样 ...

  4. BZOJ1901Zju2112 Dynamic Rankings——树状数组套主席树

    题目描述 给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1 ],a[i+2]……a[j]中第k小的数是多少(1≤k ...

  5. BZOJ4010[HNOI2015]菜肴制作——拓扑排序+堆

    题目描述 知名美食家小 A被邀请至ATM 大酒店,为其品评菜肴. ATM 酒店为小 A 准备了 N 道菜肴,酒店按照为菜肴预估的质量从高到低给予 1到N的顺序编号,预估质量最高的菜肴编号为1.由于菜肴 ...

  6. BZOJ5251 八省联考2018劈配(网络流)

    劈配,匹配,网络流.那么考虑怎么跑网络流. 先看第一问.首先套路的建出超源超汇.不用想也知道导师向汇连容量为战队人数上限的边.特别地,给出局也建一个点,向汇连容量inf的边(似乎没有必要).对于一个新 ...

  7. Android Service服务的生命周期

    与activity类似,服务也存在生命周期回调方法,你可以实现这些方法来监控服务的状态变化,并在适当的时机执行一些操作. 以下代码提纲展示了服务的每个生命周期回调方法: public class Ex ...

  8. NOI 笔试题库(我背不住的部分)

    吐槽 为什么C++选手要会编译Pascall啊!为什么Emacs选手要会使用Vim啊! Linux 中为文件改名使用的命令是:mv 在Linux 中删除当前目录下的test 目录的命令是:rm -r ...

  9. BZOJ 4004 [JLOI2015]装备购买 | 线性基

    题目链接 Luogu P3265 题解 非常正常的线性基! 但是我不会线性基-- (吐槽:#define double long double 才过--) #include <cstdio> ...

  10. get skill

    Get Skill 2018-01-16 > 001 防止数组越界的一种方法 ]; array[n%] = value; > 002 超时机制 在等待某个事件或标志时,设定一定时限,时限到 ...