什么是排序穩定性?
穩定性就是指對於兩個關鍵字相等的記錄,它們在序列中的相對位置,在排序之前和排序之后沒有發生改變。通俗地講就是有兩個關鍵字相等的數據A、B,排序前,A的位置是 i ,B的位置是 j,此時 i < j,則如果在排序后A的位置還是在B之前,那么稱它是穩定的。
它的好處是,如果排序算法都是穩定的,那么第一個排序結果可以為另一個排序所用,也就是說穩定排序可以用於兩個關鍵字的排序,比如,現在要根據英語成績排一下大家的成績,然后在根據數學成績排一下大家的成績,如果用的是穩定排序的話,就可以保證數學成績相同的人里英語分高的人排在更前面。
而且,關鍵字相同的記錄可能其他數據會不一致,這時交換它們就會引起問題。舉個例子,如果銀行有兩個用戶,一個是 VIP 用戶,另一個是普通用戶,他們都有相同的資產,如果這時候交換了他們的資產,讓普通用戶擁有了 VIP屬性的資產,就可能會影響銀行 VIP 與 普通用戶的秩序。
首先給出一張在前一個博客“時間復雜度入門理解”中出現過的一副圖,給出相關排序的穩定性:
接下來,將解釋圖中某些排序是不穩定的的原因。
I)為什么希爾排序不穩定?
A:首先要知道 Shell 排序是基於插入排序的優化排序,插入排序每次本身只能插入一位數據,希爾排序按照步長對多個元素進行插入排序。我們知道一次插入排序是穩定的,但是同時對多組數據進行插入排序,可能不穩定了,舉例:
有這樣一組數:[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ]
以第一輪是步長為5的希爾排序為例:
排序前:
第一輪排序后 :
如果把第三個元素 94 換成 13,剛開始第一個 13 在 序列第一個位置,第二個 13 (也就是圖中的94)在序列第三個位置,經過希爾排序第一輪后,很明顯第一個 13 反而排到第二個 13 后面了,這就是同時進行多次插入排序所導致的不穩定性。
II)為什么快速排序不穩定?
A:快排的思想是先設一個中樞元素(也稱是基准數,對照用),對兩遍進行排列,需要滿足左邊的元素都比中樞元素小,右邊元素都比中樞元素大,隨后對左和右的子序列也進行這樣的操作(分治)。而該排序不穩定的關鍵就是中樞元素在調換到序列中間的時候,會破壞了原來位於中央的元素的穩定性,比如有一個序列:
6 1 2 7 9 6 4 5 10 8 ,我們定義 6 為中樞元素
進行第一輪快排后,得 6 1 2 5 4 6 9 7 10 8
這樣操作就會把兩個關鍵字同為 6 的記錄交換了,算法的穩定性被破壞。所以快排是一個不穩定的排序算法,不穩定發生在中樞元素的交換時刻。
III)為什么直接選擇排序是不穩定的?
A:直接選擇排序的操作是這樣:先在未排序的序列中選擇最小的元素(或最大的元素),把它與第一個元素交換,放在第一個位置,再在剩余未排序序列中選擇第二小的,與第二個元素交換,放在第二個位置...以此類推,直到所有序列排序完畢。但是,這樣直接讓最小元素與第 i 個位置上的元素進行交換,會破壞第一個元素的穩定性,舉個例子,有一個序列如下:
33 68 46 33 25 80 19 12
第一輪排序后:
12 68 46 33 25 80 19 33
顯然,第一個 33 相對於第二個 33的位置變化了,算法是不穩定的。
IV)為什么堆排序是不穩定的?
A:要說堆的結構得先說完全二叉樹的結構。
完全二叉樹的結構要求:
完全二叉樹是一顆深度為 k ,結點總數為 n (n ≤ 2^k - 1)的二叉樹,除了第 k 層外,其他第 2~(k-1) 層都達到最大結點數,第 k 層從右向左缺失若干個結點,那么這棵樹為完全二叉樹。[圖例在這篇博客里]
完全二叉樹被堆所用的性質:如果一結點的左子結點為 i ,右子結點為 i + 1,子結點 n 的父結點的位置在 i/2 (若不為整數,則向下取整) 。
接下來說堆,堆分為大頂堆和小頂堆,大頂堆要求父結點要大於它的兩個子結點,小頂堆要求父結點要小於它的兩個子結點。而堆排序就是移除位於第一個數據的根結點,並做堆調整的遞歸運算。它涉及兩個關鍵問題:(1)如何由無序序列建“初始堆”? (2)輸出堆頂后,如何進行堆的篩選(調整)?
建立堆的操作如下:
(1)以給定的無序序列為基礎建立一個完全二叉樹
(2)由於葉子結點本身肯定滿足堆結構,那么從第一個非葉子結點(比如第一個葉子結點的父結點)開始向上調整,重復(2)步驟
當然你也可以定一個頂點為堆,然后往這個堆里添加元素,但是這個方式建堆的時間復雜度是 O(nlgn),前一個方法建堆的時間復雜度是 O(n)
輸出堆頂后,此時需要維護堆,操作如下:
(1)堆頂與堆尾交換並刪除堆尾,被刪除的堆尾的元素就是輸出過的元素
(2)把當前堆頂向下調整,直到滿足構成堆的條件,重復(2)步驟
很顯然,在建立堆的調整步驟里,由於關鍵字相同的兩個記錄位置並不會被調換,所以建堆的時候是穩定的。但是,在堆頂與堆尾交換的時候兩個相等的記錄在序列中的相對位置就可能發生改變,這就影響其穩定性了。
注意
排序算法是否為穩定的是由具體算法決定的,不穩定的算法在某種條件下可以變為穩定的算法,而穩定的算法在某種條件下也可以變為不穩定的算法。比如冒泡,若讓交換的條件改成 r[j] >= r[j+1],兩個相等的記錄就會交換位置,從而變成不穩定的算法。
有很多辦法可以將任意排序算法變成穩定的,但是,往往需要額外的時間或者空間。所以我們在討論的排序算法穩定性的時候,往往是在不需要額外的時間或空間的前提下才進行討論的。