適合在支援 random access (e.g. array) 或 sequential access (e.g. linked list) 的資料結構上實作
Time complexity:
Time complexity:
想法:Divide and Conquer
每次都找出當前區段 ([l
, r
]) 最中間位置 (mid
) 的資料做比對
如果不是所要資料,因為資料已經保證為有序的,故如果位於 mid
的資料比目標小,表示目標在 mid
右邊 ([mid + 1
, r
]);如果位於 mid
的資料比目標大,表示目標在 mid
左邊 ([l
, mid - 1
])
不斷對半切,縮小搜尋範圍,最終找到目標
遞迴寫法:
從遞迴寫法我們可以很容易看出時間函數為
筆資料以 BST 找 x |
筆資料以 Binary search 找 x |
|
---|---|---|
Best Case | ||
Worst Case |
Time Complexity | Time Complexity | Time Complexity | Space Complexity | Stable | |
---|---|---|---|---|---|
Method | Best Case | Worst Case | Avg. Case | ||
Insertion | O | ||||
Selection | X | ||||
Bubble | O | ||||
Shell | X | ||||
Quick | to | X | |||
Merge | O | ||||
Heap | X |
想法:
我們從第二個元素 () 開始看到最後一個 ()
對於 ,其前 筆資料都已經是排序好的
我就找到 中哪裡插入 會使 為有序即可
例如
input:
[5, 3, 6, 2, 4]
: [3, 5, 6, 2, 4]
: [3, 5, 6, 2, 4]
: [2, 3, 5, 6, 4]
: [2, 3, 4, 5, 6]
我們預設是由小排到大的升序排列
Time Complexity:
Space Complexity:
無須額外空間,故
Stable:
最簡單的檢驗方式就是有兩個一樣的值出現在 arr
時,排序過後會不會改變這兩個值原本輸入時的先後順序
例如 [2, 5, 5*, 3]
你會發現 insertion sort 要抓取 5*
時(key = 5*
) 他是找在 5*
之前有沒有比它還大的,而 5
並沒有比它大
所以不會改變先後順序
我們可以這樣設計資料
最後結果 [2, 3, 5, 5*]
insertion sort 很適合資料少時使用
想法:
從頭看到倒數第二個
對於區間 找出其最小值 並
例如
input:
[5, 3, 6, 2, 4]
: [2, 3, 6, 5, 4]
: [2, 3, 6, 5, 4]
: [2, 3, 4, 5, 6]
: [2, 3, 4, 5, 6]
: [2, 3, 4, 5, 6]
Time Complexity:
都是
Space Complexity:
可以注意到,selection sort 的特點就是 的迭代過程中每回合頂多只會 swap 一次
對於大型紀錄之排序,該一特點能展現出來
不過 selection sort 是 unstable 的
結果: [2, 3, 5*, 5]
這是因為我們每次只在意小的值有沒有放到正確的位置上,並沒有在意 swap 過程中造成的先後順序打亂
想法:
由左而右兩相鄰元素互比
如果前者大於後者則 swap
Tip
最多只會有 回合,每個回合我們都會把極大值升到最後面去
Note
如果改成從右到左操作,那就是把極小值降到最前面去
Time Complexity:
Space Complexity:
Bubble sort 是 stable 的
因為我們是從前看到後,每次比大小,一樣的兩個值,在前面的不會被 swap 到後面去
最後結果 [2, 3, 5, 5*]
Shell sort,也是一種 in-place 的比較排序演算法
它可以被理解為 bubble sort 或 insertion sort 的泛化
這個方法的開始是將相距較遠的元素成對排序,然後逐步減少要比較的元素之間的間隔
通過從遠距離的元素開始,它可以比單純的鄰近交換更快地將某些錯位的元素移到正確的位置
Shellsort 的運行時間非常依賴於它使用的間隔序列 (gap sequence)
對於許多實用的變體,其時間複雜度的確定仍然是未解的問題
考試不怎麼考它
就算考也不太可能考你它的時間複雜度,正如上面講的,這不是什麼很基礎的問題
以下為 wiki 上的 pseudocode
想法:也是 Divide and Conquer
選擇一個元素作為 pivot ,決定其正確位置使得位於 pivot 左邊的資料都 它、位於 pivot 右邊的資料都 它
這樣的方式就把原始陣列拆兩邊了,再對兩邊也進行 quick sort
我們每次都挑最左邊的當作 pivot 並且用以下方式進行 partition:
[pv, _, _, _, _, _, _, _, _, _]
兩個指標 i
跟 j
分別從左右兩側開始選,他們分別代表到時候 pivot 的左右兩邊
他們的目標是要挑出不符合我們目標 「位於 pivot 左邊的資料都 它、位於 pivot 右邊的資料都 它」 的元素
[pv, _, i, _, _, _, _, _, _, j]
每次找到後就
(i
跟 j
找到的分別是對方那邊需要的元素, swap 後相對排序就正確了)
之後當 i
超過 j
時表示該結束了
[pv, _, _, _, _, j, i, _, _, _]
結束的最後一步是
其中 即 pivot
這樣就的確達成 「位於 pivot 左邊的資料都 它、位於 pivot 右邊的資料都 它」
最後我們再對 pivot 兩邊也 quick sort 即可
Time Complexity:
可以看到, pivot 選的如何影響了 quick sort 的表現
Space Complexity:
因為我們需要遞迴呼叫,所以是 到
Quick sort 很明顯,是 unstable 的
結果: [5, 5, 5*, 5, 5]
只差在怎麼寫 partition
之後就都一樣
這個版本反而會有個問題,那就是當資料都相同時,反而變成 worst case
解決辦法有兩種:
我們說過,怎麼選 pivot 會影響 quick sort 的表現
我們就算亂數隨機挑一個當 pivot ,worst case 也還是一樣
好的挑法例如 middle of three 或是 median of medians
以下介紹 median of medians 的想法:
這樣就保證 quick sort worst case 也是
Important
像這些 comparison-based 的想法,一般時間最快達
想法:Divide and Conquer
比較兩個數,小的擺前面,這樣的問題應該簡單到不行吧?
資料量不大的時,比較大小排順序對我們不是什麼很難的問題
Merge sort 的想法便是把原始資料拆到足夠小,小到我們很容易解決
再把結果合併回來,得出所求
Time Complexity:
都是
Space Complexity:
merge sort 並非 in-place 的
我們需要額外的空間先處理完子問題,最後才合併
需要
Merge sort 是 stable 的
結果: [2, 3, 5, 5*]
Important
任何 stable 的 sorting algorithm 寫的時候都要注意等號的處理!
寫錯就會失去 stable
建立 heap 來排序
Time Complexity:
都是
Tip
做 回合類似 的操作,每回合
故總共
Space Complexity:
直接拿原陣列操作,所以是 in-place 的,
那很顯然 heap sort is unstable
Counting sort 並不是用 comparison 的方式
它適用於我們對當前資料的值域是已知的情況
這種時候我們可以用找數字的方式來排順序
Time Complexity:
都是
因為 其實是已知的常數,所以 counting sort 是 linear time algorithm
Space Complexity:
而可想而知,我們抓數字時,相同的值是按照原本陣列的順序在抓的
所以是 stable 的