排序算法一:插入排序

2016-04-13  学习总结记录:插入排序

#数据结构与算法
##排序算法一
###插入排序


0. 描述
1. 插入排序算法原理简单
2. 实现逻辑简单
3. 时间复杂度为 O(n^2)
4. 实现
5. 测试
6. 验证
7. 参考: 算法导论、 linux c编程

#0x00 描述

##插入排序(直接)
自己的描述:把需要插入的数值遍历一遍数组,将其插入在合适的位置,输出:从小到大按顺序排列的数组;
参考[算法导论-2.1]: 类似于玩扑克时抓牌的过程 ;

#0x01原理

类似于玩扑克时抓牌的过程,玩家每拿到一张牌都要插入到手中已有的牌里,使之从小到大排好序。

也许你没有意识到,但其实你的思考过程是这样的:现在抓到一张7,把它和手里的牌从右到左依次比较,

7比10小,应该再往左插,7比5大,好,就插这里。为什么比较了10和5就可以确定7的位置?

为什么不用再比较左边的4和2呢?因为这里有一个重要的前提:手里的牌已经是排好序的。

现在我插了7之后,手里的牌仍然是排好序的,下次再抓到的牌还可以用这个方法插入。

#0x02实现逻辑 :伪代码

#0x03时间复杂度: O(n^2)

#0x04实现
###// 实现

 1 // 直接插入排序算法
 2 // @param data: input data[]
 3 // @param nlen: input length of data[]
 4 void insert_sort(int *data, int nlen);
 5 
 6 void insert_sort(int *data, int nlen)
 7 {
 8     // 1. param verify
 9     if (data == NULL || nlen <= 0)
10     {
11         return;
12     }
13 
14     // 2. insert_sort
15     int i, j; 
16     int key;    // 设置哨兵
17     for (i = 1; i < nlen; i++)
18     {
19         key = data[i];  
20         j = i - 1;
21         while(j>=0 && data[j] > key)// 如果比哨兵大,且在数组范围内,移动数组;
22         {
23             data[j+1] = data[j];
24             j--;
25         }
26         data[j+1] = key; // 数组值比哨兵小的后一位值 赋值 key
27     }
28 }
View Code

 

#0x05测试

###测试

 1 #include <sys/time.h>
 2 #define NLEN 10
 3 int test()
 4 {
 5 
 6     int data[NLEN] = {123, 23, 11, 54, 66, 44, 654, 99, 89, 77};
 7     //test 运行时间
 8     struct  timeval  end;
 9     unsigned long timer;
10     gettimeofday(&end,NULL);
11     timer = 1000000 * (end.tv_sec)+ end.tv_usec;
12     printf("begin timer = %ld us\n",timer);
13 
14     // test insert_sort
15     insert_sort(data, NLEN);
16 
17     //test 运行时间
18     struct  timeval  end;
19     unsigned long timer;
20     gettimeofday(&end,NULL);
21     timer = 1000000 * (end.tv_sec)+ end.tv_usec;
22     printf("end timer = %ld us\n",timer);
23 
24     return 0;
25 }
View Code

 

#0x06验证

###验证

1. 输入正常不等的10个数字;
2. 输入有相同数字的数组;
3. 输入带0的和数量不全的数组;
4. 输入的空数组;
5. 输入的包含字符(不是整型)的数组;

 

###验证证明:[转自书中的验证,适用于算法正确性的验证]
如何严格证明这个算法是正确的?换句话说,只要反复执行该算法的for循环体,执行LEN-1次,就一定能把数组a排好序,而不管数组a的原始数据是什么,如何证明这一点呢?我们可以借助Loop Invariant的概念和数学归纳法来理解循环结构的算法,假如某个判断条件满足以下三条准则,它就称为Loop Invariant:

1. 第一次执行循环体之前该判断条件为真。
2. 如果“第N-1次循环之后(或者说第N次循环之前)该判断条件为真”这个前提可以成立,那么就有办法证明第N次循环之后该判断条件仍为真。
3. 如果在所有循环结束后该判断条件为真,那么就有办法证明该算法正确地解决了问题.

只要我们找到这个Loop Invariant,就可以证明一个循环结构的算法是正确的。上述插入排序算法的Loop Invariant是这样的判断条件:第j次循环之前,子序列a[0..j-1]是排好序的。在上面的打印结果中,我把子序列a[0..j-1]加粗表示。下面我们验证一下Loop Invariant的三条准则:

1. 第一次执行循环之前,j=1,子序列a[0..j-1]只有一个元素a[0],只有一个元素的序列显然是排好序的。
2. 第j次循环之前,如果“子序列a[0..j-1]是排好序的”这个前提成立,现在要把key=a[j]插进去,按照该算法的步骤,把a[j-1]、a[j-2]、a[j-3]等等比key大的元素都依次往后移一个,直到找到合适的位置给key插入,就能证明循环结束时子序列a[0..j]是排好序的。就像插扑克牌一样,“手中已有的牌是排好序的”这个前提很重要,如果没有这个前提,就不能证明再插一张牌之后也是排好序的。
3. 当循环结束时,j=LEN,如果“子序列a[0..j-1]是排好序的”这个前提成立,那就是说a[0..LEN-1]是排好序的,也就是说整个数组a的LEN个元素都排好序了。

可见,有了这三条,就可以用数学归纳法证明这个循环是正确的。

 

#0x07 参考

1.  linux c 编程

2.  算法导论

posted @ 2016-04-13 11:05  lei-lei  阅读(231)  评论(0编辑  收藏  举报