插入排序法
插入排序法是一種很容易想到的算法,它的思路與打撲克時排列手牌的方法很相似。比如我們現在單手拿牌,然後要將牌從左至右,從小到大進行排序。此時我們需要將牌一張張抽出來,分別插入到前面已排好序的手牌中的適當位置。重複這一操作直到插入最後一張牌,整個排序就完成了。
插入排序的算法如下:
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
*/