深入浅出数据结构C语言版(16)——插入排序
从这一篇博文开始,我们将开始讨论排序算法。所谓排序算法,就是将给定数据根据关键字进行排序,最终实现数据依照关键字从小到大或从大到小的顺序存储。而这篇博文,就是要介绍一种简单的排序算法——插入排序(Insertion Sort)。
为了使精力专注于排序算法本身,而不是对数据的分析、处理,若无特殊说明,我们每一篇介绍排序算法的博文,均做如下假定:
1.数据存储于一个数组之中,且数据个数N即数组大小
2.数据类型即int
3.排序目标为从小到大
那么,插入排序是怎样的算法呢?其实插入排序的思想来源就是“插队”。
首先我们想象一下这个现实场景:有一个长度为n的队伍,队伍中每个人都比前面的人要高,你是新来的第n+1个人,现在位于队尾,请问你该怎么找到自己应处的位置,以使得队伍保持原有顺序(从矮到高)?
这个问题所有人都会解,那就是:我与前一个人比较,若我更矮,则我站到他前面,持续此比较直至我比我前面的人高或同样高,或者我来到第一名为止。
这个问题的解法,就是插入排序的根本。
现在我们将场景转换到数组中:有一个大小为n的int型数组a,a[0]至a[n-2]已按从小到大排好序,但是第a[n-1]即最后一个元素是“新来的”,现在要将新元素放到正确的位置上,以使得数组保持从小到大的顺序,该怎么做?
同样的,我们令新元素不断地与前一个元素比较,若小于前一个元素则两者交换位置,直至新元素大于等于前一个元素,或新元素到达a[0]时停止。
void unfinishedInsertionSort(int *a, unsigned int n)
{
//“新元素”的当前位置CurrentPosition从N-1开始//只要新元素尚未到达a[0],且前一个元素小于新元素,则不断地令新元素与前一个元素交换位置
for (int CurPos = n - 1;CurPos != && a[CurPos - ] > a[CurPos];--CurPos)
swap(&a[CurPos], &a[CurPos - ]);
}
上述想法就是插入排序的雏形:若元素Xn前面的X0到Xn-1均已排好序,那么Xn只需要不断地向前“插入”,直至前一个元素与Xn的大小关系符合顺序,或Xn到达第一个位置即可完成排序。
不难发现,雏形中尚待解决的问题就是:如何令Xn前面的所有元素排好顺序?
这个问题可以尝试用递归的想法解决:要令Xn排好序,就需要令X0到Xn-1有序,而要令Xn-1排好序,则需要令X0到Xn-2有序……最后会发现,要想令X1排好序,则需要令X0到X0有序,而X0到X0一定是有序的,因为只有一个元素,也就是说此刻递归的基准情形出现了,而根据X0到X0的有序,可以得到X0到X1的有序,X0到X2的有序,直至得到X0到Xn-1的有序,也就有了X0到Xn的有序。因此这个递归的想法可行,而且这个想法就是插入排序从雏形到完整的解决思想。
将上述想法与插入排序的雏形相结合后,我们就得到了插入排序的实现方法:设数组a有n个元素,令下标x从1递增至n-1,对于每个a[x]我们都执行一次“插队”操作(即插入排序的雏形操作)。
下面为插入排序的例程:
void InsertionSort(int *a, unsigned int size)
{
int temp; //temp用于暂存执行插入的元素的值,使用temp可以避免元素间的交换
int CurPos; //CurPos表示执行插入的元素当下所处的下标 //StartPos表示执行插入操作的元素开始插入时的下标
//令StartPos从1递增至size-1,对于每个a[StartPos],我们执行向前插入的操作
for (int StartPos = ;StartPos < size;++StartPos)
{
temp = a[StartPos];
for (CurPos = StartPos;CurPos != && a[CurPos - ] > temp;--CurPos)
a[CurPos] = a[CurPos - ]; //令前一个元素后移,相当于令当前元素前移,但循环结束后记得令temp执行真实的移动 a[CurPos] = temp;
}
}
计算插入排序的时间复杂度并不难,最坏的情况是数组中元素恰好完全反序,此时插入排序的内循环必然执行至CurPos==0为止,而外循环从StartPos=1至StartPos=size-1共size-1次,每一次内循环执行StartPos次,即内循环总共执行1+2+3+……+size-1次,即size(size-1)/2次,即O(n2)
大部分人在学习C语言时就接触过冒泡排序,所以我们将不再对冒泡排序进行介绍。从时间复杂度上看,插入排序和冒泡排序是一样的,都是O(n2),但是在实际执行时,插入排序会比冒泡排序好得多,原因就是在数据“部分有序”时,插入排序可以减少很多比较次数,而冒泡排序的比较则是“固定的”。
举例来说,现有数据1,2,3,4,5,7,6。若插入排序则需比较7次,交换1次(元素2,3,4,5,7均一次比较即结束,元素6与7比较一次,交换,再与5比较一次,结束)。而冒泡排序则需比较6+5+4+3+2+1=21次,交换1次。
造成冒泡排序与插入排序间差异的主要原因就是:插入排序的比较“更充分地利用了已存在的顺序信息”,而冒泡排序无论如何都需要(N-1)+(N-2)+(N-3)+……+1次比较。其实在数据“接近有序”的情况下,插入排序几乎是最快的排序,完全有序的数据其只需要N-1次比较即结束排序。可以说在O(n2)这个级别的排序算法中,插入排序是绝对的首选。
不过我们再次回顾上述例子,会发现两个排序算法虽然需要的比较次数不同,但需要的交换次数却是相同的,即使你换一个数据序列,这两个排序算法需要的交换次数依然是一样多,这是为什么呢?我们下一篇博文将揭开这个秘密。
附:选择排序也是常见的初学排序算法,它需要的比较次数为N+N-1+N-2+……+2,比冒泡排序还要多N-1次,但是它的交换次数有可能比插入和冒泡都要少,比如数据5,4,3,2,1需按从小到大排序,若使用插入或冒泡排序,将需要10次交换,而选择排序的交换操作只需要2次。但是实际使用时插入排序依然比选择排序优先考虑,因为:
选择排序的“实质交换”虽然可以更少,但形式上来说,选择排序是固定执行N次交换的:每一趟我们都会找出当前最小元素然后将其交换至正确位置,所以肯定有N次交换。只不过会出现“当前元素恰好在正确位置上”的情况,从而没有“实质交换”罢了,但代价依然是有的,比如判断当前元素位置与目标位置是否不同,或直接执行自己与自己的交换。此外,在选出当前最小元素时,我们都认为是“比较操作”,然而实际上这里面混杂着很多赋值操作。而这些不起眼的操作都会使得选择排序没有理想的那么快。
深入浅出数据结构C语言版(16)——插入排序的更多相关文章
- 数据结构C语言版 表插入排序 静态表
数据结构C语言版 表插入排序.txt两个人吵架,先说对不起的人,并不是认输了,并不是原谅了.他只是比对方更珍惜这份感情./* 数据结构C语言版 表插入排序 算法10.3 P267-P270 编译 ...
- 深入浅出数据结构C语言版(5)——链表的操作
上一次我们从什么是表一直讲到了链表该怎么实现的想法上:http://www.cnblogs.com/mm93/p/6574912.html 而这一次我们就要实现所说的承诺,即实现链表应有的操作(至于游 ...
- 深入浅出数据结构C语言版(1)——什么是数据结构及算法
在很多数据结构相关的书籍,尤其是中文书籍中,常常把数据结构与算法"混合"起来讲,导致很多人初学时对于"数据结构"这个词的意思把握不准,从而降低了学习兴趣和学习信 ...
- 深入浅出数据结构C语言版(8)——后缀表达式、栈与四则运算计算器
在深入浅出数据结构(7)的末尾,我们提到了栈可以用于实现计算器,并且我们给出了存储表达式的数据结构(结构体及该结构体组成的数组),如下: //SIZE用于多个场合,如栈的大小.表达式数组的大小 #de ...
- 深入浅出数据结构C语言版(17)——希尔排序
在上一篇博文中我们提到:要令排序算法的时间复杂度低于O(n2),必须令算法执行"远距离的元素交换",使得平均每次交换减少不止1逆序数. 而希尔排序就是"简单地" ...
- 深入浅出数据结构C语言版(19)——堆排序
在介绍优先队列的博文中,我们提到了数据结构二叉堆,并且说明了二叉堆的一个特殊用途--排序,同时给出了其时间复杂度O(N*logN).这个时间界是目前我们看到最好的(使用Sedgewick序列的希尔排序 ...
- 深入浅出数据结构C语言版(20)——快速排序
正如上一篇博文所说,今天我们来讨论一下所谓的"高级排序"--快速排序.首先声明,快速排序是一个典型而又"简单"的分治的递归算法. 递归的威力我们在介绍插入排序时 ...
- 深入浅出数据结构C语言版(4)——表与链表
在我们谈论本文具体内容之前,我们首先要说明一些事情.在现实生活中我们所说的"表"往往是二维的,比如课程表,就有行和列,成绩表也是有行和列.但是在数据结构,或者说我们本文讨论的范围内 ...
- 深入浅出数据结构C语言版(3)——递归简论
相信学习过C语言的读者都已经接触过递归(不论是谭浩强的C程序设计还是C Primer Plus都有递归程序),本文就是对递归的基本原则进行简要介绍.首先,我们写一个基本的递归函数作为例子: int ...
随机推荐
- OOP in Javascript
写了几篇Vue入门的内容了,今天写点其它的放松一下,简单讲讲javascript中的面相对象. 在面向对象的语言中,都有类的概念,当然es6中开始javascript中也有类的概念了,这里以es5为基 ...
- Scala 令人着迷的类设计
尽管 Scala 和 Java 有很多相同的地方, 但是在类的声明, 构造, 访问控制上存在很大的差异, 通过本文你也能看到相比较 Java 很多啰嗦的模板代码, Scala 更加的简洁, 使用 Sc ...
- python--DenyHttp项目(1)--调用cmd控制台命令os.system()
os.system() 参数传递cmd命令,命令执行成功返回0,失败返回1 在网上查看使用ping命令,能否Ping通 大神们有 用正则的,有用Popen() os.system()直接用返回值,简单 ...
- [AOP系列]Autofac+Castle实现AOP日志
一.前言 最近公司新项目,需要搭架构进行开发,其中需要对一些日志进行输出,经过一番查找,发现很多博文都是通过Spring.Net.Unity.PostSharp.Castle Windsor这些方式实 ...
- akoj-1280另类阶乘问题
另类阶乘问题 Time Limit:3000MS Memory Limit:65536K Total Submit:22 Accepted:20 Description 大家都知道阶乘这个概念,举个 ...
- Selenium实现的技巧
截图功能: try { File srcFile = ((TakesScreenshot)dr).getScreenshotAs(OutputType.FILE); ...
- Csharp调用基于Opencv编写的类库文件
现在将Csharp调用基于Opencv编写的类库文件(Dll)的方法定下来,我取名叫做GreenOpenCsharpWarper,简称GOCW. 一.CLR编写的DLL部分 1.按照正常方法引入Ope ...
- HTTP 0.9 / 1.0 / 1.1
以下内容是从互联网摘录的,还比较有意思,如果对它有兴趣,欢迎补充在评论里. HTTP是往返“浏览器”与“WEB Server”的协议,即:Hyper Text Transfer Protocol(超文 ...
- zabbix 3.2.7 (源码包)安装部署
Zabbix 3.2.7 + CentOS7 安装 环境准备: 操作系统 CentOS Linux release 7.2.1511 (Core) zabbix server 10.30.94.60 ...
- Oracle安装oraInventory问题
Oracle安装oraInventory问题-----------------------------2013/10/15 在使用安装Oracle软件或者使用dbca创建数据库时,所有的日志都会放在o ...