)
很多人學 C 語言走到排序這一章第一反應就是背代碼。尤其是冒泡排序名氣太大幾乎每本教材都要講一遍導致不少初學者誤以為“排序算法 冒泡排序”。等真正面試或者做算法題的時候發(fā)現(xiàn)冒泡排序在數(shù)據(jù)量稍大時慢得離譜這才回頭重新研究其他排序。如果你也有類似困惑我的建議是C 語言入門階段先徹底吃透插入排序而不是急著背冒泡排序。為什么因為插入排序的思想和生活經(jīng)驗最貼近代碼量極小而且非常容易驗證正確性。你只需要理解“整理撲克牌”的過程就能寫出這個算法。更重要的是插入排序是理解更高級排序算法希爾排序、桶排序思想等的基礎學好它后面會順很多。這篇文章會從零開始用手工模擬、動畫拆解、完整代碼、時間復雜度和常見 bug 的方式幫你把直接插入排序徹底搞清楚。目標很明確30 分鐘內(nèi)你能獨立寫出沒有 bug 的插入排序代碼并且能解釋清楚每一行代碼為什么這么寫。1. 插入排序到底解決什么問題先明確一個基本問題排序算法到底在干什么給你一個無序數(shù)組比如int arr[8] {5, 2, 9, 1, 5, 6, 3, 8};排序的目標很簡單讓數(shù)組從小到大排列變成{1, 2, 3, 5, 5, 6, 8, 9}這看起來太簡單了簡單到很多人覺得“這不就是調(diào)一個函數(shù)的事嗎”。但在 C 語言學習階段排序的意義不在于“把數(shù)組排好”而在于訓練你三個核心能力循環(huán)邊界控制數(shù)組下標從 0 開始循環(huán)條件什么時候是i n什么時候是i n - 1寫錯一個邊界就是數(shù)組越界。元素移動思想插入排序的過程本質(zhì)是“平移元素”這種思想在很多算法里都會用到。算法復雜度意識同樣解決一個問題不同算法的效率差別巨大插入排序是最容易分析復雜度的算法之一。所以插入排序是 C 語言學習里一個性價比極高的知識點。它不只是讓你會排一個數(shù)組而是讓你第一次真正體會到“算法”這個詞的含義。2. 插入排序的核心思想整理撲克牌先拋開代碼想一個生活場景。你打撲克牌抓牌的時候會把牌一張張插到手里已經(jīng)排好序的牌中。比如手里已經(jīng)有3 5 8這時抓了一張6你會怎么放你會把8往后挪一位把6插到5和8之間變成3 5 6 8這個過程就是插入排序的本質(zhì)。現(xiàn)在把場景翻譯成數(shù)組操作數(shù)組的前一部分是“已經(jīng)排好序的牌”。數(shù)組的后一部分是“還沒抓上來的牌”。每一輪操作從“沒排序的部分”取第一張牌往“已經(jīng)排好序的部分”里插。插入的過程就是把比它大的元素往后挪空出位置再放進去。這就是“直接插入排序”Straight Insertion Sort。這個思想聽起來簡單但有一個細節(jié)很容易忽略在數(shù)組里“插入”一個元素不是直接塞進去而是要先把后面的元素往后挪。數(shù)組的內(nèi)存是連續(xù)的沒有“縫隙”可以讓元素直接插進去。所以插入排序的全部操作本質(zhì)上就是兩件事從后往前比較找到插入位置。把插入位置之后的元素全部往后移一格。理解了這一點代碼就不難寫了。2.1 插入排序的三種叫法你可能見過“直接插入排序”“插入排序”“簡單插入排序”這些名詞。它們說的是同一個東西名稱說明插入排序統(tǒng)稱指這一類通過插入來排序的算法直接插入排序最基礎的插入排序逐個向前比較并插入簡單插入排序和直接插入排序是同一個意思強調(diào)它實現(xiàn)簡單先掌握直接插入排序后面如果學到希爾排序你就能理解希爾排序是在直接插入排序基礎上做了“分組優(yōu)化”本質(zhì)上還是插入思想。3. 動畫級拆解一步一步看插入排序的過程這一節(jié)非常重要。很多人寫不出插入排序的代碼不是因為不會寫 C 語言而是腦子里沒有“排序過程”的動態(tài)畫面。我們用一組數(shù)據(jù)把每一輪的比較和移動都列出來。假設數(shù)組是int arr[6] {4, 3, 2, 10, 5, 1};目標是排成升序從小到大。下面是完整過程注意看每一輪發(fā)生了什么。初始狀態(tài)索引: 0 1 2 3 4 5 數(shù)值: 4 3 2 10 5 1我們規(guī)定索引 0 的元素即第一個元素 4已經(jīng)是“手里排好序的牌”因為單獨一個元素天然是有序的。所以從索引 1 開始逐個把后面的元素插入到前面有序區(qū)。第 1 輪插入元素 arr[1] 3當前狀態(tài)有序區(qū)[4] 待插入3把 3 和有序區(qū)從后往前比較4 3所以 4 往后移一位。位置 0 空出來了把 3 放進去。結果索引: 0 1 2 3 4 5 數(shù)值: 3 4 2 10 5 1此時前兩個元素3, 4有序。第 2 輪插入元素 arr[2] 2當前狀態(tài)有序區(qū)[3, 4] 待插入2從后往前比較4 24 往后移一位。3 23 往后移一位。位置 0 空出把 2 放進去。結果索引: 0 1 2 3 4 5 數(shù)值: 2 3 4 10 5 1此時前三個元素2, 3, 4有序。第 3 輪插入元素 arr[3] 10當前狀態(tài)有序區(qū)[2, 3, 4] 待插入10從后往前比較4 10不用移動。直接把 10 放在原位置。結果索引: 0 1 2 3 4 5 數(shù)值: 2 3 4 10 5 1這一輪其實什么都沒變。原因很簡單10 比有序區(qū)所有元素都大它已經(jīng)在正確位置了。很多初學者會在這里犯嘀咕那這輪還算不算“插入”算。只是移動次數(shù)為 0。這也提醒我們插入排序對基本有序的數(shù)據(jù)移動次數(shù)很少。第 4 輪插入元素 arr[4] 5當前狀態(tài)有序區(qū)[2, 3, 4, 10] 待插入5從后往前比較10 510 往后移一位。4 5停止移動。把 5 放到原來 10 的位置索引 3。結果索引: 0 1 2 3 4 5 數(shù)值: 2 3 4 5 10 1第 5 輪插入元素 arr[5] 1當前狀態(tài)有序區(qū)[2, 3, 4, 5, 10] 待插入1從后往前比較10 110 往后移一位。5 15 往后移一位。4 14 往后移一位。3 13 往后移一位。2 12 往后移一位。位置 0 空出把 1 放進去。最終結果索引: 0 1 2 3 4 5 數(shù)值: 1 2 3 4 5 10排序完成。3.1 過程規(guī)律總結把上面五輪操作抽象出來規(guī)律非常清晰外層循環(huán)從索引i 1開始到i n - 1結束表示“當前要處理的元素”。把arr[i]暫存到一個變量里因為后面移動元素會覆蓋它。內(nèi)層循環(huán)從j i - 1開始從后往前掃描有序區(qū)。如果arr[j] 暫存值就把arr[j]移到arr[j 1]繼續(xù)往前比較。如果arr[j] 暫存值說明找到插入位置停止移動把暫存值放到arr[j 1]。還有一種情況如果一直比到j 0說明暫存值比有序區(qū)所有元素都小應該放在數(shù)組開頭也就是位置 0。這段規(guī)律直接翻譯成 C 語言代碼就是完整的插入排序。4. 完整代碼基礎版直接插入排序先看最標準的寫法。這個版本不含哨兵邏輯最直觀適合剛開始學習的階段。// 文件路徑insert_sort.c #include stdio.h // 直接插入排序升序 void insertSort(int arr[], int n) { int i, j, temp; for (i 1; i n; i) { temp arr[i]; // 暫存待插入元素 j i - 1; // 從有序區(qū)的最后一個元素開始比較 // 從后往前找插入位置比 temp 大的元素都往后移動 while (j 0 arr[j] temp) { arr[j 1] arr[j]; // 后移元素 j--; } arr[j 1] temp; // 把 temp 放到正確位置 } } // 打印數(shù)組 void printArray(int arr[], int n) { for (int i 0; i n; i) { printf(%d , arr[i]); } printf(\n); } int main() { int arr[] {4, 3, 2, 10, 5, 1}; int n sizeof(arr) / sizeof(arr[0]); printf(排序前); printArray(arr, n); insertSort(arr, n); printf(排序后); printArray(arr, n); return 0; }編譯并運行gcc insert_sort.c -o insert_sort ./insert_sort預期輸出排序前4 3 2 10 5 1 排序后1 2 3 4 5 10這段代碼就是插入排序的標準形態(tài)請確保你能閉著眼睛寫出來。4.1 核心代碼逐行解釋我們來解釋一下最關鍵的四行邏輯temp arr[i];arr[i]就是這一輪要插入的元素。必須先存到臨時變量里因為在后面的 while 循環(huán)中arr[j 1] arr[j]會從右往左覆蓋元素如果不提前保存arr[i]的值會被覆蓋掉數(shù)據(jù)就丟了。j i - 1;i - 1是有序區(qū)最后一個元素的下標。比如第 4 輪處理索引 4值是 5時有序區(qū)是[2, 3, 4, 10]最后一個元素是arr[3]也就是 10。所以j從 3 開始往前比較非常自然。while (j 0 arr[j] temp)這是整個算法的核心判斷條件。它做了兩件事j 0防止數(shù)組下標越界。如果一直往前比較到數(shù)組開頭還沒找到位置說明temp是當前最小的元素應該放在位置 0。arr[j] temp表示“前一個元素比待插入元素大”。這種情況下前一個元素必須往后挪給temp騰地方。特別注意條件里的順序不能反。j 0必須寫在前面。因為 C 語言的是短路運算一旦j 0成立后面的arr[j]根本不會執(zhí)行這樣就不會訪問arr[-1]。arr[j 1] temp;循環(huán)結束后j指向的是最后一個不比temp大的元素所以temp應該放到j 1的位置。如果你直接把temp放到arr[j]就會漏掉一個位置或者把不該覆蓋的元素覆蓋掉。這個細節(jié)值得單獨說一遍插入位置是 j 1不是 j。5. 親自驗證在循環(huán)中打印每一輪的排序結果光看最終輸出很多人還是不太放心“排序過程中到底發(fā)生了什么”。這里提供一個加強版的代碼它會在每一輪結束后打印當前數(shù)組狀態(tài)方便你手動對照上文的動畫級拆解。// 文件路徑insert_sort_debug.c #include stdio.h void insertSortWithProcess(int arr[], int n) { int i, j, temp; for (i 1; i n; i) { temp arr[i]; j i - 1; while (j 0 arr[j] temp) { arr[j 1] arr[j]; j--; } arr[j 1] temp; // 打印當前第 i 輪結束后的數(shù)組狀態(tài) printf(第 %d 輪后, i); for (int k 0; k n; k) { printf(%d , arr[k]); } printf(\n); } } int main() { int arr[] {4, 3, 2, 10, 5, 1}; int n sizeof(arr) / sizeof(arr[0]); printf(初始數(shù)組); for (int k 0; k n; k) { printf(%d , arr[k]); } printf(\n); insertSortWithProcess(arr, n); printf(最終結果); for (int k 0; k n; k) { printf(%d , arr[k]); } printf(\n); return 0; }運行輸出初始數(shù)組4 3 2 10 5 1 第 1 輪后3 4 2 10 5 1 第 2 輪后2 3 4 10 5 1 第 3 輪后2 3 4 10 5 1 第 4 輪后2 3 4 5 10 1 第 5 輪后1 2 3 4 5 10 最終結果1 2 3 4 5 10建議你運行這段代碼對照上一節(jié)的手工模擬過程逐行比對。這一步能幫你把“抽象的循環(huán)”和“數(shù)組下標的變化”對應起來理解會立刻深入一層。6. 時間復雜度與穩(wěn)定性分析排序算法不能只看“能不能排對”還要看“快不快”。插入排序的性能分析是重點也是面試中頻繁考察的知識點。6.1 時間復雜度插入排序的核心操作有兩個比較和移動。最壞情況數(shù)組完全逆序比如{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}。每一輪當前元素都要和前面所有元素比較一遍并且全部要往后移動。比較次數(shù)和移動次數(shù)都接近n^2 / 2所以時間復雜度是O(n2)。最好情況數(shù)組已經(jīng)完全有序比如{1, 2, 3, 4, 5}。每一輪當前元素只需要比較一次因為前一個元素比它小不需要移動所以比較次數(shù)是n - 1移動次數(shù)是 0。時間復雜度是O(n)。平均情況時間復雜度是O(n2)。這里的結論非常有意思插入排序對“基本有序”的數(shù)據(jù)表現(xiàn)極好。如果數(shù)據(jù)本身已經(jīng)接近有序插入排序會比很多 O(n2) 級別的排序算法快得多甚至接近 O(n)。正是這個特性讓插入排序成為許多復雜排序算法的“最后一步收尾工具”比如快速排序在處理小規(guī)模子數(shù)組時有些實現(xiàn)會切換成插入排序。6.2 空間復雜度插入排序是原地排序只需要一個臨時變量temp空間復雜度是O(1)。6.3 穩(wěn)定性插入排序是穩(wěn)定排序。什么叫穩(wěn)定如果數(shù)組里有兩個相等的元素比如{3, 5a, 5b, 1}其中5a和5b值相同但來自不同的原始位置穩(wěn)定排序能保證排完序后5a仍然在5b前面。插入排序為什么穩(wěn)定因為代碼里的判斷條件是arr[j] temp注意是“大于”不是“大于等于”。當遇到相等的元素時循環(huán)會停止temp被放到相等元素的后方不會跨過相等元素所以相同元素的相對順序不會改變。6.4 三種情況小結情況比較次數(shù)移動次數(shù)時間復雜度最好已有序n - 10O(n)最壞逆序n2/2n2/2O(n2)平均隨機約 n2/4約 n2/4O(n2)7. 插入排序的優(yōu)化哨兵版本很多教材在講插入排序時會提到一個優(yōu)化版本使用“哨兵”來減少邊界判斷。先看原來的內(nèi)層循環(huán)條件while (j 0 arr[j] temp)這里有j 0這個判斷。每次循環(huán)都要檢查一次雖然開銷不大但理論上可以減少。優(yōu)化思路是把temp暫存到arr[0]然后用arr[0]作為哨兵。這樣即使temp比所有有序區(qū)元素都小循環(huán)到j 0時因為arr[0] temparr[j] temp不成立while 自然停止不需要額外判斷j 0。不過要注意這種寫法把數(shù)組下標為 0 的位置當作“緩存區(qū)”所以實際排序的數(shù)據(jù)要從下標 1 開始存放。如果你在競賽或者教材中看到這種寫法不要覺得奇怪。// 文件路徑insert_sort_sentinel.c #include stdio.h // 帶哨兵的插入排序arr[0] 作為哨兵實際數(shù)據(jù)從 arr[1] 開始 void insertSortWithSentinel(int arr[], int n) { // n 是待排序元素個數(shù)有效下標從 1 到 n int i, j; for (i 2; i n; i) { arr[0] arr[i]; // 哨兵暫存待插入元素 j i - 1; while (arr[j] arr[0]) { arr[j 1] arr[j]; j--; } arr[j 1] arr[0]; } } int main() { // 注意下標 0 是哨兵位不參與排序實際排序元素從下標 1 開始 int arr[7] {0, 4, 3, 2, 10, 5, 1}; int n 6; // 實際排序 6 個元素下標 1~6 printf(排序前); for (int i 1; i n; i) { printf(%d , arr[i]); } printf(\n); insertSortWithSentinel(arr, n); printf(排序后); for (int i 1; i n; i) { printf(%d , arr[i]); } printf(\n); return 0; }這個版本的優(yōu)點在于內(nèi)層 while 少了j 0的邊界檢查代碼更精簡理論上運行更快。缺點是理解難度稍高因為下標從 1 開始如果你剛接觸指針和數(shù)組可能容易混淆。我的建議是先掌握最基礎版本哨兵版本作為進階理解。在實際工程項目中編譯器對邊界判斷的優(yōu)化已經(jīng)很好了哨兵帶來的性能提升并不明顯更多是算法教材里用來訓練思維。8. 插入排序 vs 冒泡排序 vs 選擇排序C 語言入門時這三個算法經(jīng)常被放在一起比較。用一個表格看懂它們的關鍵差異維度插入排序冒泡排序選擇排序核心思想往有序區(qū)插入元素相鄰元素交換大的沉底每輪選擇最小的放前面最好時間復雜度O(n)O(n2)基礎版O(n2)最壞時間復雜度O(n2)O(n2)O(n2)平均時間復雜度O(n2)O(n2)O(n2)空間復雜度O(1)O(1)O(1)穩(wěn)定性穩(wěn)定穩(wěn)定不穩(wěn)定對“近似有序”數(shù)據(jù)表現(xiàn)極好慢慢代碼難度中等最簡單簡單冒泡排序勝在好理解選擇排序勝在“交換次數(shù)少”但插入排序在綜合表現(xiàn)上通常更好。特別是數(shù)據(jù)規(guī)模很小或者數(shù)據(jù)已經(jīng)接近有序時插入排序幾乎是無敵的。表格里沒有提到快速排序因為它和插入排序不在一個學習階段??焖倥判蚴欠种嗡枷脒m合大數(shù)據(jù)量插入排序是基礎排序適合小數(shù)據(jù)量和教學場景。兩者不是替代關系而是互補關系。9. 常見錯誤與排查方法寫插入排序的過程中初學者最常見的錯誤集中在幾個地方。這里把高頻 bug 列成表格方便你遇到問題時快速對照。問題現(xiàn)象可能原因排查方式解決方案排序結果第一個元素是亂的while 中j 0忘記判斷導致訪問arr[-1]在 while 循環(huán)前打印 j 的初值查看是否出現(xiàn) -1補上j 0條件注意短路順序排序后元素丟失出現(xiàn)重復值沒有使用temp暫存移動元素時覆蓋了待插入值在進入 while 前打印arr[i]和后續(xù)輸出對比先temp arr[i]再開始移動排序結果不對但沒報錯第一個元素總是被覆蓋哨兵版本中把arr[0]當成普通數(shù)據(jù)參與了排序檢查數(shù)組定義下標 0 是否留給了哨兵數(shù)據(jù)從下標 1 開始存或者不用哨兵版數(shù)組越界程序崩潰外層循環(huán)i n錯寫成i n檢查循環(huán)條件改成i n因為下標最大是 n-1while 寫成了死循環(huán)j--寫在循環(huán)體內(nèi)但被條件擋住沒執(zhí)行到單步調(diào)試看 j 是否有變化確認j--在循環(huán)體內(nèi)一定會被執(zhí)行排序結果完全沒變化數(shù)組傳參方式錯誤或者是傳入的是值拷貝在函數(shù)內(nèi)打印數(shù)組地址確認和調(diào)用方一致C 語言數(shù)組傳參本質(zhì)是傳指針檢查函數(shù)簽名輸入有重復元素排序后出現(xiàn)亂序且不對內(nèi)層判斷寫成了arr[j] temp破壞了穩(wěn)定性用含重復數(shù)據(jù)的數(shù)組測試改成arr[j] temp這里額外強調(diào)一個最重要的排查手段打印和單步調(diào)試。如果排序結果不對不要猜直接在關鍵位置加printf打印每一輪的數(shù)組狀態(tài)和 3.1 節(jié)的手工模擬結果對照很快就能找到問題。10. 插入排序的工程實踐建議與適用場景學了插入排序什么時候真的會用到它這里給幾個實際的判斷。10.1 適合插入排序的場景數(shù)據(jù)量很小當數(shù)組長度小于幾十時插入排序的實現(xiàn)簡單、常數(shù)小不一定比快排慢。數(shù)據(jù)基本有序比如日志按時間寫入偶爾有少數(shù)亂序記錄用插入排序效率很高。作為復雜排序的收尾C 標準庫里的qsort雖然用快速排序但很多快速排序實現(xiàn)在遞歸到子數(shù)組足夠小的時候會改用插入排序。這是插入排序在真實工程中最常見的用途之一。鏈表排序對鏈表來說插入排序非常自然因為鏈表不需要大量移動元素只需要修改指針。10.2 不適合插入排序的場景超大規(guī)模數(shù)據(jù)幾十萬甚至上百萬條數(shù)據(jù)時O(n2) 的時間復雜度會讓程序卡到無法接受此時應使用歸并排序、快速排序、堆排序等 O(n log n) 級別的算法。數(shù)據(jù)完全隨機且規(guī)模很大插入排序會退化成大量比較和移動性能很差。10.3 代碼風格建議排序函數(shù)不要依賴全局變量通過參數(shù)傳入數(shù)組指和長度保持函數(shù)通用性。數(shù)組長度盡量用sizeof(arr) / sizeof(arr[0])計算不要寫死。在函數(shù)內(nèi)不要修改數(shù)組長度變量n在排序過程中保持不變。建議把函數(shù)名寫成insertSort而不是sort避免命名過于泛化也方便和其他排序算法區(qū)分。11. 一個綜合練習插入排序 從文件讀取數(shù)據(jù)如果你覺得單純排一個固定數(shù)組不夠過癮可以試試這個綜合練習從文本文件中讀取一組整數(shù)用插入排序排好序再把結果輸出到另一個文件。這個練習覆蓋了 C 語言文件操作和排序算法在很多時候你上網(wǎng)搜“C語言文件讀寫操作代碼”實際遇到的就是類似需求。// 文件路徑insert_sort_file.c #include stdio.h void insertSort(int arr[], int n) { int i, j, temp; for (i 1; i n; i) { temp arr[i]; j i - 1; while (j 0 arr[j] temp) { arr[j 1] arr[j]; j--; } arr[j 1] temp; } } int main() { FILE *fin, *fout; int arr[100]; int n 0; fin fopen(input.txt, r); if (fin NULL) { printf(無法打開 input.txt\n); return 1; } // 從文件讀取整數(shù)直到文件末尾 while (fscanf(fin, %d, arr[n]) 1 n 100) { n; } fclose(fin); printf(讀取到 %d 個整數(shù)。\n, n); printf(排序前); for (int i 0; i n; i) { printf(%d , arr[i]); } printf(\n); insertSort(arr, n); printf(排序后); for (int i 0; i n; i) { printf(%d , arr[i]); } printf(\n); fout fopen(output.txt, w); if (fout NULL) { printf(無法創(chuàng)建 output.txt\n); return 1; } for (int i 0; i n; i) { fprintf(fout, %d , arr[i]); } fclose(fout); printf(結果已寫入 output.txt\n); return 0; }在同一目錄下創(chuàng)建input.txt內(nèi)容如下42 7 19 3 88 1 5 23編譯運行gcc insert_sort_file.c -o insert_sort_file ./insert_sort_file預期輸出讀取到 8 個整數(shù)。 排序前42 7 19 3 88 1 5 23 排序后1 3 5 7 19 23 42 88 結果已寫入 output.txt同時output.txt里會寫入排序后的結果。這個練習還有一個價值讓你理解fscanf的返回值。fscanf成功讀取一個整數(shù)時返回 1讀到文件末尾返回 EOF所以fscanf(fin, %d, arr[n]) 1才能作為循環(huán)條件。這種寫法在“從文件讀取未知數(shù)量數(shù)據(jù)”的場景里非常實用值得單獨記憶。12. 給初學者的學習路徑建議如果你正在自學 C 語言不清楚算法這塊應該按什么順序學這里給一條經(jīng)過驗證的路徑先理解數(shù)組和循環(huán)插入排序的代碼幾乎全是數(shù)組和循環(huán)的組合如果for、while還不熟練先補基礎。動手模擬一輪排序拿紙和筆手動把一個 5 元素的數(shù)組按插入排序的過程走一遍。這一步不能省它能幫你建立算法執(zhí)行的畫面感。默寫基礎版代碼不看參考資料憑記憶寫出insertSort函數(shù)。寫不出來也沒關系對照本文 4.1 節(jié)的解釋找出卡住的地方。用調(diào)試代碼驗證過程運行 5.1 節(jié)的增強版程序把輸出和你的手寫模擬對照。嘗試做變體練習比如改成降序排列把arr[j] temp改成arr[j] temp或者統(tǒng)計排序過程中的比較次數(shù)和移動次數(shù)。了解哨兵優(yōu)化和復雜度分析這一層屬于進階能理解最好暫時看不懂也不影響使用。按照這個順序插入排序這個知識點基本就吃透了。之后再去學快速排序、歸并排序你會發(fā)現(xiàn)它們雖然更復雜但很多分析思路和插入排序是相通的。插入排序不是最快的排序算法但它小巧、穩(wěn)定、貼近生活是 C 語言學習者進入算法世界的第一道門。把這一道門走通后面的路會順暢很多。希望這篇文章能幫你把插入排序徹底弄懂而不是停留在“看別人代碼覺得自己會了”的階段。建議收藏起來寫代碼卡住的時候隨時回看。