這幾天筆試了好幾回了,連續碰到一個關於常見排序算法穩定性判別的問題,每每仍是多選,對於我以及和我同樣拿不許的同窗可不是一個能輕易下結論的題目,固然若是你筆試以前已經記住了數據結構書上哪些是穩定的,哪些不是穩定的,作起來應該能夠輕鬆搞定。算法
本文是針對總是記不住這個或者想真正明白到底爲何是穩定或者不穩定的人準備的。 shell
首先,排序算法的穩定性你們應該都知道,通俗地講就是能保證排序前2個相等的數其在序列的先後位置順序和排序後它們兩個的先後位置順序相同。在簡單形式化一下,若是Ai = Aj, Ai原來在位置前,排序後Ai仍是要在Aj位置前。 數組
其次,說一下穩定性的好處。排序算法若是是穩定的,那麼從一個鍵上排序,而後再從另外一個鍵上排序,第一個鍵排序的結果能夠爲第二個鍵排序所用。基數排序就 是這樣,先按低位排序,逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,若是排序算法穩定,對基於比較的排序算法而言,元素交換 的次數可能會少一些(我的感受,沒有證明)。 數據結構
回到主題,如今分析一下常見的排序算法的穩定性,每一個都給出簡單的理由。 ui
(1)冒泡排序 spa
冒泡排序就是把小的元素往前調或者把大的元素日後調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。因此,若是兩個元素相等,我想你是不會再無 聊地把他們倆交換一下的;若是兩個相等的元素沒有相鄰,那麼即便經過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,因此相同元素的先後順序並無改 變,因此冒泡排序是一種穩定排序算法。 設計
(2)選擇排序 排序
選擇排序是給每一個位置選擇當前元素最小的,好比給第一個位置選擇最小的,在剩餘元素裏面給第二個元素選擇第二小的,依次類推,直到第n-1個元素,第n個 元素不用選擇了,由於只剩下它一個最大的元素了。那麼,在一趟選擇,若是當前元素比一個元素小,而該小的元素又出如今一個和當前元素相等的元素後面,那麼 交換後穩定性就被破壞了。比較拗口,舉個例子,序列5 8 5 2 9, 咱們知道第一遍選擇第1個元素5會和2交換,那麼原序列中2個5的相對先後順序就被破壞了,因此選擇排序不是一個穩定的排序算法。 遞歸
(3)插入排序
插入排序是在一個已經有序的小序列的基礎上,一次插入一個元素。固然,剛開始這個有序的小序列只有1個元素,就是第一個元素。比較是從有序序列的末尾開 始,也就是想要插入的元素和已經有序的最大者開始比起,若是比它大則直接插入在其後面,不然一直往前找直到找到它該插入的位置。若是遇見一個和插入元素相 等的,那麼插入元素把想插入的元素放在相等元素的後面。因此,相等元素的先後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,因此插入排序是穩 定的。 內存
(4)快速排序
快速排序有兩個方向,左邊的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)歸併排序
歸併排序是把序列遞歸地分紅短序列,遞歸出口是短序列只有1個元素(認爲直接有序)或者2個序列(1次比較和交換),而後把各個有序的段序列合併成一個有 序的長序列,不斷合併直到原序列所有排好序。能夠發現,在1個或2個元素時,1個元素不會交換,2個元素若是大小相等也沒有人故意交換,這不會破壞穩定 性。那麼,在短的有序序列合併的過程當中,穩定是是否受到破壞?沒有,合併過程當中咱們能夠保證若是兩個當前元素相等時,咱們把處在前面的序列的元素保存在結 果序列的前面,這樣就保證了穩定性。因此,歸併排序也是穩定的排序算法。
(6)基數排序
基數排序是按照低位先排序,而後收集;再按照高位排序,而後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優 先級排序,最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,因此其是穩定的排序算法。
(7)希爾排序(shell)
希爾排序是按照不一樣步長對元素進行插入排序,當剛開始元素很無序的時候,步長最大,因此插入排序的元素個數不多,速度很快;當元素基本有序了,步長很小, 插入排序對於有序的序列效率很高。因此,希爾排序的時間複雜度會比o(n^2)好一些。因爲屢次插入排序,咱們知道一次插入排序是穩定的,不會改變相同元 素的相對順序,但在不一樣的插入排序過程當中,相同的元素可能在各自的插入排序中移動,最後其穩定性就會被打亂,因此shell排序是不穩定的。
(8)堆排序
咱們知道堆的結構是節點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個相同的元素之間的穩定性就被破壞了。因此,堆排序不是穩定的排序算法
1 快速排序(QuickSort)
快速排序是一個就地排序,分而治之,大規模遞歸的算法。從本質上來講,它是歸併排序的就地版本。快速排序能夠由下面四步組成。
(1) 若是很少於1個數據,直接返回。
(2) 通常選擇序列最左邊的值做爲支點數據。
(3) 將序列分紅2部分,一部分都大於支點數據,另一部分都小於支點數據。
(4) 對兩邊利用遞歸排序數列。
快速排序比大部分排序算法都要快。儘管咱們能夠在某些特殊的狀況下寫出比快速排序快的算法,可是就一般狀況而言,沒有比它更快的了。快速排序是遞歸的,對於內存很是有限的機器來講,它不是一個好的選擇。
2 歸併排序(MergeSort)
歸併排序先分解要排序的序列,從1分紅2,2分紅4,依次分解,當分解到只有1個一組的時候,就能夠排序這些分組,而後依次合併回原來的序列中,這樣就能夠排序全部數據。合併排序比堆排序稍微快一點,可是須要比堆排序多一倍的內存空間,由於它須要一個額外的數組。
3 堆排序(HeapSort)
堆排序適合於數據量很是大的場合(百萬數據)。
堆排序不須要大量的遞歸或者多維的暫存數組。這對於數據量很是巨大的序列是合適的。好比超過數百萬條記錄,由於快速排序,歸併排序都使用遞歸來設計算法,在數據量很是大的時候,可能會發生堆棧溢出錯誤。
堆排序會將全部的數據建成一個堆,最大的數據在堆頂,而後將堆頂數據和序列的最後一個數據交換。接下來再次重建堆,交換數據,依次下去,就能夠排序全部的數據。
4 Shell排序(ShellSort)
Shell排序經過將數據分紅不一樣的組,先對每一組進行排序,而後再對全部的元素進行一次插入排序,以減小數據交換和移動的次數。平均效率是O(nlogn)。其中分組的合理性會對算法產生重要的影響。如今多用D.E.Knuth的分組方法。
Shell排序比冒泡排序快5倍,比插入排序大體快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢不少。可是它相對比較簡單,它適合於數據量在5000如下而且速度並非特別重要的場合。它對於數據量較小的數列重複排序是很是好的。
5 插入排序(InsertSort)
插入排序經過把序列中的值插入一個已經排序好的序列中,直到該序列的結束。插入排序是對冒泡排序的改進。它比冒泡排序快2倍。通常不用在數據大於1000的場合下使用插入排序,或者重複排序超過200數據項的序列。
6 冒泡排序(BubbleSort)
冒泡排序是最慢的排序算法。在實際運用中它是效率最低的算法。它經過一趟又一趟地比較數組中的每個元素,使較大的數據下沉,較小的數據上升。它是O(n^2)的算法。
7 交換排序(ExchangeSort)和選擇排序(SelectSort)
這兩種排序方法都是交換方法的排序算法,效率都是 O(n2)。在實際應用中處於和冒泡排序基本相同的地位。它們只是排序算法發展的初級階段,在實際中使用較少。
8 基數排序(RadixSort)
基數排序和一般的排序算法並不走一樣的路線。它是一種比較新穎的算法,可是它只能用於 整數的排序,若是咱們要把一樣的辦法運用到浮點數上,咱們必須瞭解浮點數的存儲格式,並經過特殊的方式將浮點數映射到整數上,而後再映射回去,這是很是麻 煩的事情,所以,它的使用一樣也很少。並且,最重要的是,這樣算法也須要較多的存儲空間。
9 總結
下面是一個總的表格,大體總結了咱們常見的全部的排序算法的特色。
排序法 | 平均時間 | 最差情形 | 穩定度 | 額外空間 | 備註 |
冒泡 | O(n2) | O(n2) | 穩定 | O(1) | n小時較好 |
交換 | O(n2) | O(n2) | 不穩定 | O(1) | n小時較好 |
選擇 | O(n2) | O(n2) | 不穩定 | O(1) | n小時較好 |
插入 | O(n2) | O(n2) | 穩定 | O(1) | 大部分已排序時較好 |
基數 | O(logRB) | O(logRB) | 穩定 | O(n) | B是真數(0-9), R是基數(個十百) |
Shell | O(nlogn) | O(ns) 1<s<2 | 不穩定 | O(1) | s是所選分組 |
快速 | O(nlogn) | O(n2) | 不穩定 | O(nlogn) | n大時較好 |
歸併 | O(nlogn) | O(nlogn) | 穩定 | O(1) | n大時較好 |
堆 | O(nlogn) | O(nlogn) | 不穩定 | O(1) | n大時較好 |
選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法,冒泡排序、插入排序、歸併排序和基數排序是穩定的排序算法。 |