2.1 插入排序(Insertion sort)
提示:这一部分由问题、如何理解插入排序、插入排序中的伪代码及原理、循环不变式与插入排序的正确性组成,其中循环不变式与插入排序的正确性是个人认为比较晦涩的部分,但是我认为它里面蕴含的思想是非常值得我们去参考的。
1、问题:
输入:一个数的序列<a1,a2,a3,a4>
输出:<a1',a2',a3',a4'>,其中满足递增顺序
2、如何理解插入排序:
以扑克牌为例,我们每从桌上抽取一张扑克牌时,都会将扑克牌插入到左手正确的位置(让其从左到右递增排列)。为了找这个正确的位置,我们会从右到左将它和已经在左手中的每张牌进行比较,如果刚好小于某一张牌,那么将我们抽取的牌放置在这张牌的左边,如图,左手中的牌就处于正确的顺序中。
如果没打过牌,那么我们也可以用试卷排序来理解,我们老师在排试卷的时候是从高到低排,假设为左高右低,当我们抽出一张未排序的试卷,将其放入已经排序好的那些试卷中,我们往往需要和右边的依次卷子比较,找到一个正确的位置,使这张试卷的分数正好在左右两边试卷分数的区间内。
3、插入排序的伪代码及原理:
for j = 2 to A.length //从第二个元素开始循环
key = A[j]
//Insert A[j] into sorted sequece A[1...j-1](将A[j]插入到之前已经排序好的A[1...j-1]序列中)
i = j - 1 //i就相当于已经排序好的最后一张牌,就是待排序牌的前一张牌
while i > 0 and A[i] > key //依次比较,当前面的牌大于待排序牌的时候
A[i+1] = A[i] //我们将前面的牌向后移动一位
i = i - 1 //然后角标-1,即将比较对象往前再推一位
A[i+1] = key //while循环结束后,待排序牌已经找到了正确的位置,进行赋值操作即可
以排序<5,2,4,6,1,3>为例:
【1】此图标记对应伪代码:
(1)黑色长方形:A[j]
(2)灰色长方形:A[1...j-1]
(3)灰色箭头:A[i+1] = A[i] 和 i = i - 1 ,即将符合while条件的数字均向后移动一位
(4)黑色箭头:A[i+1] = key ,待排序元素被移动到的地方
【2】具体解释:如图所示,我只解释d->e的变化过程,其他过程也和这个原理相同
(1)可以发现1作为A[j]比前面任何一个数字都要小。
(2)此时将前面的比1大的数字依次往后挪一位。
(3)最后将1赋值到第一位上。
4、循环不变式与插入排序的正确性
【1】循环不变式(loop invariant)是在循环体的每次执行前后均为真的谓词。
【2】循环不变式拥有以下性质,证明算法的正确性时需要用到这些性质:
(1)初始化(Initialization):循环第一次迭代之前,它为真。(相当于数学归纳法中k=1)
(2)保持 (Maintenance):如果循环的某次迭代之前它为真,那下次迭代之前它也为真。(相当于数学归纳法中假设k=n时成立,证明k=n+1时也成立)
(3)终止(Termination):循环终止时,不变式为我们提供一个有助于证明算法是正确的性质。(在数学归纳法中,k是可以无穷大的,但是终止说明了这里和数学归纳法的不同之处在于这里的k是有限度的,即到不符合循环的条件为止)
【3】在插入排序中,证明这些性质的正确性:
(1)初始化:当j = 2时,在子数组A[1...j-1]只有一个元素A[1]时,循环不变式显然成立(子数组按序排列)。
(2)保持:先将A[1..j-1]中大于A[j]的元素均向右移动一位,找到A[j]的正确位置并将其插入,此时产生的子数组A[1...j]由原来数组中第1到第j个元素产生,且符合递增条件,因此其迭代保持循环不变式。
(3)终止:在循环终止时,j > A.length即 j > n,因此最后必有 j = n + 1,此时子数组A[1...j-1]
(即A[1...n])由未排序前的原数组A[1...n]组成,且这一子数组已经经过排序,我们知道A[1...n]就是整个数组,因此可以知道整个数组已经排序,因此算法是正确的。
5、Exercise
2。1-1:按照上面的图片就可以做出
2.1-2:将A[i] > key中的>改为<即可
for j = 2 to A.length //从第二个元素开始循环
key = A[j]
//Insert A[j] into sorted sequece A[1...j-1](将A[j]插入到之前已经排序好的A[1...j-1]序列中)
i = j - 1 //i就相当于已经排序好的最后一张牌,就是待排序牌的前一张牌
while i > 0 and A[i] < key //依次比较,当前面的牌小于待排序牌的时候
A[i+1] = A[i] //我们将前面的牌向后移动一位
i = i - 1 //然后角标-1,即将比较对象往前再推一位
A[i+1] = key //while循环结束后,待排序牌已经找到了正确的位置,进行赋值操作即可
2.1-3:
for i = 1 to A.length
if A[i] == v
return i //查找到返回i
return NIL
证明算法正确性:
(1)初始化:只有一个元素的时候,如果A[i] = v,那么会return i,如果不等,那么return NIL,符合条件
(2)保持:同理,如果检索到A[i] = v,那么return i,否则一直检索下去,直到符合条件或者没有任何数可以与v相等,因此其迭代保持循环不变式
(3)终止:假如中途A[i]=v,程序终止,说明找到了v,假如未能找到,那么最后当i=A.leng+1时,返回NIL,因此符合循环不变式
2.1-4
思路:从右到左逐个相加,同时如果是两个1相加,左边数字加1
for i = n downto 2
C[i] = A[i] + b[i]
if(c[i] > 1)
c[i] = 0
c[i-1] += 1