【初等排序】插入排序法详解

版权声明:Laugh https://blog.csdn.net/laugh12321/article/details/81977453

插入排序法

插入排序法是一种很容易想到的算法,它的思路与打扑克时排列手牌的方法很相似。比如我们现在单手拿牌,然后要将牌从左至右,从小到大进行排序。此时我们需要将牌一张张抽出来,分别插入到前面已排好序的手牌中的适当位置。重复这一操作直到插入最后一张牌,整个排序就完成了。

插入排序的算法如下:

insertionSort(A, N) // 包含 N 个元素的 0 起点数组 A
    for i from 1 to N - 1
        v = A[i]
        j = i - 1
        while j >= 0 and A[j] > v
            A[j + 1] = A[j]
            j--
        A[j + 1] = v

讲解

插入排序法在排序过程中,会将整个数组分成 “已排序部分” 和 “未排序部分”。

举个例子,我们对数组 A = {\left \{ 8,3,1,5,2,1 \right \}} 进行插入排序时,整体流程如下图所示。

8 3 1 5 2 1 // 步骤 0
0 1 2 3 4 5 // 下标

3 8 1 5 2 1 // 步骤 1
0 1 2 3 4 5 // 下标

1 3 8 5 2 4 // 步骤 2
0 1 2 3 4 5 // 下标

1 3 5 8 2 1 // 步骤 3
0 1 2 3 4 5 // 下标

1 2 3 5 8 1 // 步骤 4
0 1 2 3 4 5 // 下标

1 1 2 3 5 8 // 步骤 5
0 1 2 3 4 5 // 下标


在步骤 1 中,将开头元素 A[0](=8) 视为已排序,所以我们取出 A[1] 的 3,将其插入已排序部分的恰当位置。首先把原先位于 A[0] 的 8 移动至 A[1],再把 3 插入 A[0]。这样一来,开头 2 个元素就完成了排序。

在步骤 2 中,我们要把 A[2] 的 1 插入恰当位置。这里首先将比 1 大的 A[1](=8)A[0](=3) 顺次向后移动一个位置,然后把 1 插入 A[0]

在步骤 3 中,我们要把 A[3] 的 5 插入恰当位置。这次将比 5 大的 A[2](=8) 向后移动一个位置,然后把 5 插入 A[2]

之后同理,将已排序部分的其中一段向后移动,再把未排序部分的开头元素插入已排序部分的恰当位置。插入排序法的特点在于,只要 0 到第 i 号元素全部排入已排序部分,那么无论后面如何插入,这个 0 到第 i 号元素都将永远保持排序完毕的状态。

实现插入排序法时需要的主要变量如下表所示:

            A[N]                                                                 长度为 N 的整形数组
               i 循环变量,表示未排序部分的开头元素
               v 临时保存 A[i] 值的变量
               j

循环变量,用于在已排序部分寻找 v 的插入位置

外层循环的 i 从 1 开始自增。在每次循环开始时,将 A[i] 的值临时保存在变量 v 中。

接下来是内部循环。我们要从已排序部分找出比 v 大的元素并让它们顺次后移一个位置。这里,我们让 ji- 1 开始向前自减,同时将比 v 大的元素从 A[j] 移动到 A[j+1]。一旦 j 等于 -1 或当前 A[j]  小于等于 v 则结束循环 ,并将 v 插入当前 j +1 的位置。


考察

在插入排序法中,我们只将比 v (去出的值)大的元素向后平移,不相邻的元素不会直接交换位置,因此整个排序算法十分稳定。

然后我们考虑一下插入排序法的复杂度。这里需要估计每个 i 循环中 A[j] 元素向后移动的次数。最坏的情况下,每个 i 循环都需要执行 i 次移动,总共需要 1+2+....+N-1=(N^{2} - N)/2 次移动,即算法复杂度为 O(N^{2})。大多数时候,我们在计算复杂度的过程中,可以大致估计一下运算次数,然后只留下对代数式影响最大的项,忽略常数项。比如 \frac{N^{2}}{2} - \frac{N}{2} ,这里的 N 相对于 N^{2} 而言就小得足以忽略,然后再忽略掉常数倍 \frac{1}{2} ,得出复杂度与 N^{2} 成正比。当然,前提是假设这里的 N 足够大。

插入排序法是一种很有趣的算法,输入数据的顺序能大幅影响它的复杂度。我们前面说它的复杂度为 O(N^{2}),也仅是指输入数据为降序排列的情况。如果输入数据为升序排列,那么 A[j] 从头至尾都不需要移动,程序只需要经历 N 次比较便可执行完毕。可见,插入排序法的优势就在于能快速处理相对有序的数据。


算法实现

#include <stdio.h>

/* 按顺序输出数组元素 */
void trace(int A[], int N) {
    int i;
    for (i = 0; i < N; i++) {
        if (i > 0) printf(" "); /* 在相邻元素之间输出 1 个空格 */
        printf("%d", A[i]);
    }
    printf("\n");
}

/* 插入排序(0 起点数组)*/
void insertionSort(int A[], int N) {
    int i, j, v;
    for (i = 1; i < N; i++) {
        v = A[i];
        j = i - 1;
        while (j >= 0 && A[j] > v) {
            A[j + 1] = A[j];
            j--;
        }
        A[j + 1] = v;
        trace(A, N);
    }
}

int main() {
    int N, i, j;
    int A[100];

    scanf("%d", &N);
    for (i = 0; i < N; i++) scanf("%d", &A[i]);

    trace(A, N);
    insertionSort(A, N);

    return 0;
}

/*
输入示例:
6
5 2 4 6 1 3

输出示例:
5 2 4 6 1 3
2 5 4 6 1 3
2 4 5 6 1 3
2 4 5 6 1 3
1 2 4 5 6 3
1 2 3 4 5 6
*/

猜你喜欢

转载自blog.csdn.net/laugh12321/article/details/81977453