經典排序演算法原理及穩定性判斷
1. 氣泡排序
原理:
將
n
個記錄看作按縱向排列,每趟排序時自下至上對每對相鄰記錄進行比較,若次序不符合要求(逆序)就交換。每趟排序結束時都能使排序範圍內關鍵字最小的記錄象一個氣泡一樣升到表上端的對應位置,整個排序過程共進行n-1
趟,依次將關鍵字最小、次小、第三小…的各個記錄“冒到”表的第一個、第二個、第三個… 位置上。
例子
例子為從小到大排序,
原始待排序陣列| 6 | 2 | 4 | 1 | 5 | 9 |
第一趟排序(外迴圈)
第一次兩兩比較6 > 2交換(內迴圈)
交換前狀態| 6 | 2 | 4 | 1 | 5 | 9 |
交換後狀態| 2 | 6 | 4 | 1 | 5 | 9 |
第二次兩兩比較,6 > 4交換
交換前狀態| 2 | 6 | 4 | 1 | 5 | 9 |
交換後狀態| 2 | 4 | 6 | 1 | 5 | 9 |
第三次兩兩比較,6 > 1交換
交換前狀態| 2 | 4 | 6 | 1 | 5 | 9 |
交換後狀態| 2 | 4 | 1 | 6 | 5 | 9 |
第四次兩兩比較,6 > 5交換
交換前狀態| 2 | 4 | 1 | 6 | 5 | 9 |
交換後狀態| 2 | 4 | 1 | 5 | 6 | 9 |
第五次兩兩比較,6 < 9不交換
交換前狀態| 2 | 4 | 1 | 5 | 6 | 9 |
交換後狀態| 2 | 4 | 1 | 5 | 6 | 9 |
第二趟排序(外迴圈)
第一次兩兩比較2 < 4不交換
交換前狀態| 2 | 4 | 1 | 5 | 6 | 9 |
交換後狀態| 2 | 4 | 1 | 5 | 6 | 9 |
第二次兩兩比較,4 > 1交換
交換前狀態| 2 | 4 | 1 | 5 | 6 | 9 |
交換後狀態| 2 | 1 | 4 | 5 | 6 | 9 |
第三次兩兩比較,4 < 5不交換
交換前狀態| 2 | 1 | 4 | 5 | 6 | 9 |
交換後狀態| 2 | 1 | 4 | 5 | 6 | 9 |
第四次兩兩比較,5 < 6不交換
交換前狀態| 2 | 1 | 4 | 5 | 6 | 9 |
交換後狀態| 2 | 1 | 4 | 5 | 6 | 9 |
第三趟排序(外迴圈)
第一次兩兩比較2 > 1交換
交換後狀態| 2 | 1 | 4 | 5 | 6 | 9 |
交換後狀態| 1 | 2 | 4 | 5 | 6 | 9 |
第二次兩兩比較,2 < 4不交換
交換後狀態| 1 | 2 | 4 | 5 | 6 | 9 |
交換後狀態| 1 | 2 | 4 | 5 | 6 | 9 |
第三次兩兩比較,4 < 5不交換
交換後狀態| 1 | 2 | 4 | 5 | 6 | 9 |
交換後狀態| 1 | 2 | 4 | 5 | 6 | 9 |
第四趟排序(外迴圈)無交換
第五趟排序(外迴圈)無交換
排序完畢,輸出最終結果1 2 4 5 6 9
穩定性判斷:
氣泡排序就是把小的元素往前調或者把大的元素往後調。比較是相鄰的兩個
元素比較,交換也發生在這兩個元素之間。
所以,如果兩個元素相等,我想你是不會再無聊地把他們倆交換一下的;如果兩個相等的元素沒有相鄰,那麼即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前後順序並沒有改變.
所以氣泡排序是一種穩定排序演算法。
2. 選擇排序
原理:
顧名思意,就是直接從待排序數組裡選擇一個最小(或最大)的數字,每次都拿一個最小數字出來,順序放入新陣列,直到全部拿完。
再簡單點,對著一群陣列說,你們誰最小出列,站到最後邊,然後繼續對剩餘的無序陣列說,你們誰最小出列,站到最後邊,再繼續剛才的操作,一直到最後一個,繼續站到最後邊,現在陣列有序了,從小到大。
例子
先說看每步的狀態變化,後邊介紹細節,現有無序陣列[6 2 4 1 5 9]
第一趟找到最小數1,放到最前邊(與首位數字交換)
交換前:| 6 | 2 | 4 | 1 | 5 | 9 |
交換後:| 1 | 2 | 4 | 6 | 5 | 9 |
第二趟找到餘下數字[2 4 6 5 9]裡的最小數2,與當前陣列的首位數字進行交換,實際沒有交換,本來就在首位
交換前:| 1 | 2 | 4 | 6 | 5 | 9 |
交換後:| 1 | 2 | 4 | 6 | 5 | 9 |
第三趟繼續找到剩餘[4 6 5 9]數字裡的最小數4,實際沒有交換,4待首位置無須交換
第四趟從剩餘的[6 5 9]裡找到最小數5,與首位數字6交換位置
交換前:| 1 | 2 | 4 | 6 | 5 | 9 |
交換後:| 1 | 2 | 4 | 5 | 6 | 9 |
第五趟從剩餘的[6 9]裡找到最小數6,發現它待在正確的位置,沒有交換
排序完畢輸出正確結果[1 2 4 5 6 9]
下面是找出最小數的演算法:
第一趟找到最小數1的細節
當前陣列是| 6 | 2 | 4 | 1 | 5 | 9 |
先把6取出來,讓它扮演最小數
當前最小數6與其它數一一進行比較,發現更小數就交換角色
當前最小數6與2比較,發現更小數,交換角色,此時最小數是2,接下來2與剩餘數字比較
當前最小數2與4比較,不動
當前最小數2與1比較,發現更小數,交換角色,此時最小數是1,接下來1與剩餘數字比較
當前最小數1與5比較,不動
當前最小數1與9比較,不動,到達末尾
當前最小數1與當前首位數字進行位置交換,如下所示
交換前:| 6 | 2 | 4 | 1 | 5 | 9 |
交換後:| 1 | 2 | 4 | 6 | 5 | 9 |
完成一趟排序,其餘步驟類似
穩定性判斷:
選擇排序是給每個位置選擇當前元素最小的,比如給第一個位置選擇最小的,在剩餘元素裡面給第二個元素選擇第二小的,依次類推,直到第n-1個元素,第n個元素不用選擇了,因為只剩下它一個最大的元素了。那麼,在一趟選擇,如果當前元素比一個元素小,而該小的元素又出現在一個和當前元素相等的元素後面,那麼交換後穩定性就被破壞了。
比較拗口,舉個例子,序列5 8 5 2 9,我們知道第一遍選擇第1個元素5會和2交換,那麼原序列中2個5的相對前後順序就被破壞了,所以選擇排序不是一個穩定的排序演算法。
3. 插入排序
原理:
插入排序就是每一步都將一個待排資料按其大小插入到已經排序的資料中的適當位置,直到全部插入完畢。
插入排序方法分直接插入排序和折半插入排序兩種。
折半插入排序基本思想和直接插入排序一樣,區別在於尋找插入位置的方法不同,折半插入排序採用折半查詢法來尋找插入位置。折半查詢法只能對有序的序列使用。基本思想就是查詢插入位置的時候,把序列分成兩半(選擇一箇中間數mid),如果帶插入資料大於mid則到右半部分序列去在進行折半查詢;反之,則到左半部分序列去折半查詢。
例子
設陣列為a[0…n-1]。
1. 初始時,a[0]自成1個有序區,無序區為a[1..n-1]。令i=1
2. 將a[i]併入當前的有序區a[0…i-1]中形成a[0…i]的有序區間。
3. i++並重復第二步直到i==n-1。排序完成。
穩定性判斷:
插入排序是在一個已經有序的小序列的基礎上,一次插入一個元素。當然,剛開始這個有序的小序列只有1個元素,就是第一個元素。
比較是從有序序列的末尾開始,也就是想要插入的元素和已經有序的最大者開始比起,如果比它大則直接插入在其後面,否則一直往前找直到找到它該插入的位置。
如果碰見一個和插入元素相等的,那麼插入元素把想插入的元素放在相等元素的後面。所以,相等元素的前後順序沒有改變,從原無序序列出去的順序就是排好序後的順序。
所以插入排序是穩定的。
4. 快速排序
原理:
通過一趟掃描將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列
例子
如無序陣列[6 2 4 1 5 9]
a),先把第一項[6]取出來,
用[6]依次與其餘項進行比較,
如果比[6]小就放[6]前邊,2 4 1 5都比[6]小,所以全部放到[6]前邊
如果比[6]大就放[6]後邊,9比[6]大,放到[6]後邊,//6出列後大喝一聲,比我小的站前邊,比我大的站後邊,行動吧!霸氣十足~
一趟排完後變成下邊這樣:
排序前 6 2 4 1 5 9
排序後 2 4 1 5 6 9
b),對前半拉[2 4 1 5]繼續進行快速排序
重複步驟a)後變成下邊這樣:
排序前 2 4 1 5
排序後 1 2 4 5
前半拉排序完成,總的排序也完成:
排序前:[6 2 4 1 5 9]
排序後:[1 2 4 5 6 9]
排序結束
穩定性判斷:
快速排序有兩個方向,左邊的i下標一直往右走,當a[i] <= a[center_index],
其中center_index是中樞元素的陣列下標,一般取為陣列第0個元素。而右邊的j下標一直往左走,當a[j] > a[center_index]。如果i和j都走不動了,i <= j, 交換a[i]和a[j]。
重複上面的過程,直到i>j。交換a[j]和a[center_index],完成一趟快速排序。在中樞元素和a[j]交換的時候,很有可能把前面的元素的穩定性打亂,比如序列為 5 3 3 4 3 8 9 10 11,現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂。
所以快速排序是一個不穩定的排序演算法,不穩定發生在中樞元素和a[j] 交換的時刻
。
5. 歸併排序
原理:
把原始陣列分成若干子陣列,對每一個子陣列進行排序,繼續把子陣列與子數組合並,合併後仍然有序,直到全部合併完,形成有序的陣列。
例子
無序陣列[6 2 4 1 5 9]
先看一下每個步驟下的狀態,完了再看合併細節
第一步 [6 2 4 1 5 9]原始狀態
第二步 [2 6] [1 4] [5 9]兩兩合併排序,排序細節後邊介紹
第三步 [1 2 4 6] [5 9]繼續兩組兩組合並
第四步 [1 2 4 5 6 9]合併完畢,排序完畢
輸出結果[1 2 4 5 6 9]
合併細節
詳細介紹第二步到第三步的過程,其餘類似
第二步:[2 6] [1 4] [5 9]
兩兩合併,其實僅合併[2 6] [1 4],所以[5 9]不管它,
原始狀態
第一個陣列[2 6]
第二個陣列[1 4]
--------------------
第三個陣列[...]
第1步,順序從第一,第二個數組裡取出一個數字:2和1
比較大小後將小的放入第三個陣列,此時變成下邊這樣
第一個陣列[2 6]
第二個陣列[4]
--------------------
第三個陣列[1]
第2步,繼續剛才的步驟,順序從第一,第二個數組裡取資料,2和4,
同樣的比較大小後將小的放入第三個陣列,此時狀態如下
第一個陣列[6]
第二個陣列[4]
--------------------
第三個陣列[1 2]
第3步,再重複前邊的步驟變成,將較小的4放入第三個陣列後變成如下狀態
第一個陣列[6]
第二個陣列[...]
--------------------
第三個陣列[1 2 4]
第4步,最後將6放入,排序完畢
第一個陣列[...]
第二個陣列[...]
--------------------
第三個陣列[1 2 4 6]
[ 1 2 4 6 ]與[ 5 9 ]的合併過程與上邊一樣,不再分解
穩定性判斷:
歸併排序是把序列遞迴地分成短序列,遞迴出口是短序列只有1個元素(認為直接
有序)或者2個序列(1次比較和交換),然後把各個有序的段序列合併成一個有序的長序列,不斷合併直到原序列全部排好序。可以發現,在1個或2個元素時,1個元素不會交換,2個元素如果大小相等也沒有人故意交換,這不會破壞穩定性。那麼,在短的有序序列合併的過程中,穩定是是否受到破壞?沒有,合併過程中我們可以保證如果兩個當前元素相等時,我們把處在前面的序列的元素儲存在結果序列的前面,這樣就保證了穩定性。
所以,歸併排序也是穩定的排序演算法。
6. 基數排序
原理:
一個元素有多個關鍵字,定義排序後的“有序”是指依次比較這些關鍵字,不同的直接按其大小關係,相同的比較後續的關鍵字,例如字串與數字。然後,這些關鍵字都有一些範圍。依次選取這些關鍵字作為依據,進行依次分類,這樣,類別之間就有了相對的大小關係。然後,對每個類別進行相同的操作,直至所有關鍵字都被比較過為止。
例子
待排序陣列[62,14,59,88,16]簡單點五個數字
分配10個桶,桶編號為0-9,以個位數數字為桶編號依次入桶,變成下邊這樣
| 0 | 0 | 62 | 0 | 14 | 0 | 16 | 0 | 88 | 59 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |桶編號
將桶裡的數字順序取出來,
輸出結果:[62,14,16,88,59]
再次入桶,不過這次以十位數的數字為準,進入相應的桶,變成下邊這樣:
由於前邊做了個位數的排序,所以當十位數相等時,個位數字是由小到大的順序入桶的,就是說,入完桶還是有序
| 0 | 14,16 | 0 | 0 | 0 | 59 | 62 | 0 | 88 | 0 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |桶編號
因為沒有大過100的數字,沒有百位數,所以到這排序完畢,順序取出即可
最後輸出結果:[14,16,59,62,88]
穩定性判斷:
基數排序是按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類
推,直到最高位。有時候有些屬性是有優先順序順序的,先按低優先順序排序,再按高優先順序排序,最後的次序就是高優先順序高的在前,高優先順序相同的低優先順序高的在前。基數排序基於分別排序,分別收集。
所以其是穩定的排序演算法。
7. 希爾排序(shell)
原理:
希爾排序的實質就是分組插入排序,該方法又稱縮小增量排序,因DL.Shell於1959年提出而得名。
該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因為直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。
例子
以n=10的一個數組49, 38, 65, 97, 26, 13, 27, 49, 55, 4為例
第一次 gap = 10 / 2 = 5
49 | 38 | 65 | 97 | 26 | 13 | 27 | 49 | 55 | 4 |
---|---|---|---|---|---|---|---|---|---|
1A | 1B | ||||||||
2A | 2B | ||||||||
3A | 3B | ||||||||
4A | 4B | ||||||||
5A | 5B |
1A,1B,2A,2B等為分組標記,數字相同的表示在同一組,大寫字母表示是該組的第幾個元素, 每次對同一組的資料進行直接插入排序。即分成了五組(49, 13) (38, 27) (65, 49) (97, 55) (26, 4)這樣每組排序後就變成了(13, 49) (27, 38) (49, 65) (55, 97) (4, 26),下同。
第二次 gap = 5 / 2 = 2
排序後
13 | 27 | 49 | 55 | 4 | 49 | 38 | 65 | 97 | 26 |
---|---|---|---|---|---|---|---|---|---|
1A | 1B | 1C | 1D | 1E | |||||
2A | 2B | 2C | 2D | 2E |
第三次 gap = 2 / 2 = 1
4 | 26 | 13 | 27 | 38 | 49 | 49 | 55 | 97 | 65 |
---|---|---|---|---|---|---|---|---|---|
1A | 1B | 1C | 1D | 1E | 1F | 1G | 1H | 1I | 1J |
第四次 gap = 1 / 2 = 0 排序完成得到陣列:
4 | 13 | 26 | 27 | 38 | 49 | 49 | 55 | 65 | 97 |
穩定性判斷:
希爾排序是按照不同步長對元素進行插入排序,當剛開始元素很無序的時候,步
長最大,所以插入排序的元素個數很少,速度很快;當元素基本有序了,步長很小,插入
排序對於有序的序列效率很高。所以,希爾排序的時間複雜度會比o(n^2)好一些。由於多
次插入排序,我們知道一次插入排序是穩定的,不會改變相同元素的相對順序,但在不同
的插入排序過程中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂
。
所以shell排序是不穩定的。
8. 堆排序
原理:
堆分為大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在陣列的非降序排序中,需要使用的就是大根堆,因為根據大根堆的要求可知,最大的值一定在堆頂。
既然是堆排序,自然需要先建立一個堆,而建堆的核心內容是調整堆,使二叉樹滿足堆的定義(每個節點的值都不大於其父節點的值)。調堆的過程應該從最後一個非葉子節點開始
例子
穩定性判斷:
我們知道堆的結構是節點i的孩子為2*i和2*i+1節點,大頂堆要求父節點大於等於
其2個子節點,小頂堆要求父節點小於等於其2個子節點。在一個長為n 的序列,堆排序
的過程是從第n/2開始和其子節點共3個值選擇最大(大頂堆)或者最小(小頂堆),這3個元素
之間的選擇當然不會破壞穩定性。但當為n /2-1, n/2-2, ...1這些個父節點選擇元素時
,就會破壞穩定性。有可能第n/2個父節點交換把後面一個元素交換過去了,而第n/2-1個
父節點把後面一個相同的元素沒有交換,那麼這2個相同的元素之間的穩定性就被破壞了
。
所以堆排序不是穩定演算法。
常用的排序演算法的時間複雜度和空間複雜度
排序演算法 | 最差時間分析 | 平均時間複雜度 | 穩定性 | 空間複雜度 |
---|---|---|---|---|
氣泡排序 | 穩定 | |||
快速排序 | 不穩定 | |||
選擇排序 | 穩定 | |||
二叉樹排序 | 不一定 | |||
插入排序 | 穩定 | |||
堆排序 | 不穩定 | |||
希爾排序 | 不穩定 |