這週介紹一些基本的排序演算法
學習這些排序演算法,並不一定只為了解決排序問題
例如 merge sort 能應用在逆序數對問題,quick sort 的 partition 能快速找出數列中第 大的數字,counting sort 或 radix sort 能解決更複雜的排序問題
而且這些演算法的實現想法,非常值得模仿與吸收
將一堆元素由"給定規則"排成一順序,稱為排序 (sort)。
例如:
5, 6, 9, 8, 2
這五個元素由小到大排為 2, 5, 6, 8, 9
a, bc, aa, ay
這四個元素由字典順序排為 a, aa, ay, bc
如果兩個一樣的元素,在排序後會發生位置互相調換,則此排序法為不穩定排序 (unstable sort)。
如第一週所講的,演算法的優劣不僅僅只看複雜度,要看場合。
中文譯作泡泡排序
概念是陣列中每次有個泡泡(?)會從左到右將當前遇到的最大值帶至最右端
其中 i
為每次的最右端,
由於這次已將最大值放至最右端,下次的泡泡不需顧及這次的最右端
重覆行為至多 次,就能達到排序的效果
中文譯作插入排序
其精神為每次將選到的元素插入適當的位置
大部分的人玩撲克牌應該都這樣排的吧
i
每次選一個數字,接著藉由比較的方式將其往左依序交換至適當的位置
由於在輸入規模小的時候有不錯的時間效率,插入排序也作為 STL sort 與 stdlib qsort 的擴充
GCJ Qualification Round 2018 Trouble Sort
下面介紹的兩個是利用分而治之 (D&C) 精神所實作的演算法
通常透過 D&C 實作的演算法,其複雜度都看得見
中文譯作合併排序,根據以下實作方法有不少意想不到的應用
[3]
上圖流程跟程式碼的描述是有點出入的 (要怪筆者懶得做圖),不過足以幫助理解過程。
中文譯作快速排序,是十分優越的演算法。
若隨機挑一元素,然後將陣列中比此元素還大的元素們放到它右邊,小於等於的放到左邊
此元素在這樣的操作後,是否能被擺到最適合 (排序後) 的位置?這樣的操作稱為 partition,並且稱挑的數為 pivot:
p 的位置被換了,那麼來驗證一下這樣的猜想:
明顯的,p 進行 partition 後,他的位置到了排序後的位置
這挺直觀的,從小到大排序的概念就正是大數字往右邊,小數字往左邊
那麼對每一個數字做這樣的操作,是不是整個陣列就排序完了?
選擇的數字都被放到最適合的位置
基於這樣的想法,新的演算法就誕生了:
上面介紹的 partition 是取最右端的元素當 pivot。
若每次 partition 將其他元素都放到 pivot 的右邊,會使複雜度從 退化至 。
每次要搬動 個元素,共挑選 次 pivot
若改為隨機取一元素當 pivot 呢?複雜度將幾乎不可能退至 ,隨著資料規模越來越大,在平均來看,不太可能每次都使得上述構造的壞測資發生,有興趣可自行證明期望的複雜度。
事實上,只要在排序中用到"比較大小"的概念,複雜度不可能低於
mergesort 與 quicksort 的程式碼,在每次呼叫函式時區間表示方式不同,因為:
ZEROJUDGE a233 排序法~~~ 挑戰極限
LeetCode 215 Kth Largest Element in an Array
* LeetCode 4 Median of Two Sorted Arrays
counting sort,不經由比較元素大小就將元素排序好的演算法。
哦?不經由比較大小呢 Σ(゚д゚)
陣列中給定的元素將會有個明確的上界,且只適用於整數的排序
複雜度是線性 maxn
若每次給的 都很小,那
maxn
定的太大就顯得慘烈
所以該演算法還是要看準場合去使用。
NCKU OJ 7 藍的競程之旅–魔法藥
Sky 62 我最好的朋友[6]
UVa OJ 11462 Age Sort[7]
LeetCode 75 Sort Colors
建議將同樣第五週的 Graph Structures 讀過再讀此章,不然會違反拓樸排序規則(?)
有別於一般對數字或是文字做排序,這裡排序的是圖中點與邊的關係
對於排序後點的數列
有 從 到 沒有路徑
或是說, 在排序中要在 之前
代表 到 有條有向邊,但 到 不一定有邊
根據上圖以及拓樸排序規則,有好幾種排序後的數列:
5, 7, 3, 11, 8, 2, 9, 10
(從左到右,從上到下看)
3, 5, 7, 8, 11, 2, 9, 10
(優先看小的數字)
5, 7, 3, 8, 11, 10, 9, 2
(優先看最少的邊)
7, 5, 11, 3, 10, 8, 9, 2
(優先看大的數字)
5, 7, 11, 2, 3, 8, 9, 10
(從上到下,從左到右看)
3, 7, 8, 5, 11, 10, 2, 9
(隨便選)
而圖是有向無環圖若且唯若圖有拓樸排序
有向無環圖英文為 Directed Acyclic Graph (DAG)
也就是說想找圖中的拓樸排序,前提圖必須是個 DAG,若發現環則違反條件
根據規則,入度為 的點必定可放進拓樸排序數列中的最前端
入度 的點不會有邊連向他,所以不會有點比他排更前面 (除其他入度 的點)
當這些點排好後,接著是被他們所連向的入度為 的點,可依序排進拓樸排序數列
因為這些入度為 的點也沒有其他點能排到他們之前了
看看上述讓入度為 的點加入拓樸排序數列的動機
所以對於還不能排進拓樸排序數列的點 ,有若干個點 連向
但是一旦所有 都已進入拓樸排序, 就可以進入拓樸排序數列中
怎樣知道目前有幾個 已進入拓樸排序?
每次一個連向 的 進入拓樸排序, 的入度就可以減
反過來想,若是某點沒有任何後繼點,那麼可以直接把它加入拓樸排序數列的最尾端
依循此法,可以逆著把拓樸排序給造出來
也就是當某點的所有後繼點都已在拓樸排序中,那它就能加入拓樸排序數列
這些後繼點的其他子孫後繼點都得在拓樸排序數列中
因為此排序是逆著造出的,所以要將順序倒過來:
給定圖,判斷其是否有環
UVa OJ 124 Following Orders
CODEFORCES 510C Fox And Names
TIOJ 1092 A.跳格子遊戲
wikipedia/ Animated visualization of the quickselect algorithm ↩︎
wikipedia/ Animated visualization of the quicksort algorithm ↩︎
看得出來大家都先嘗試一波 MLE 才開始意識到此題不單純 ↩︎
UVa 測資頗弱, 陣列開滿用 STL sort 也能過 ↩︎