1.基本概念
2.時空復雜度
3.穩定性
4.使用情況分析
排序算法總結(C語言版)已介紹排序算法的基本思想和C語言實現,本文只介紹時空復雜度和穩定性。
1.基本概念
時間復雜度:
一個算法花費的時間與算法中語句的執行次數成正比例,哪個算法中語句執行次數多,它花費時間就多。一個算法的語句執行次數稱為語句頻度或時間頻度。記為T(n)。n稱為問題的規模,當n不斷變化時,時間頻度T(n)也會不斷變化。但有時我們想知道它變化時呈現什么規律,為此,引入時間復雜度概念。若有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值為不等於零的常數,則稱f(n)是T(n)的同數量級函數。記作T(n)=O(f(n)),稱O(f(n))為算法的漸進時間復雜度,簡稱時間復雜度。
按數量級遞增排列,常見的時間復雜度有:常數階O(1),對數階O(log2n),線性階O(n),線性對數階O(nlog2n),平方階O(n2),立方階O(n3),...,k次方階O(nk),指數階O(2n)。隨着問題規模n的不斷增大,上述時間復雜度不斷增大,算法的執行效率越低。
在各種不同算法中,若算法中語句執行次數為一個常數,則時間復雜度為O(1)。
空間復雜度:
空間復雜度是對一個算法在運行過程中臨時占用存儲空間大小的量度。算法在運行過程中臨時占用的存儲空間隨算法的不同而異,有的算法只需要占用少量的臨時工作單元,而且不隨問題規模的大小而改變,我們稱這種算法是"就地"進行的,是節省存儲的算法;有的算法需要占用的臨時工作單元數與解決問題的規模n有關,它隨着n的增大而增大,當n較大時,將占用較多的存儲單元。
當一個算法的空間復雜度為一個常量,即不隨被處理數據量n的大小而改變時,可表示為O(1)。
穩定性:
穩定性,是指假設待排序記錄中有兩個相同的元素,它們排序前后的相對位置是否變化。主要用在排序時有多個排序規則的情況下。
如何對於不穩定的算法進行改進,使之穩定?其實很簡單,只需要在每個記錄上加一個index,表示初始時的數組索引,當根據不穩定的算法排好序后,對於相同的元素再根據index排序即可。
2.時空復雜度
排序方法 |
時間復雜度 |
空間復雜度 |
穩定性 |
|||
平均情況 |
最好情況 |
最壞情況 |
||||
插入排序 |
直接插入 |
O(n2) |
O(n) |
O(n2) |
O(1) |
穩定 |
Shell排序 |
O(n1~2) |
O(nlog2n) |
O(n2) |
O(1) |
不穩定 |
|
選擇排序 |
直接選擇 |
O(n2) |
O(n2) |
O(n2) |
O(1) |
不穩定 |
堆排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
O(1) |
不穩定 |
|
交換排序 |
冒泡排序 |
O(n2) |
O(n) |
O(n2) |
O(1) |
穩定 |
快速排序 |
O(nlog2n) |
O(nlog2n) |
O(n2) |
O(log2n) |
不穩定 |
|
歸並排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
O(n) |
穩定 |
|
基數排序 |
O(d(n+r)) |
O(d(n+r)) |
O(d(n+r)) |
O(n+rd) |
穩定 |
|
注:1.希爾排序的時間復雜度和增量的選擇有關。 2.基數排序的復雜度中,r代表關鍵字的基數,d代表長度,n表示關鍵字的個數。 |
下述討論中假設排序為升序。
直接插入排序:
最好的情況:序列已經是升序排列,在這種情況下,需要比較(n-1)次即可。O(n)
最壞的情況:序列是降序排列,此時需要比較n(n-1)/2次。O(n2)
希爾排序:
希爾算法在最壞的情況下O(n2)和平均情況下O(n1~2)執行效率相差不是很多,與此同時快速排序在最壞的情況下執行的效率會非常差。
希爾排序是按照不同步長對元素進行插入排序,剛開始元素很無序時,步長最大,所以插入排序的元素個數很少,速度很快;當元素基本有序了,步長很小,插入排序對於有序的序列效率很高。所以,希爾排序的時間復雜度會比o(n2)好一些。
直接選擇排序:
比較次數與初始狀態無關,為n(n-1)/2。故最好,最壞和平均情況均為O(n2)。交換次數為0~n-1,最好的情況是序列為升序,交換0次,最壞的情況是序列為降序,交換n-1次。賦值次數為0~3(n-1)。(?)
堆排序:
最好,最壞和平均情況均為O(nlog2n)。
冒泡排序:
最好的情況:序列已經是升序排列,在這種情況下,需要比較(n-1)次即可。O(n)
最壞的情況:序列是降序排列,此時需要n(n-1)/2次比較和n(n-1)/2次交換。O(n2)
插入排序和冒泡排序的比較:插入排序在比較過程中發現逆序對時,不進行交換,只是賦值,而冒泡排序每發現一次逆序對就要進行交換。故插入排序比冒泡排序快。
快速排序:
最好的情況下:每次都將序列均分為兩個部分,故為O(nlog2n)(一般二分的復雜度都和log2n相關)。
最壞的情況下:序列已經是升序排列時,要比較n(n-1)/2次,故為O(n2)。此時快速排序不如插入排序快。
歸並排序:
歸並排序是一種非“就地”排序,需要與待排序序列一樣多的輔助空間。故歸並排序的缺點是所需額外空間多。
對長度為n的數組,需進行log2n趟二路歸並,每趟歸並的時間為O(n),故其時間復雜度無論是在最好情況下還是在最壞情況下均是O(nlog2n)。
基數排序:
分配需要O(n),收集為O(r),其中r為分配后鏈表的個數,以r=10為例,則有0~9這樣10個鏈表來將原來的序列分類。而d,也就是位數(如最大的數是1234,位數是4,則d=4),即"分配-收集"的趟數。因此時間復雜度為O(d*(n+r))。
3.穩定性
穩定排序:直接插入排序,冒泡排序,歸並排序,基數排序
不穩定排序:希爾排序,快速排序,直接選擇排序,堆排序
(1)冒泡排序
冒泡排序就是把小的元素往前調或者把大的元素往后調。比較是相鄰的兩個元素比較,交換也發生在這兩個元素之間。所以,如果兩個元素相等,我想你是不會再無聊地把他們倆交換一下的;如果兩個相等的元素沒有相鄰,那么即使通過前面的兩兩交換把兩個相鄰起來,這時候也不會交換,所以相同元素的前后順序並沒有改變,所以冒泡排序是一種穩定排序算法。
(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個相同的元素之間的穩定性就被破壞了。比如滿二叉樹5,10,7,8,9,8,9,第一次建大頂堆時,9和7交換,5,10,9,8,9,8,7,第二次建堆時,10,9,8中10最大,不需要交換,此時兩個9的順序發生了變化。所以,堆排序不是穩定的排序算法。
4.使用情況分析
(1)若n較小(如n≤50),可采用直接插入或直接選擇排序。
當記錄規模較小時,直接插入排序較好;否則因為直接選擇移動的記錄數少於直接插入,應選直接選擇排序為宜。
(2)若文件初始狀態基本有序(指正序),則應選用直接插入、冒泡或隨機的快速排序為宜;
(3)若n較大,則應采用時間復雜度為O(nlogn)的排序方法:快速排序、堆排序或歸並排序。
快速排序是目前基於比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短;堆排序所需的輔助空間少於快速排序,並且不會出現快速排序可能出現的最壞情況。這兩種排序都是不穩定的。若要求排序穩定,則可選用歸並排序。但從單個記錄起進行兩兩歸並的排序算法並不值得提倡,通常可以將它和直接插入排序結合在一起使用。先利用直接插入排序求得較長的有序子文件,然后再兩兩歸並之。因為直接插入排序是穩定的,所以改進后的歸並排序仍是穩定的。
(4)若n很大,記錄的關鍵字位數較少且可以分解時,采用基數排序較好。
箱排序和基數排序只需一步就會引起m種可能的轉移,即把一個記錄裝入m個箱子之一,因此在一般情況下,箱排序和基數排序可能在O(n)時間內完成對n個記錄的排序。但是,箱排序和基數排序只適用於像字符串和整數這類有明顯結構特征的關鍵字,而當關鍵字的取值范圍屬於某個無窮集合(例如實數型關鍵字)時,無法使用箱排序和基數排序,這時只有借助於"比較"的方法來排序。雖然桶排序對關鍵字的結構無要求,但它也只有在關鍵字是隨機分布時才能使平均時間達到線性階,否則為平方階。同時要注意,箱、桶、基數這三種分配排序均假定了關鍵字若為數字時,則其值均是非負的,否則將其映射到箱(桶)號時,又要增加相應的時間。
參考: