直接插入排序(Insert Sort)

标签

稳定排序、原地排序、比较排序

基本思想

直接插入排序是一种非常直观的排序算法,它的基本思想是将线性表分为已排序的前半部分和待排序的后半部分,从待排序部分选出第一个元素,插入到已排序部分的对应位置中,直到全部记录都插入到已排序部分中。通常来讲,我们不会真正的首先判断插入位置,再进行移动,而是边移动,边判断是否已经移动到对应的位置。

算法描述

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:

  • 步骤1: 从第一个元素开始,该元素可以认为已经被排序;
  • 步骤2: 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 步骤3: 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  • 步骤4: 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  • 步骤5: 将新元素插入到该位置后;
  • 步骤6: 重复步骤2~5。

动图演示

时间复杂度

直接插入排序每次插入的时间复杂度为$O(n)$,一共执行$n - 1$次,因此总体时间复杂度是$O(n^2)$。在插入排序插入位置的过程可以使用折半查找算法将查找位置的复杂度优化到$O(log n)$,但因为还需要$O(n)$的时间复杂度在顺序表上执行操作,所以总体时间复杂度依然是$O(n^2)$。

最好情况:当待排序记录已经有序,这时需要比较的次数是$C_{min} = n − 1 = O(n)$。

最坏情况:如果待排序记录为逆序,则最多的比较次数为$C_{max} = sum_{i = 1}^{n − 1} (i) = n (n−1)=O(n^2)。

平均情况:$O(n^2)$

空间复杂度

直接插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

算法示例

基本实现

void insert_sort_v1(int *arr, int l, int r) {
    for (int i = l + 1; i <= r; i++) {
        int j = i;
        while (j > l && arr[j] >  arr[j - 1] {
            swap(arr[j], arr[j - 1]);
            --j;
        }
    }
    return;
}

无监督优化

找到序列最小值,提前移至最左端,可减少一个监督项(监督项的代价为$O(n^2)$,查找并移换最小值的代价为$O(n)$)。

void insert_sort_v2(int *arr, int l, int r) {
    //查找和移位都是O(n)
    int min = l;
    for (int i = l + 1; i <= r; i++) {
        if (arr[i] < arr[min]) min = i;
    }
    //为什么逐次交换,而不是直接和最左端交换?
    for (int i = min; i > l; i--) {
        swap(arr[i], arr[i - 1]);
    }
    for (int i = l + 2; i <= r; i++) {
        int j = i;
        while (arr[j] < arr[j - 1]) { //去掉了一个监督项j > l
            swap(arr[j], arr[j - 1]);
            --j;
        }
    }
    return;
}

参考资料:

https://blog.csdn.net/coolwriter/article/details/78732728

https://blog.csdn.net/weixin_41190227/article/details/86600821

https://www.cnblogs.com/itsharehome/p/11058010.html

Min是清明的茗
原文地址:https://www.cnblogs.com/MinPage/p/14103952.html