1. 程式人生 > >校招準備系列5-排序演算法

校招準備系列5-排序演算法

常用排序演算法總結 來源:SteveWang

內部排序演算法,即資料記錄在記憶體中進行排序。

排序演算法大體可分為兩種:

  • 一種是比較排序,時間複雜度O(nlogn) ~ O(n^2),主要有:氣泡排序,選擇排序,插入排序,希爾排序,歸併排序,堆排序,快速排序等。
  • 另一種是非比較排序,時間複雜度可以達到O(n),主要有:計數排序,基數排序,桶排序等。

排序演算法的穩定性

排序演算法穩定性的簡單形式化定義為:如果Ai = Aj,對於每一次排序結果,Ai和Aj的相對次序保持不變。即排序前Ai在Aj之前,排序後Ai還在Aj之前,則稱這種排序演算法是穩定的。通俗地講就是保證排序前後兩個相等的數的相對順序不變。 證明排序演算法的穩定性,需要對演算法進行分析從而得到穩定的特性

排序演算法的時間、空間複雜度對比

在這裡插入圖片描述

氣泡排序(Bubble Sort)

氣泡排序是一種極其簡單的排序演算法,也是我所學的第一個排序演算法。它重複地走訪過要排序的元素,依次比較相鄰兩個元素,如果他們的順序錯誤就把他們調換過來,直到沒有元素再需要交換,排序完成。這個演算法的名字由來是因為越小(或越大)的元素會經由交換慢慢“浮”到數列的頂端。

改進:如果一輪冒泡過程中都沒有發生交換,說明整個陣列已經是有序的了,可以退出排序過程。 (雞尾酒排序:稍微改進了氣泡排序,從低到高然後從高到低)

選擇排序(Selection Sort)

選擇排序也是一種簡單直觀的排序演算法。它的工作原理很容易理解:初始時在序列中找到最小(大)元素,放到序列的起始位置作為已排序序列;然後,再從剩餘未排序元素中繼續尋找最小(大)元素,放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。

選擇排序是不穩定的排序演算法,不穩定發生在最小元素與A[i]交換的時刻。

插入排序(Insertion Sort)

插入排序是一種簡單直觀的排序演算法。它的工作原理非常類似於我們抓撲克牌

對於未排序資料(右手抓到的牌),在已排序序列(左手已經排好序的手牌)中從後向前掃描,找到相應位置並插入。

插入排序在實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。

插入排序不適合對於資料量比較大的排序應用。但是,如果需要排序的資料量很小,比如量級小於千,那麼插入排序還是一個不錯的選擇。 插入排序在工業級庫中也有著廣泛的應用,在STL的sort演算法和stdlib的qsort演算法中,都將插入排序作為快速排序的補充,用於少量元素的排序(通常為8個或以下)。

希爾排序(Shell Sort)

希爾排序,也叫遞減增量排序,是插入排序的一種更高效的改進版本。希爾排序是不穩定的排序演算法。

希爾排序是基於插入排序的以下兩點性質而提出改進方法的:

  • 插入排序在對幾乎已經排好序的資料操作時,效率高,即可以達到線性排序的效率
  • 但插入排序一般來說是低效的,因為插入排序每次只能將資料移動一位

希爾排序通過將比較的全部元素分為幾個區域來提升插入排序的效能。這樣可以讓一個元素可以一次性地朝最終位置前進一大步。然後演算法再取越來越小的步長進行排序,演算法的最後一步就是普通的插入排序,但是到了這步,需排序的資料幾乎是已排好的了(此時插入排序較快)。 假設有一個很小的資料在一個已按升序排好序的陣列的末端。如果用複雜度為O(n^2)的排序(氣泡排序或直接插入排序),可能會進行n次的比較和交換才能將該資料移至正確位置。而希爾排序會用較大的步長移動資料,所以小資料只需進行少數比較和交換即可到正確位置。

歸併排序(Merge Sort)

歸併排序的實現分為遞迴實現與非遞迴(迭代)實現。 遞迴實現的歸併排序是演算法設計中分治策略的典型應用,我們將一個大問題分割成小問題分別解決,然後用所有小問題的答案來解決整個大問題。 非遞迴(迭代)實現的歸併排序首先進行是兩兩歸併,然後四四歸併,然後是八八歸併,一直下去直到歸併了整個陣列。

堆排序(Heap Sort)

堆排序的基本思想是:

將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。 將堆頂元素與末尾元素進行交換,此時末尾就為最大值。 然後將剩餘n-1個元素重新構造成一個堆,重複上一步得到次大值。 如此反覆執行步驟2,3,便能得到一個有序序列了 參考連結:http://www.cnblogs.com/MOBIN/p/5374217.html

