插入排序法
插入排序法是一种很容易想到的算法,它的思路与打扑克时排列手牌的方法很相似。比如我们现在单手拿牌,然后要将牌从左至右,从小到大进行排序。此时我们需要将牌一张张抽出来,分别插入到前面已排好序的手牌中的适当位置。重复这一操作直到插入最后一张牌,整个排序就完成了。
插入排序的算法如下:
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
讲解
插入排序法在排序过程中,会将整个数组分成 “已排序部分” 和 “未排序部分”。
举个例子,我们对数组 进行插入排序时,整体流程如下图所示。
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 中,将开头元素 视为已排序,所以我们取出 的 3,将其插入已排序部分的恰当位置。首先把原先位于 的 8 移动至 ,再把 3 插入 。这样一来,开头 2 个元素就完成了排序。
在步骤 2 中,我们要把 的 1 插入恰当位置。这里首先将比 1 大的 和 顺次向后移动一个位置,然后把 1 插入 。
在步骤 3 中,我们要把 的 5 插入恰当位置。这次将比 5 大的 向后移动一个位置,然后把 5 插入 。
之后同理,将已排序部分的其中一段向后移动,再把未排序部分的开头元素插入已排序部分的恰当位置。插入排序法的特点在于,只要 0 到第 号元素全部排入已排序部分,那么无论后面如何插入,这个 0 到第 号元素都将永远保持排序完毕的状态。
实现插入排序法时需要的主要变量如下表所示:
长度为 的整形数组 | |
循环变量,表示未排序部分的开头元素 | |
临时保存 值的变量 | |
循环变量,用于在已排序部分寻找 的插入位置 |
外层循环的 从 1 开始自增。在每次循环开始时,将 的值临时保存在变量 中。
接下来是内部循环。我们要从已排序部分找出比 大的元素并让它们顺次后移一个位置。这里,我们让 从 开始向前自减,同时将比 大的元素从 移动到 。一旦 等于 -1 或当前 小于等于 则结束循环 ,并将 插入当前 的位置。
考察
在插入排序法中,我们只将比 (去出的值)大的元素向后平移,不相邻的元素不会直接交换位置,因此整个排序算法十分稳定。
然后我们考虑一下插入排序法的复杂度。这里需要估计每个 循环中 元素向后移动的次数。最坏的情况下,每个 循环都需要执行 次移动,总共需要 次移动,即算法复杂度为 。大多数时候,我们在计算复杂度的过程中,可以大致估计一下运算次数,然后只留下对代数式影响最大的项,忽略常数项。比如 ,这里的 相对于 而言就小得足以忽略,然后再忽略掉常数倍 ,得出复杂度与 成正比。当然,前提是假设这里的 足够大。
插入排序法是一种很有趣的算法,输入数据的顺序能大幅影响它的复杂度。我们前面说它的复杂度为 ,也仅是指输入数据为降序排列的情况。如果输入数据为升序排列,那么 从头至尾都不需要移动,程序只需要经历 次比较便可执行完毕。可见,插入排序法的优势就在于能快速处理相对有序的数据。
算法实现
#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
*/