插入排序的主要思路是不断地将待排序的数值插入到有序段中,使有序段逐渐扩大,直至所有的数值都进入有序段中。
根据待排序的数值插入到有序段中的顺序,可以归纳为两类:直接插入排序和折半查找插入排序。
直接插入排序的基本思想是依次将记录序列中的每一个记录插入到有序段中,使有序段的长度不断地扩大。
现在以一个实例简单的说明一下这个算法的原理,假设数据存在数组a[]中,排序后的结果为升序:
初始状态: 45 37 61 87 66 23 29 45
45 37 61 87 66 23 29 45 ( 第一个元素认为已经排好序)
第一次: 37 45 61 87 66 23 29 45
目标元素a[1] = 37, 比较 a[1]和a[0],由于a[1] < a[0], 故需要交换位置。
第二次: 37 45 61 87 66 23 29 45
目标元素a[2] = 61,为了找到插入的位置需要分别比较a[0]和a[1]。分别比较a[2]和a[0]、a[1],a[1] < a[2],故不需要移动位置。
第三次: 37 45 61 87 66 23 29 45
目标元素a[3] = 87,为了找到插入的位置需要分别比较a[0]、a[1]和a[2]。a[2] < a[3],故不需要移动位置。
第四次: 37 45 61 66 87 23 29 45
目标元素a[4] = 66,为了找到插入的位置需要分别比较a[0]、a[1]、a[2]和a[3]。a[4] > a[2](61),a[4] < a[3](87),a[3]、a[4]交换位置。
第五次: 23 37 45 61 66 87 29 45
目标元素a[5] = 23,为了找到插入的位置需要分别比较a[0]、a[1]、a[2]、a[3]和a[4]。a[5] < a[0](37),因此a[0]及后面已排好序的元素均需要后移,a[5]移至a[0]。
第六次: 23 29 37 45 61 66 87 45
目标元素a[6] = 29,为了找到插入的位置需要分别比较a[0]、a[1]、a[2]、a[3]、a[4]和a[5]。a[6] > a[0](23),a[6] < a[1](37),因此a[1]及后面已排好序的元素均需要后移,a[6]移至a[1]。
第七次: 23 29 37 45 45 61 66 87
目标元素a[7] = 45,为了找到插入的位置需要分别比较a[0]、a[1]、a[2]、a[3]、a[4]、a[5]和a[6]。a[7] >= a[3](45),a[7] < a[4](61),因此a[4]及后面已排好序的元素均需要后移,a[7]移至a[4]。
参考代码:
1 #include <stdio.h> 2 3 #define MAX_NUM 80 4 5 void insertSort(int* a, int n) 6 { 7 int guard = -1; 8 for (int i = 0; i < n; i++) 9 { 10 guard = a[i]; 11 int j = i - 1; 12 while(guard < a[j] && j >= 0) 13 { 14 a[j+1] = a[j]; 15 j = j-1; 16 } 17 a[j+1] = guard; 18 19 for(int i = 0; i < n;i++) 20 { 21 printf("%d ",a[i]); 22 } 23 printf("\n"); 24 } 25 26 } 27 28 29 void binaryInsertSort(int* a, int n) 30 { 31 int guard = -1; 32 for (int i = 0; i < n; i++) 33 { 34 guard = a[i]; 35 int low = 0; 36 int high = i - 1; 37 38 while(low <= high) 39 { 40 int m = (low + high)/2; 41 if(guard < a[m] ) 42 high = m - 1; 43 else 44 low = m + 1; 45 46 } 47 48 for(int j = i-1; j>= high+1 ; --j) 49 a[j+1] = a[j]; 50 a[high+1] = guard; 51 52 for(int i = 0; i < n;i++) 53 { 54 printf("%d ",a[i]); 55 } 56 printf("\n"); 57 58 } 59 } 60 61 int main(int argc, char* argv[]) 62 { 63 int a[MAX_NUM]; 64 int n; 65 66 printf("Input total numbers: "); 67 scanf("%d",&n); 68 69 if( n > MAX_NUM ) n = MAX_NUM; 70 71 for(int i = 0; i < n;i++) 72 { 73 scanf("%d",&a[i]); 74 } 75 76 printf("排序步骤:\n"); 77 insertSort(a,n); 78 79 return 0; 80 }
案例结果截图:
_
直接插入排序的时效性与稳定性分析:
(1)最好情况:初始排序关键字已经有序,for循环进行一轮关键字比较,内循环while的条件均不满足,因此共比较n-1次,移动0次,算法时间复杂度为O(n).
(2)最坏情况:在待排序序列完全逆序,for循环共运行n-1次,在while循环进行关键字比较,总的比较次数为1+2+……+(n-1) = n(n-1)/2;
由于在待排序序列全部逆序,古仔while循环进行关键字比较时,每次比较均满足循环条件,故需要移动i-1步,因此总共移动次数为1+2+……+(n-1) = n(n-1)/2;
所以在最坏的情况下,比较和移动次数均为 n(n-1)/2次。
(3)平均情况:在这种情况下,外循环for循环的次数不变,但是内循环while循环在进行关键在比较时,平均有一半的元素在中间找到插入位置,比较次数比最坏情况降低一半,同时移动元素次数也会降低一半,因此比较和移动次数均为O(n(n+1)/2)/2,则时间复杂度为O(n2)。
由于直接插入排序是根据俄输入序列的顺序的大小来决定排序排序后的位置,是一种稳定的排序方法。
折半查找插入排序先取有序段的中间元素与查找值相比较。如果相等则查找成功;如果查找值大于中间元素,则再取高半部的中间元素与查找值相比较;如果查找值小于中间元素,则再取低半部的中间元素与查找值相比较。如此重复直到查找成功或最终为找到该数为止。在折半查找插入排序算法中,由于进行关键字比较的次数比较少,所以算法的效率就比较高。它与直接插入排序唯一不同的就是在有序段中搜索插入位置是,使用折半查找方式,故算法的时间复杂度同样为O(n2),也是一种稳定的排序方法。
代码在上述参考代码中给出。
注:主要参考彭军、向毅主编的 《数据结构与算法》