堆排序是不穩定的排序演算法,不穩定發生在堆頂元素與A[i]交換的時刻。

時間複雜度:O(nlogn)

初始化建堆時間O(n),參考

堆的插入和刪除,都需要在堆的末尾n+1的位置插入元素

快速排序(Quick Sort)

快速排序是由東尼·霍爾所發展的一種排序演算法。在平均狀況下,排序n個元素要O(nlogn)次比較。在最壞狀況下則需要O(n^2)次比較,但這種狀況並不常見。事實上,快速排序通常明顯比其他O(nlogn)演算法更快,因為它的內部迴圈可以在大部分的架構上很有效率地被實現出來。

快速排序使用分治策略(Divide and Conquer)來把一個序列分為兩個子序列。步驟為:

從序列中挑出一個元素,作為"基準"(pivot). 把所有比基準值小的元素放在基準前面,所有比基準值大的元素放在基準的後面(相同的數可以到任一邊),這個稱為分割槽(partition)操作。 對每個分割槽遞迴地進行步驟1~2,遞迴的結束條件是序列的大小是0或1,這時整體已經被排好序了。 快速排序是不穩定的排序演算法,不穩定發生在基準元素與A[tail+1]交換的時刻。

基於比較的排序演算法的時間複雜度下界為O(nlogn)的證明

對於n個待排序元素,在未比較時,可能的正確結果有n!種。

在經過一次比較後,其中兩個元素的順序被確定,所以可能的正確結果剩餘n!/2種。

依次類推,直到經過m次比較,剩餘可能性n!/(2^m)種。

直到n!/(2^m)<=1時,結果只剩餘一種。此時的比較次數m為o(nlogn)次。

所以基於排序的比較演算法,最優情況下,複雜度是o(nlogn)的

常用的非比較排序演算法:計數排序,基數排序,桶排序。

計數排序(Counting Sort)

通俗地理解,例如有10個年齡不同的人,假如統計出有8個人的年齡不比小明大(即小於等於小明的年齡,這裡也包括了小明),那麼小明的年齡就排在第8位,通過這種思想可以確定每個人的位置,也就排好了序。當然,年齡一樣時需要特殊處理(保證穩定性):通過反向填充目標陣列,填充完畢後將對應的數字統計遞減,可以確保計數排序的穩定性。

計數排序的步驟如下:

統計陣列A中每個值A[i]出現的次數,存入C[A[i]] 從前向後,使陣列C中的每個值等於其與前一項相加,這樣陣列C[A[i]]就變成了代表陣列A中小於等於A[i]的元素個數 反向填充目標陣列B:將陣列元素A[i]放在陣列B的第C[A[i]]個位置(下標為C[A[i]] - 1),每放一個元素就將C[A[i]]遞減 因此對於資料範圍很大的陣列,計數排序需要大量時間和記憶體。例如:對0到99之間的數字進行排序,計數排序是最好的演算法,然而計數排序並不適合按字母順序排序人名。

將計數排序用在基數排序演算法中,能夠更有效的排序資料範圍很大的陣列。(例如,對於0~10000範圍內的數,如果使用計數排序,需要O(10000),而使用基數排序的話,只需要O(4*10))

基數排序(Radix Sort)

基數排序的發明可以追溯到1887年赫爾曼·何樂禮在打孔卡片製表機上的貢獻。它是這樣實現的:將所有待比較正整數統一為同樣的數位長度,數位較短的數前面補零。然後,從最低位開始進行基數為10的計數排序,一直到最高位計數排序完後,數列就變成一個有序序列(利用了計數排序的穩定性)。

基數排序的時間複雜度是O(n * dn),其中n是待排序元素個數,dn是數字位數。這個時間複雜度不一定優於O(nlogn),dn的大小取決於數字位的選擇(比如位元位數),和待排序資料所屬資料型別的全集的大小;dn決定了進行多少輪處理,而n是每輪處理的運算元目。

如果考慮和比較排序進行對照,基數排序的形式複雜度雖然不一定更小,但由於不進行比較,因此其基本操作的代價較小,而且如果適當的選擇基數,dn一般不大於log n,所以基數排序一般要快過基於比較的排序,比如快速排序。由於整數也可以表達字串(比如名字或日期)和特定格式的浮點數,所以基數排序並不是只能用於整數排序。

桶排序(Bucket Sort)

桶排序也叫箱排序。工作的原理是將陣列元素對映到有限數量個桶裡,利用計數排序可以定位桶的邊界,每個桶再各自進行桶內排序(使用其它排序演算法或以遞迴方式繼續使用桶排序)。

桶排序不是比較排序,不受到O(nlogn)下限的影響,它是鴿巢排序的一種歸納結果,當所要排序的陣列值分散均勻的時候,桶排序擁有線性的時間複雜度。