死磕算法导论(一)插入排序

2016年6月20日

  说真的,作为一个小白,在博客上发表自己的见解有的时候是需要挺大的勇气的,我所写的东西,基本上都是一些写滥的内容,同时我的能力又和大神们之间差了十万八千里,不免有些诚惶诚恐。打算写这篇博客之前,我有些纠结,毕竟《算法导论》的厚度摆在那里,去年年末想自己开一个系列结果刚写一篇就没了。终于,我下定决心决定开始写出自己学习《算法导论》时的想法,希望自己能够坚持写到自己读完这本书。

  在正式踏足插入排序之前,和我一样的小白们一定会想到一个问题:算法,算法,就是解决问题的办法。那么,只要我们随便想到一个办法把问题解决不就好了吗?为什么还会有专门的人来研究这个?首先,世界上的问题不是我们想象中的那么简单,我们的计算方法可以在一定的范围内是有效的,但超出这个范围就无效了,但写代码的我们肯定希望我们的代码是健壮的,是能经得起考验的,因此如何更好地解决问题就值得研究了;另外,计算机的存储空间是一定的,人们也不可能忍受计算机对着一个问题不停地算下去,我们需要更好的计算方法来节省时间和空间。

  斗地主大家都玩过,里面有非常丰富的出牌方式,像顺子、连对、炸弹、飞机等等。为了我们能够更好地看清楚手上的牌以及如何出牌,我们往往会从小到大把牌排个序。面对一手乱牌,我们把6插到K的前面,J插到8的后面。最后,我们得到了一手有序的牌。这就是插入排序。

  我们现在要抽象这个过程。这里,我采用了书上的随机数5、2、4、6、1、3。
初始状态
这是未排序之前的状态

第一步
拿2和5对比,发现2比5小,于是拿出2,将5推到2的位置,再把2推到原来5的位置

第二步
拿4和5对比,发现4比5小,于是重复第一步的动作

第三步
拿6和5对比,发现6比5大,不做其他操作

到这一步其实已经非常明显了,接下来,1和6对比,小于6,插到5、6之间;1和5对比,小于5,插到4、5之间,最终1放到了第一个;3遵循同样的方法也插到其合适的位置。最终
结果

根据上面这段图解,就可以写出C++代码如下:

// 这里省略了相关的变量定义
for (i = 1; i < N; i++)
{
    key = A[i];
    j = i - 1;
    while(j >= 0 && A[j] > key)
    {
        A[j+1] = A[j];
        j = j - 1;
    }
    A[j+1] = key;
}

  代码很短,也不算难,但到底这段代码对不对呢?书上给了一种方法来证明:叫做循环不变式。不论我们的状态如何变化,总有一些状态是保持不变的。如果这种一直保持不变的状态在某一刻发生了变化,那么算法就出错了。这有一点像数学中经常使用的证明方法:数学归纳法。如果不变式运行之前,算法是对的;不变式运行之中,算法没有发生错误;不变式最终结束,算法也能正常的退出,那么整个算法就是对的。对于插入排序来说,它的循环不变式就是,每次一个外层循环开始之前,第0~i-1号位置都是一定是有序的。

  开始阶段,i==1,0~i-1号只有一个元素5,当然是有序的。
  保持阶段,这个我确实才疏学浅,不会用形式化的语言表述,《算法导论》在讲插入排序时也没有采用形式化的描述方法。这里,程序的运行过程就如图解,从图解上看,程序的运行是没有问题的,所以我就认为没问题了。谁有更严谨的判断保持阶段没有问题的方法可以告诉我。
  结束阶段,这时i=N,0~i-1号的元素确实已经排完序,程序也确实在这个时候是终止的。那么,我认为,结束阶段也没有问题。

  综合以上几点,我可以认为,这个程序是无误的。当然,我能够如此理直气壮地说它无误,还是因为我运行过。但循环不变式和运行程序比起来算是一种比较普世的方式,同时,我们程序员们也可以在运行程序的时候运用循环不变式一点一点地验证程序的正确性,尽自己最大的可能确保自己的每一段代码都是无误的。编译器和debug功能的确可以很强大,但纠错功能再强大,也比不上错误本来就很少,不是么?

  最后,我们来分析一下这段代码的时间复杂度问题。
算法分析
影响时间复杂度的最关键的语句在while语句这里,如果一开始我们给定的数组就是有序的,那么就会出现最佳情况,其最佳运行时间和时间复杂度为:
最佳情况
如果一开始我们给定的数组是逆序的,那么就会出现最坏情况,其最坏运行时间和时间复杂度为:
最坏情况

  嗯,这里,插入排序就结束了。我现在的格式还不算好,以后的博客会尽量改善。还有就是我的文章里不可避免地会出现一些错误,各位看官,希望你们能够给我指出。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值