【DS】排序算法的稳定性
主要的排序算法有八种:直接插入排序,希尔排序(这两种统称为插入排序),冒泡排序,快速排序(这两种统称为交换排序),直接选择排序,堆排序(这两种统称为选择排序),归并排序,基数排序。今天我们就讨论一下它们各自的稳定性。如果对算法不熟悉,可以查看我的另外几篇博客,然后再来阅读。
一、什么是算法稳定性
考察排序算法的时候有一个很重要的特性,就是算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,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】排序算法的稳定性的更多相关文章
- JS实现常用排序算法—经典的轮子值得再造
关于排序算法的博客何止千千万了,也不多一个轮子,那我就斗胆粗制滥造个轮子吧!下面的排序算法未作说明默认是从小到大排序. 1.快速排序2.归并排序3.冒泡排序4.选择排序(简单选择排序)5.插入排序(直 ...
- 八大排序算法Java
目录(?)[-] 概述 插入排序直接插入排序Straight Insertion Sort 插入排序希尔排序Shells Sort 选择排序简单选择排序Simple Selection Sort 选择 ...
- [Data Structure & Algorithm] 八大排序算法
排序有内部排序和外部排序之分,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存.我们这里说的八大排序算法均为内部排序. 下图为排序 ...
- 排序算法 ----(转载::http://blog.csdn.net/hguisu/article/details/7776068)
1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表.即:先将序列的第1个记录看成是一个有序 ...
- Java常用排序算法+程序员必须掌握的8大排序算法
概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大, ...
- C++:主要几种排序算法及其复杂度
常见排序算法稳定性和复杂度分析快速简记以及转载 分类: 算法 2012-02-07 22:18 399人阅读 评论(1) 收藏 举报 算法mergeshell http://blogold.chin ...
- C#排序算法的比较
首先通过图表比较不同排序算法的时间复杂度和稳定性. 排序方法 平均时间 最坏情况 最好情况 辅助空间 稳定性 直接插入排序 O(n2) O(n2) O(n) O(1) 是 冒泡排序 O(n2) O(n ...
- 8种排序算法的C#实现
排序是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列.排序根据涉及的存储器的不同分为内部排序和外部排序:内部排序是指待排序记录存放在内存进行的排序过程:外部排序是指待排序记录的数 ...
- 排序算法总结及Java实现
1. 整体介绍 分类 排序大的分类可以分为两种,内排序和外排序.在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序.主要需要理解的都是内排序算法: 内排序可以分为 ...
随机推荐
- 转:为Docker容器设置固定IP实现网络联通(1)——通过Pipework为Docker容器设置
https://blog.csdn.net/chinagissoft/article/details/51250839 1. 创建并启动一个容器: docker run --cap-add=NET_A ...
- Jmeter常见问题及场景应用
Jmeter作为工具来讲,已经是一个相对比较牛掰的工具,除了它能够支持那么多协议以及方法之外,更在与它的前置处理以及后置处理.同步监控的人性化.当然,所有的工具.框架都是作为业务的支撑,如果不能满足我 ...
- Windows 版本下 Oracle12.1.0.2 升级Oracle12.2.0.1的步骤
oracle12.1.0.1 2013年发布的产品 2014年左右发布12.1.0.2 2016年底发布了 oracle12.2.0.1 经常有人会安装了最早的oracle版本,然后需要升级到最新的o ...
- 普通PC安装ESXi5.5以及以上的方法
原贴内容 With ESXi 5, ESX no longer uses MBR for boot, it has gone to GPT-based partitions instead. W ...
- /include/caffe/common.cuh(9): error: function "atomicAdd(double *, double)" has already been defined
https://stackoverflow.com/questions/39274472/error-function-atomicadddouble-double-has-already-been- ...
- Debug时含有的子元素,在代码里获取不到的问题
比如,Debug时如下图展示: 我想要获取的是:ansList.get(i).getComponent().getConnectorId() debug时明明有这个元素,但是当我写出来的时候却发现:a ...
- 学习Spring Boot:(二十五)使用 Redis 实现数据缓存
前言 由于 Ehcache 存在于单个 java 程序的进程中,无法满足多个程序分布式的情况,需要将多个服务器的缓存集中起来进行管理,需要一个缓存的寄存器,这里使用的是 Redis. 正文 当应用程序 ...
- 一篇写得很好的关于lct的博客
连接 orz orz
- VirtualBox中slitaz系统不能联网
首先,关于VirtualBox虚拟机中安装slitaz操作系统中,先不讲,现在假设电脑中已经装好了VirtualBox,并且已经装好了slitaz操作系统,一个轻量版的linux发行版本. 右上角我画 ...
- activity之间的数据传递方法
1 基于消息的通信机制 Intent--------boudle,extra 用这种简单的形式,一般而言传递一些简单的类型是比较容易的,如int.string等 详细介绍下Intent机制 Inte ...