1.基本概念html
2.時空複雜度web
3.穩定性算法
4.使用狀況分析shell
排序算法總結(C語言版)已介紹排序算法的基本思想和C語言實現,本文只介紹時空複雜度和穩定性。數組
1.基本概念ide
時間複雜度:函數
一個算法花費的時間與算法中語句的執行次數成正比例,哪一個算法中語句執行次數多,它花費時間就多。一個算法的語句執行次數稱爲語句頻度或時間頻度。記爲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的不斷增大,上述時間複雜度不斷增大,算法的執行效率越低。spa
在各類不一樣算法中,若算法中語句執行次數爲一個常數,則時間複雜度爲O(1)。.net
空間複雜度:
空間複雜度是對一個算法在運行過程當中臨時佔用存儲空間大小的量度。算法在運行過程當中臨時佔用的存儲空間隨算法的不一樣而異,有的算法只須要佔用少許的臨時工做單元,並且不隨問題規模的大小而改變,咱們稱這種算法是"就地"進行的,是節省存儲的算法;有的算法須要佔用的臨時工做單元數與解決問題的規模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個記錄的排序。可是,箱排序和基數排序只適用於像字符串和整數這類有明顯結構特徵的關鍵字,而當關鍵字的取值範圍屬於某個無窮集合(例如實數型關鍵字)時,沒法使用箱排序和基數排序,這時只有藉助於"比較"的方法來排序。雖然桶排序對關鍵字的結構無要求,但它也只有在關鍵字是隨機分佈時才能使平均時間達到線性階,不然爲平方階。同時要注意,箱、桶、基數這三種分配排序均假定了關鍵字若爲數字時,則其值均是非負的,不然將其映射到箱(桶)號時,又要增長相應的時間。
參考: