1. 程式人生 > >初學者學演演算法|排序法入門:選擇排序與插入排序法

初學者學演演算法|排序法入門:選擇排序與插入排序法

O(n²):插入排序法 (Insertion Sort)

同樣擁有 O(n²) 時間複雜度,插入排序法 Insertion Sort 則是另外一個非常常見的排序法。簡單來說,插入排序法就是你玩撲克牌時用到的排序法。

讀一個數字

  • 從「未排序過的數字」中讀取一個數

插入合適位置

  • 把這個讀取的數往前插入一個位置

現在,我們重新使用 [41, 33, 17, 80, 61, 5, 55] 的陣列,在下面的圖中,我們把尚未排序過的數字用紅色標示,這輪要插入的值以橘色標示,排序過的以藍色標示。

從上面可以看到,插入排序法的步驟就像我們玩撲克牌在整牌的時候一樣,只是我們在插入排序法時一次只會插入一個數,而撲克牌在整牌的時候我們有時會同時插入兩三張牌。

接下來,讓我們用慢動作再複習一次:

看完插入排序的慢動作後,讓我們一起來分析它的時間複雜度的部分吧!

插入排序的時間複雜度

觀察插入排序法的時間複雜度,我們要同時複習一個在上一篇文章中提到的觀念:

通常程式步驟的時間複雜度會是用程式執行會碰到的最壞狀況 (Worst Case) 來表示

在這邊,我們要用另外一種方法分析時間複雜度:我們分別從排序法會遇到的最佳狀況與最壞狀況來分析。

最佳狀況

排序法遇到的最佳狀況會是什麼呢?直觀地想,如果一個陣列在讀取前就剛好已經排序好了,那麼我們在做排序法時,通常可以花比較少的步驟數。

最佳狀況

回想插入排序法的兩個步驟,每一輪的「讀一個數字」我們需要一個步驟(不知道為何需要一個步驟可以回去上一篇中複習陣列讀取),而「插入合適位置」則因為每個數字剛好都在合適位置了,所以不需要再做任何動作。

因此,在最佳狀況,每一輪需要一個步驟,總共要做 n 輪,所以時間複雜度是非常單純的 O(n)。

O(n) 的時間複雜度可說是工程師夢寐以求的美妙設計,然而,我們在前面就先警告過大家了,最壞狀況 (Worst Case) 才是我們計算時間複雜度時最關心的。

最壞狀況

排序法遇到的最壞狀況會是什麼呢?直觀地想,如果我們要將一個陣列由小到大排列,但陣列在我們排序前剛好是由大到小排列時,我們需要花最多的步驟數才能排列好。

再次回想插入排序法的兩個步驟,每一輪的「讀一個數字」我們同樣需要一個步驟,而「插入合適位置」我們在第一輪需要比較一個(把 61 跟 80 比),第二輪兩個(把 55 跟 80、61 比),第三輪三個,以此類推。

因此,在最壞狀況,我們在「插入合適位置」需要 1+2+3+…+n 個步驟,在「讀一個數字」需要總共 n 個步驟, 經過神祕計算後, 就會得到和選擇排序法相同的 n * (n+3) / 2 個步驟,其時間複雜度我們記為 O(n²)。

插入排序法在程式碼中的例子如下,同樣地,如果下方的程式碼對於讀者還有些吃力的話,可以先多多熟悉語法後回來複習即可。

Numbers = [41,33,17,80,61,5,55]​length = len(Numbers)for i in range(1, length):    position = i    value = Numbers[i]    while position > 0 and Numbers[position-1] > Numbers[position]:        Numbers[position], Numbers[position-1] = Numbers[position-1], Numbers[position]        position -= 1​print(Numbers)

小結

在這篇文章中,我們瞭解了經典的演算種類:排序法,並認識了最簡單的兩種排序法選擇排序法 (Selection Sort) 與插入排序法 (Insertion Sort)。同樣擁有 O(n) 的時間複雜度,但在分析插入排序法的時間複雜度時,我們分別分析了它的最佳狀況 (Best Case) 與最壞狀況 (Worst Case)。

為了讓讀者也有小小的練習機會,讀者也可以回頭分析選擇排序法的最佳狀況與最壞狀況的分別需要的步驟數,並試看看分析兩者有什麼差別。

在下一篇文章中,我們將從 O(n²) 的排序法加速到 O(n log n),認識更進階的排序法。