插入排序有两种方法:直接插入排序、希尔排序
1.直接插入排序 (升序)
把待排序的记录按其关键码值的大小逐个插入到一个已经拍好序的有序序列中,直到所有的记录都插入完成为止,得到一个新的有序序列。
当插入第i个元素的时,前面的 i-1个元素都已经排好序了(arr[0],arr[1],arr[2],……arr[i-1],),此时用arr[i]和arr[i-1],arr[i-2]……的排序进行比较,找到第一个小于arr[i]的元素arr[j],将arr[i]插入到arr[j]后面位置,arr[j]和arr[i]之间的元素依次向后移动一个单位。
代码实现:
void InsertSort(int array[], int size){
for (int i = 1; i < size; i++){
int k = array[i];
int j;
for (int j = i - 1; j >= 0; --j){
if (array[j] <= k)
break;
else
array[j + 1] = array[j];
}
array[j + 1] = k;
}
}
总结:
- 元素集合越接近有序,直接插入排序的时间效率越高
- 时间复杂度:O(N^2) 最坏O(N^2) 最好的情况 O(N)
- 空间复杂度:O(1)
- 稳定性: 稳定
可优化的地方:在寻找插入位置arr[j+1]时,可使用二分查找的方法快速计算插入位置。
2.希尔排序(缩小增量排序)
直接插入排序的时间复杂度为O(n^2),但是若待排序序列为“正序”时,且数据量比较小,其时间复杂度可提高至O(n),由此可见若待排序列越接近有序,直接插入排序的效率也就越高。
希尔排序是对直接插入排序的优化。希尔排序基本思想就是:先将整个待排序列分割成若干个自序列分别进行直接插入排序,待整个序列接近基本有序时再对整个序列进行一次直接插入排序。
希尔排序的具体流程:
初始化一段数据:9 1 2 5 7 4 8 6 3 5 分别为N0~N9;
第一步先将待排序列分为若干组,{N0,N5},{N1,N5},{N2,N7},{N3,N8},{N4,N9},每一个子序列内部进行直接插入排序,排序结果如图第三行,这个过程被成为一次希尔排序。
然后进行第二次希尔排序将第一次排序得到的序列再此分组,因为已经经历过一次排序,序列“比较有序”所以第二次分组每组多分几个元素,然后每组内进行直接插入排序。
执行若干次希尔排序直至待排序列有序,希尔排序结束。
分组问题和判断待排序列有序问题:
从上述的排序过程可见,希尔排序的一个特点是:子序列的构成不是简单的“逐段分割”而是将相邻某个"增量"的记录组成一个子序列。
如上例子中,第一趟排序时的增量为 5,第二趟排序时的增量为2,第三次排序也就是最后一次排序时增量为1,此时进行的希尔排序就是直接插入排序,但是待排序列已经基本接近有序。
因此当增量为1的时候,是希尔排序的最后一次排序。
void InsertSortGap(int array[], int size, int gap){
for (int i = gap; i < size; i++){
int k = array[i];
int j;
for ( j = i - gap; j >= 0; j -= gap){
if (array[j] <= k)
break;
else
array[j + gap] = array[j];
}
array[j + gap] = k;
}
}
void ShellSort(int array[], int size){
int gap = size;
while (1){
gap = gap / 3 + 1;
//gap 比较大的数 --> 小 --->1 结束
InsertSortGap(array, size, gap);
if (gap == 1)
break;
}
Print(array, size);
}
总结:
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
- 时间复杂度:O(N^2)
- 稳定性: 不稳定