摘要訊息 : 每次向集合中插入元素的時候, 都讓元素找到合適的位置保持集合有序, 這樣就可以得到一個排序演算法.
0. 前言
對於一個空的集合, 如果每次插入元素之前, 我們通過比較找到該元素在集合中的位置, 使得插入元素之後集合仍然有序, 那麼最終我們得到的集合也是有序的. 這種思想也可以用在排序當中.
本篇文章中, 我們主要討論的是升序的插入排序法, 也就是從小到大對元素進行排序. 對於降序的情況, 類似可得.
更新紀錄 :
- 2022 年 4 月 26 日進行第一次更新和修正.
1. 插入並保持有序
對於一個新的元素 , 如果要插入一個有序集合 中, 我們可以先把 放到 的最後, 然後複製這個值得到一個副件 . 現在, 我們從 開始, 如果 的值比 大, 那麼就令 . 對於 都這樣處理, 直到找到一個 滿足 , 然後令 即可. 其中, . 這樣, 整個集合仍然有序.

例題 1. 設集合 , 使用 Algorithm 1 插入新元素 . 其中, 和 數值上相等, 下標僅用於區分.
:首先將 放入 尾部得到 . 比較 和 發現 , 因此更新 得到 . 比較 和 發現 , 因此更新 得到 . 比較 和 發現 不成立, 因此更新 得到 . 最終, 有序.
2. 插入排序法
當集合中不存在元素或者集合中有且唯有一個元素的時候, 集合就是有序的, 因此我們考慮集合中元素多於兩個的情況. 當集合中元素多於兩個的時候, 我們每次都取子集. 第一次取前兩個元素形成子集, 對這個子集使用 Algorithm 1, 這樣前兩個元素就保持有序了. 然後取前三個元素形成子集, 再次使用 Algorithm 1, 這樣前三個元素就保持有序了. 不斷重複這個過程, 直到取全部元素使用 Algorithm 1 之後, 整個集合就有序了.

我們合成 Algorithm 1 和 Algorithm 2 為插入排序法 (insertion sort).
3. 穩定性分析
們在《【演算法】名次排序法 (Ranking Sort)》中引入了排序演算法穩定性的定義 (見定義 1). 現在我們來分析一下由 Algorithm 1 驅動的插入排序法是否是穩定的排序.
斷言 1. 由 Algorithm 1 驅動的插入排序法是穩定的排序演算法.
:設集合 的子集 已經有序. 其中, . 若 和 相等 (), 那麼 在 中的位置顯然排在 之後. 由於 Algorithm 1 的比較和移動是從集合末尾開始往前的, 當 遇到 時, Algorithm 1 會將 安排在 的位置. 這樣, 在 中的位置仍然在 之後.
綜上所述, 由 Algorithm 1 驅動的插入排序法是穩定的排序演算法.
斷言 1 中並沒有直接斷言插入排序法就是穩定的演算法. 這是因為如果我們將 Algorithm 1 中的比較運算子換成 或者 , 那麼就會破壞排序的穩定性. 當然, 那些在 中相同的元素, 在 中是倒序排列的.
4. 複雜度分析
插入排序法是原地重排的排序法, 因此只需要一些輔助性的空間, 空間複雜度為 . 在最壞的情況下, 當我們每一次使用 Algorithm 1 的時候, 都需要移動 個元素. 在插入排序法中, Algorithm 1 會被使用 次, 所以選擇排序法的時間複雜度為 . 在最好的情況下, 我們不需要移動元素, 只需要進行比較, 因此時間複雜度為 . 因為一共要比較 次, 所以最好情況下的時間複雜度為 . 平均情況下, 插入排序法的時間複雜度為 .
5. 實作
Code 1. 插入排序法void insertion_sort(int arr[], int size) { for(auto i {1}; i < size; ++i) { auto j {i - 1}; auto value {arr[i]}; for(; j >= 0 and value < arr[j]; --j) { arr[j + 1] = arr[j]; } arr[j + 1] = value; } }
對於泛型版本的實作, 大家可以參考我的 GitHub : https://github.com/Jonny0201/data_structure/blob/master/data_structure/algorithm.hpp, 搜尋 insertion_sort
即可.
自創文章, 原著 : Jonny. 如若閣下需要轉發, 在已經授權的情況下請註明本文出處 :