插入排序

作者: Sun东辉 | 来源:发表于2022-06-16 08:27 被阅读0次

插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用 in-place 排序(原地排序,即只需用到 O(1) 的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

执行过程

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

插入排序的伪代码实现如下:

INSERTION-SORT(A)
1 for j = 2 to A.length
2     key = A[j]
3     // Insert A[j] into the sorted sequence A[1..j-1].
4     i = j - 1
5     while i > 0  and A[i] > key 
6          A[i+1] = A[i]
7          i = i - 1
8     A[i + 1] = key

使用循环不变式来理解算法的正确性

  • 初始化:首先,可以看出,在第一次循环迭代之前(当 j=2 时),子数组 A[1..j-1] 只有一个元素 A[1],因此,循环不变式成立。
  • 保持:接着,处理第二条性质:证明每次迭代保持循环不变式。在 for 循环体的第 4~7 行将 A[j-1]、A[j-2]、A[j-3] 等向右移动一个位置,直到找到 A[j] 的适当位置,第 8 行将 A[j] 的值插入该位置。此时,子数组 A[1..j] 由原来在 A[1..j] 中的元素组成,但已按序排列。那么对 for 循环的下一次迭代增加 j 将保持循环不变式。
  • 终止:最后,研究在循环终止时发生了什么。导致 for 循环终止的条件是 j>A.length=n。因为每次循环迭代 j 增加 1,那么必有 j=n+1。在循环不变式的表述中将 j 用 n+1 代替,最终:子数组 A[1..n] 由原来在 A[1..n]中的元素组成,但已按序排列,即子数组 A[1..n] 就是整个数组已排序的结果,因此算法正确。

算法分析

我们首先给出 INSERTION-SORT 中,每条语句的执行时间和执行次数。

该算法的运行时间是执行每条语句的运行时间之和。即,在具有 n 个值的输入时,INSERTION-SORT 的运行时间

T(n)\;=\;c_1n+c_2(n-1)+c_4(n-1)+c_5\sum_{j=2}^nt_j+c_6\sum_{j=2}^n(t_j-1)+c_7\sum_{j=2}^n(t_j-1)+c_8(n-1)

当出现最佳情况时(输入数组已排好序),算法的运行时间

T(n)\;=\;c_1n+c_2(n-1)+c_4(n-1)+c_5(n-1)+c_8(n-1)=(c_1+c_2+c_4+c_5+c_8)n-(c_2+c_4+c_5+c_8)

此时,我们可以把运行时间表示为 an+b,其中常量 a 和 b 依赖于语句代价 c_i。因此,它是 n 的线性函数。

当出现最坏情况时(输入数组已反向排序),每个元素 A[j] 都会与整个已排序的子数组 A[1..j-1] 中的每个元素进行比较,此时,

\sum_{j=2}^nt_j=\frac{n(n+1)}2-1

\overset n{\underset{j=2}{\sum(}}t_j-1)=\frac{n(n-1)}2

算法的运行时间

T(n)\;=\;c_1n+c_2(n-1)+c_4(n-1)+c_5(\frac{n(n+1)}2-1)+c_6(\frac{n(n-1)}2)+c_7(\frac{n(n-1)}2)+c_8(n-1)=(\frac{c_5}2+\frac{c_6}2+\frac{c_7}2)n^2+(c_1+c_2+c_4+\frac{c_5}2-\frac{c_6}2-\frac{c_7}2+c_8)n-(c_2+c_4+c_5+c_8)

此时,我们可以把运行时间表示为 an^2+bn+c,其中常量 a、b 和 c 依赖于语句代价 c_i。因此,它是 n 的二次函数。

算法复杂度

上面,我们已经分析了插入排序的最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需进行 n-1 次;最坏情况就是,序列是降序排列,那么此时需要进行的比较共有 \frac{1}{2}n(n-1)次。插入排序的赋值操作是比较操作的次数减去 n-1 次,(因为 n-1 次循环中,每一次循环的比较都比赋值多一个,多在最后那一次比较并不带来赋值)。平均来说插入排序算法复杂度为 O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千;或者若已知输入元素大致上按照顺序排列,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在 STL 的 sort 算法和stdlib 的 qsort 算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)。

代码实现

JavaScript

function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr;
}

Python

def insertionSort(arr):
    for i in range(len(arr)):
        preIndex = i-1
        current = arr[i]
        while preIndex >= 0 and arr[preIndex] > current:
            arr[preIndex+1] = arr[preIndex]
            preIndex-=1
        arr[preIndex+1] = current
    return arr

Go

func insertionSort(arr []int) []int {
        for i := range arr {
                preIndex := i - 1
                current := arr[i]
                for preIndex >= 0 && arr[preIndex] > current {
                        arr[preIndex+1] = arr[preIndex]
                        preIndex -= 1
                }
                arr[preIndex+1] = current
        }
        return arr
}

Java

public class InsertSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
        for (int i = 1; i < arr.length; i++) {

            // 记录要插入的数据
            int tmp = arr[i];

            // 从已经排序的序列最右边的开始比较,找到比其小的数
            int j = i;
            while (j > 0 && tmp < arr[j - 1]) {
                arr[j] = arr[j - 1];
                j--;
            }

            // 存在比其小的数,插入
            if (j != i) {
                arr[j] = tmp;
            }

        }
        return arr;
    }
}

C

void insertion_sort(int arr[], int len){
        int i,j,key;
        for (i=1;i<len;i++){
                key = arr[i];
                j=i-1;
                while((j>=0) && (arr[j]>key)) {
                        arr[j+1] = arr[j];
                        j--;
                }
                arr[j+1] = key;
        }
}

C++

void insertion_sort(int arr[],int len){
        for(int i=1;i<len;i++){
                int key=arr[i];
                int j=i-1;
                while((j>=0) && (key<arr[j])){
                        arr[j+1]=arr[j];
                        j--;
                }
                arr[j+1]=key;
        }
}

参考文献

相关文章

网友评论

    本文标题:插入排序

    本文链接:https://www.haomeiwen.com/subject/orkxvrtx.html