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

一、什么是算法稳定性

考察排序算法的时候有一个很重要的特性,就是算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,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. RBAC权限管理及使用原生PHP实现

    关于RBAC的原理讲解在网上可以找到很多,推荐:编程浪子的RBAC讲解,本篇博客就不再累述RBAC的原理到底是什么样的. 传统的权限控制有ACL和RBAC方式,ACL的耦合度很高,扩展性不佳,RBAC ...

  2. 文本文件合并(C++实现)

    直接附上代码吧 #include<iostream> #include<fstream> #include<cstdlib> using namespace std ...

  3. What is the best Java email address validation method?

    https://stackoverflow.com/questions/624581/what-is-the-best-java-email-address-validation-method htt ...

  4. MyBatis 集合操作语法范例:配合SQL的in关键字

    Java语法: private String[] tagIds; MyBatis语法 <delete id="deleteByIds" parameterType=" ...

  5. Windows平台下面Oracle11.2.0.1 升级Oracle11.2.0.4 的简单步骤

    1. 首先查看数据库的版本: 2. ESXi 上面的虚拟机挂在 oracle11.2.0.4的 iso磁盘 3. 执行set 进行升级 4. 安装选项进行选择 升级现有的数据库 5. 注意安装位置必须 ...

  6. [转帖] IIS经典模式和集成模式的区别

    在 IIS 7.0 中,应用程序池有两种运行模式:集成模式和经典模式. https://blog.csdn.net/hongwei_23/article/details/44300923 这里面添加一 ...

  7. VSCODE安装以及使用Python运行调试代码的简单记录

    1. VScode安装 官网下载VSCODE https://code.visualstudio.com/ 下载呢windows的x64安装包,安装stable的版本 当前日期 2018.01.15 ...

  8. CMake--Set用法

    CMake中的set用于给一般变量,缓存变量,环境变量赋值. cmake官方文档set set(<variable> <value> [[CACHE <type> ...

  9. Vue 组件化

    根实例└─ TodoList ├─ TodoItem │ ├─ DeleteTodoButton │ └─ EditTodoButton └─ TodoListFooter ├─ ClearTodos ...

  10. PHP5.5特性

    1.PHP生成器(使用yield关键字) <?php //使用yield关键字实现平方的生成器,在循环结构中则生成的是数组 function do2pos($n){ for($i=1; $i&l ...