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