常见排序算法导读(4)[直接插入排序]
上一节介绍了简单选择排序,这一节将介绍直接插入排序(Straight Insertion Sort)。 会玩扑克牌的同志应该很容易理解插入排序,通常整理扑克牌的方法就是一张一张的来,将每一张扑克牌插入到其他已经有序的扑克牌中的适当位置。在计算机实现中,为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移一个位置。这种方法就叫做插入排序(Insertion Sort)。 与选择排序一样,当前索引左边的所有元素都是有序的,但它们的最终位置还不确定,为了给更小的元素腾出空间,它们可能会被移动。但是当索引到达数组的右端时,排序就完成了。
直接插入排序的思想
当插入第i(>=1)个对象时,前面的V[0], V[1], ..., V[i-1]已经排好序。这时候, 用V[i]的关键字与V[0], V[1], ..., V[i-1]的关键码顺序进行比较,找到插入位置后将V[i]插入,原来位置上的对象向后顺移。
典型的直接插入排序的过程看起来是这样子滴,【图片来源: https://en.wikipedia.org/wiki/Insertion_sort】
o 直接插入排序(Straight Insertion Sort)的C代码实现 : 函数sisort()
1 static void insert(int a[], int m, int n); 2 3 /* 4 * Straight Insertion Sort (sisort in short) 5 */ 6 void sisort(int a[], size_t n) 7 { 8 for (int i = 1; i < n; i++) { // a[i .. n-1] is not sorted 9 for (int j = 0; j < i; j++) { // a[0 .. i-1] is sorted 10 if (a[i] < a[j]) { // walk a[0 .. i-1], if a[i] < a[j] 11 insert(a, j, i); // insert a[i] before a[j] 12 break; 13 } 14 } 15 } 16 } 17 18 /* 19 * Insert a[n] before a[m] 20 * .-----------. 21 * | | 22 * o Input : a[m-1], a[m], a[m+1], ..., a[n-1], a[n], a[n+1] | 23 * \ \ \ | 24 * o Output: a[m-1], a[n], a[m], a[m+1], ..., a[n-1], a[n+1] | 25 * \____________________________________/ 26 */ 27 static void insert(int a[], int m, int n) 28 { 29 int t = a[n]; 30 for (int i = n; i > m; i--) 31 a[i] = a[i-1]; 32 a[m] = t; 33 }
接下来,给出一个完整的sisort.c并编译后测试, 以便形象化地理解直接插入排序的全过程。
o sisort.c // 将sisort()做稍稍增强以打印详细的排序过程。 注意:下面的程序中某些代码行风格很不好是故意的,因为只是为了帮助打印排序过程故一切从简。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 typedef struct obs_s { 5 unsigned int loop; 6 unsigned int inst; 7 unsigned int move; 8 } obs_t; 9 10 obs_t g_obs = { .loop = 0, .inst = 0, .move = 0 }; 11 12 static void insert(int a[], int m, int n); 13 static void show(int a[], size_t n); 14 15 void sisort(int a[], size_t n) 16 { 17 for (int i = 1; i < n; i++) { 18 printf("\n#%d:\t\t", i); show(a, n); 19 for (int j = 0; j < i; j++) { g_obs.loop++; 20 if (a[i] < a[j]) { g_obs.inst++; 21 printf("\t<-- insert a[%d] before a[%d]", i, j); 22 insert(a, j, i); 23 break; 24 } 25 } 26 } 27 } 28 29 static void insert(int a[], int m, int n) 30 { 31 int t = a[n]; 32 for (int i = n; i > m; i--) { g_obs.move++; 33 a[i] = a[i-1]; 34 } 35 a[m] = t; 36 } 37 38 static void show(int a[], size_t n) 39 { 40 for (int i = 0; i < n; i++) 41 printf("%c ", a[i]); 42 } 43 44 int main(int argc, char *argv[]) 45 { 46 if (argc < 2) { 47 fprintf(stderr, "Usage %s <C1> [C2] ...\n", argv[0]); 48 return -1; 49 } 50 51 argc--; 52 argv++; 53 54 size_t n = argc; 55 int *a = (int *)malloc(sizeof(int) * argc); 56 if (a == NULL) { 57 fprintf(stderr, "failed to malloc()\n"); 58 return -1; 59 } 60 61 for (int i = 0; i < n; i++) 62 *(a+i) = argv[i][0]; 63 64 printf(" \t0 1 2 3 4 5 6 7 8 9 10\n"); 65 printf("Before sorting: "); show(a, n); 66 sisort(a, n); printf("\n"); 67 printf("After sorting: "); show(a, n); printf("\n"); 68 printf("\n"); 69 printf("Total loop times : %2d\n", g_obs.loop); 70 printf("Total insertion times : %2d\n", g_obs.inst); 71 printf("Total move times : %2d\n", g_obs.move); 72 73 free(a); a = NULL; 74 75 return 0; 76 }
o 编译并测试
$ gcc -g -Wall -m32 -std=c99 -o sisort sisort.c $ ./sisort 0 1 2 3 4 5 6 7 8 9 a #[1] 0 1 2 3 4 5 6 7 8 9 10 Before sorting: 0 1 2 3 4 5 6 7 8 9 a #1: 0 1 2 3 4 5 6 7 8 9 a #2: 0 1 2 3 4 5 6 7 8 9 a #3: 0 1 2 3 4 5 6 7 8 9 a #4: 0 1 2 3 4 5 6 7 8 9 a #5: 0 1 2 3 4 5 6 7 8 9 a #6: 0 1 2 3 4 5 6 7 8 9 a #7: 0 1 2 3 4 5 6 7 8 9 a #8: 0 1 2 3 4 5 6 7 8 9 a #9: 0 1 2 3 4 5 6 7 8 9 a #10: 0 1 2 3 4 5 6 7 8 9 a After sorting: 0 1 2 3 4 5 6 7 8 9 a Total loop times : 55 Total insertion times : 0 Total move times : 0 $ ./sisort a 9 8 7 6 5 4 3 2 1 0 #[2] 0 1 2 3 4 5 6 7 8 9 10 Before sorting: a 9 8 7 6 5 4 3 2 1 0 #1: a 9 8 7 6 5 4 3 2 1 0 <-- insert a[1] before a[0] #2: 9 a 8 7 6 5 4 3 2 1 0 <-- insert a[2] before a[0] #3: 8 9 a 7 6 5 4 3 2 1 0 <-- insert a[3] before a[0] #4: 7 8 9 a 6 5 4 3 2 1 0 <-- insert a[4] before a[0] #5: 6 7 8 9 a 5 4 3 2 1 0 <-- insert a[5] before a[0] #6: 5 6 7 8 9 a 4 3 2 1 0 <-- insert a[6] before a[0] #7: 4 5 6 7 8 9 a 3 2 1 0 <-- insert a[7] before a[0] #8: 3 4 5 6 7 8 9 a 2 1 0 <-- insert a[8] before a[0] #9: 2 3 4 5 6 7 8 9 a 1 0 <-- insert a[9] before a[0] #10: 1 2 3 4 5 6 7 8 9 a 0 <-- insert a[10] before a[0] After sorting: 0 1 2 3 4 5 6 7 8 9 a Total loop times : 10 Total insertion times : 10 Total move times : 55 $ ./sisort S O R T E X A M P L E #[3] 0 1 2 3 4 5 6 7 8 9 10 Before sorting: S O R T E X A M P L E #1: S O R T E X A M P L E <-- insert a[1] before a[0] #2: O S R T E X A M P L E <-- insert a[2] before a[1] #3: O R S T E X A M P L E #4: O R S T E X A M P L E <-- insert a[4] before a[0] #5: E O R S T X A M P L E #6: E O R S T X A M P L E <-- insert a[6] before a[0] #7: A E O R S T X M P L E <-- insert a[7] before a[2] #8: A E M O R S T X P L E <-- insert a[8] before a[4] #9: A E M O P R S T X L E <-- insert a[9] before a[2] #10: A E L M O P R S T X E <-- insert a[10] before a[2] After sorting: A E E L M O P R S T X Total loop times : 27 Total insertion times : 8 Total move times : 36
以上排序过程(#[3])截图如下(截图来源: Algorithms Fourth Edition P251)
小结: 直接插入排序算法是稳定的算法,其时间复杂度和空间复杂度是:
Worst-case performance О(n**2) comparisons, swaps
Best-case performance O(n) comparisons, O(1) swaps
Average performance О(n**2) comparisons, swaps
Worst-case space complexity О(n) total, O(1) auxiliary
到此为止,我们已经完全弄明白了直接插入排序的原理。其核心就是:在已排好序的i条记录中插入一条新的记录,得到有序的i+1条记录。 下一节,我们将介绍可谓家喻户晓妇孺皆知的一种排序算法,那就是冒泡排序。