常見的五類排序算法圖解和實現(選擇類:簡單選擇排序,錦標賽排序,樹形選擇排序,堆排序)

選擇類的排序算法算法

簡單選擇排序算法函數

採用最簡單的選擇方式,從頭至尾掃描待排序列,找一個最小的記錄(遞增排序),和第一個記錄交換位置,再從剩下的記錄中繼續反覆這個過程,直到所有有序。測試

具體過程:spa

首先經過 n –1 次關鍵字比較,從 n 個記錄中找出關鍵字最小的記錄,將它與第一個記錄交換。.net

再經過 n –2 次比較,從剩餘的 n –1 個記錄中找出關鍵字次小的記錄,將它與第二個記錄交換。3d

重複上述操做,共進行 n –1 趟排序後,排序結束。

如圖code

 

過程圖解blog

令 k=i;j = i + 1;排序

目的是使用 k 找出剩餘的 n-1個記錄中,一趟排序的最值,若是 j 記錄小於 k 記錄,k=j;it

繼續比較,j++,若是 j 記錄不小於 k 記錄,繼續 j++,這裏使用 k 來找出一趟排序的最值

直到 j=n 爲止

交換k 記錄和第 i 個記錄(i 從頭開始的),而後 i++,進行下一趟選擇排序過程

整個過程圖示

直到無序序列爲0爲止

代碼以下:

 1 //簡單選擇遞增排序
 2 void selectSort(int List[], int len)
 3 {
 4     //簡單選擇排序的循環
 5     for (int i = 0; i < len; i++) {
 6         int k = i;
 7         //一次排序過程,終止條件是 j 掃描到了最後一個記錄處
 8         for (int j = i + 1; j <= len; j++) {
 9             if (List[j] < List[k]) {
10                 k = j;
11             }
12         }
13         //掃描完畢,交換最值,先判斷是否重複
14         if (i != k) {
15             //交換
16             List[i] = List[i] + List[k];
17             List[k] = List[i] - List[k];
18             List[i] = List[i] - List[k];
19         }// end of if
20     }//end of for
21 }
22 
23 int main(void)
24 {
25     
26     int source[7] = {49, 38, 65, 97, 76, 13, 27};
27     
28     selectSort(source, 7);
29     
30     for (int i = 1; i < 8; i++) {
31         printf(" %d  ", source[i]);
32     }
33 
34     return 0;
35 }

 13   27   38   49   65   76   97  Program ended with exit code: 0

空間複雜度:O(1)

比較次數:

時間複雜度:O(n^2)

簡單選擇排序的穩定性:不穩定

 

錦標賽排序和樹形選擇排序

錦標賽排序也叫樹形選擇排序,是一種按照錦標賽的思想進行選擇的排序方法,該方法是在簡單選擇排序方法上的改進。簡單選擇排序,花費的時間大部分都浪費在值的比較上面,而錦標賽排序恰好用樹保存了前面比較的結果,下一次比較時直接利用前面比較的結果,這樣就大大減小比較的時間,從而下降了時間複雜度,由O(n^2)降到O(nlogn),可是浪費了比較多的空間,「最大的值」也比較了屢次。

大概過程以下:

首先對n個記錄進行兩兩比較,而後優勝者之間再進行兩兩比較,如此重複,直至選出最小關鍵字的記錄爲止。

相似甲乙丙三隊比賽,前提是有這樣一種傳遞關係:若乙勝丙,甲勝乙,則認爲甲必能勝丙。

錦標賽排序圖解以下

初始序列,這麼多隊伍參加比賽

兩兩比較之,用一個徹底二叉樹表示,反覆直到一趟比較後,選出冠軍

找到了 bao,是冠軍,選出冠軍的比較次數爲   2^2+2^1+2^0 = 2^3 -1 = n-1,而後繼續比較,把原始序列的 bao 去掉

選了 cha,選出亞軍的比較次數爲 3,即 log2 n 次。 同理,把 cha 去掉,繼續兩兩比較

找到了 diao,其後的 n-2 我的的名次均如此產生

因此對於 n 個參賽選手來講,即對 n 個記錄進行錦標賽排序,總的關鍵字比較次數至多爲  (n-1)log2 n + n -1,故時間複雜度爲: O(nlogn)。

此法除排序結果所需的 n 個單元外,尚需 n-1 個輔助單元。 

這個過程可用一棵有n個葉子結點的徹底二叉樹表示,根節點中的關鍵字即爲葉子結點中的最小關鍵字。在輸出最小關鍵字以後,根據關係的可傳遞性,欲選出次小關鍵字, 僅需將葉子結點中的最小關鍵字改成「最大值」,如∞,而後從該葉子結點開始,和其左(右)兄弟的關鍵字進行比較,修改從葉子結點到根的路徑上各結點的關鍵字,則根結點的關鍵字即爲次小關鍵字。也就是所謂的樹形選擇排序,這種算法的缺點在於:輔助存儲空間較多、最大值進行多餘的比較。

 

樹形選擇排序

思想:首先對 n 個記錄的關鍵字進行兩兩比較,而後在其中 不大於 n/2  的整數個較小者之間再進行兩兩比較,直到選出最小關鍵字的記錄爲止。能夠用一棵有 n 個葉子結點的徹底二叉樹表示。

樹形選擇排序圖解以下:

對 n 個關鍵字兩兩比較,直到選出最小關鍵字爲止,一趟排序結束

       

反覆這個過程,僅需將葉子結點的最小關鍵字改成最大值∞,便可

而後從該葉子結點開始,繼續和其左右兄弟的關鍵字比較,找出最值

時間複雜度:因爲含有 n 個葉子結點的徹底二叉樹的深度爲,則在樹形選擇排序中,除了最小關鍵字外,每選擇一個次小關鍵字僅需進行   次比較,故時間複雜度爲 O(n logn)。

缺點:  一、與「∞」的比較多餘;  二、輔助空間使用多。

爲了彌補這些缺點,1964年,堆排序誕生。

 

堆排序

堆的定義:n 個元素的序列 (k1, k2, …, kn),當且僅當知足下列關係:任何一個非終端結點的值都大於等於(或小於等於)它左右孩子的值時,稱之爲堆。若序列{k1,k2,…,kn}是堆,則堆頂元素(即徹底二叉樹的根)必爲序列中n個元素的最小值(或最大值) 。

可將堆序列當作徹底二叉樹,則: k2i 是 ki 的左孩子; k2i+1 是 ki 的右孩子。全部非終端結點的值均不大(小)於其左右孩子結點的值。堆頂元素必爲序列中 n 個元素的最小值或最大值。

若:ki <= k2i  ,  ki <= k2i+1,也就是說父小孩大,則爲小頂堆(小根堆,正堆),反之,父大孩小,叫大頂堆(大根堆,逆堆)

堆排序定義:將無序序列建成一個堆,獲得關鍵字最小(大)的記錄;輸出堆頂的最小(大)值後,將剩餘的 n-1 個元素重又建成一個堆,則可獲得 n 個元素的次小值;如此重複執行,直到堆中只有一個記錄爲止,每一個記錄出堆的順序就是一個有序序列,這個過程叫堆排序。

堆排序需解決的兩個問題:

  一、如何由一個無序序列建成一個堆?

  二、在輸出堆頂元素後,如何將剩餘元素調整爲一個新的堆? 

第二個問題解決方法——篩選:

所謂「篩選」指的是,對一棵左/右子樹均爲堆的徹底二叉樹,「調整」根結點使整個二叉樹也成爲一個堆。具體是:輸出堆頂元素以後,以堆中最後一個元素替代之;而後將根結點值與左、右子樹的根結點值進行比較,並與其中小者進行交換;重複上述操做,直至葉子結點,將獲得新的堆,稱這個從堆頂至葉子的調整過程爲「篩選」。

例: (13, 38, 27, 49, 76, 65, 49, 97) 

輸出堆頂元素以後,以堆中最後一個元素替代之;

而後將根結點值與左、右子樹的根結點值進行比較,並與其中小者進行交換

輸出堆頂元素以後,以堆中最後一個元素替代之;

而後將根結點值與左、右子樹的根結點值進行比較,並與其中小者進行交換

輸出堆頂元素以後,以堆中最後一個元素替代之;

而後將根結點值與左、右子樹的根結點值進行比較,並與其中小者進行交換

輸出堆頂元素以後,以堆中最後一個元素替代之;

而後將根結點值與左、右子樹的根結點值進行比較,並與其中小者進行交換

輸出堆頂元素以後,以堆中最後一個元素替代之;

而後將根結點值與左、右子樹的根結點值進行比較,並與其中小者進行交換

對深度爲 k 的堆,「篩選」所需進行的關鍵字比較的次數至多爲 2(k-1)。

第一個問題解決方法:從無序序列的第 個元素(即無序序列對應的徹底二叉樹的最後一個內部結點)起,至第一個元素止,進行反覆篩選。建堆是一個從下往上進行「篩選」的過程。把原始的序列一一對應的(從左到右,從上到下)創建一個徹底二叉樹便可,而後創建小頂堆(大頂堆)

建堆

調整,篩選過程

一趟堆排序完畢,選出了最值和堆裏最後一個元素交換,繼續

第二趟堆排序完畢,選出了次最值和剩下的元素的最後一個元素交換

第三趟堆排序完畢,重複往復,這樣進行堆調整。

第四躺排序完畢,繼續

第五躺排序完畢

第六趟排序完畢

最後所有堆排序完畢

這是整個建堆,調整爲小頂堆的過程,也就是遞增排序。具體是自上而下調整徹底二叉樹裏的關鍵字,使其成爲一個大頂堆(遞減排序過程)

操做過程以下:

1)初始化堆:將R[1..n]構造爲堆;

2)將當前無序區的堆頂元素R[1]同該區間的最後一個記錄交換,而後將新的無序區調整爲新的堆。

對於堆排序,最重要的兩個操做就是構造初始堆和調整堆,其實構造初始堆事實上也是調整堆的過程,只不過構造初始堆是對全部節點都進行調整

調整堆的代碼以下:

 1 //堆排序的堆的調整過程
 2 // 已知 H.r[s..m]中記錄的關鍵字除 H.r[s] 以外均知足堆的特徵,本函數自上而下調整 H.r[s] 的關鍵字,使 H.r[s..m] 成爲一個大頂堆
 3 void heapAdjust(int List[], int s, int length)
 4 {
 5     //s 爲 當前子樹 的 臨時 堆頂,先把堆頂暫存到 temp
 6     int maxTemp = s;
 7     //s 結點 的 左孩子 2 * s , 2 * s + 1是 s結點 的右孩子,這是自上而下的篩選過程,length是序列的長度
 8     int sLchild = 2 * s;
 9     int sRchild = 2 * s + 1;
10     //徹底二叉樹的葉子結點不須要調整,沒有孩子
11     if (s <= length / 2) {
12         //若是 當前 結點的左孩子比當前結點記錄值大,調整,大頂堆
13         if (sLchild <= length && List[sLchild] >  List[maxTemp]) {
14             //更新 temp
15             maxTemp = sLchild;
16         }
17         //若是 當前 結點的右孩子比當前結點記錄值大,調整,大頂堆
18         if (sRchild <= length && List[sRchild] > List[maxTemp]) {
19             maxTemp = sRchild;
20         }
21         //若是調整了就交換,不然不須要交換
22         if ( List[maxTemp] != List[s]) {
23             List[maxTemp] = List[maxTemp] + List[s];
24             List[s] = List[maxTemp] - List[s];
25             List[maxTemp] = List[maxTemp] - List[s];
26             //交換完畢,防止調整以後的新的以 maxtemp 爲父節點的子樹不是大頂堆,再調整一次
27             heapAdjust(List, maxTemp, length);
28         }
29     }
30 }

創建堆的過程,本質仍是堆調整的過程

 1 //建堆,就是把待排序序列一一對應的創建成徹底二叉樹(從上到下,從左到右的順序填滿徹底二叉樹),而後創建大(小)頂堆
 2 void bulidHeap(int List[], int length)
 3 {
 4     //明確,具備 n 個結點的徹底二叉樹(從左到右,從上到下),編號後,有以下關係,設 a 結點編號爲 i,若 i 不是第一個結點,那麼 a 結點的雙親結點的編號爲[i/2]
 5     //非葉節點的最大序號值爲 length / 2
 6     for (int i = length / 2; i >= 0; i--) {
 7         //從頭開始調整爲大頂堆
 8         heapAdjust(List, i, length);
 9     }
10 }

堆排序過程

 1 //堆排序過程
 2 void heapSort(int List[], int length)
 3 {
 4     //建大頂堆
 5     bulidHeap(List, length);
 6     //調整過程
 7     for (int i = length; i >= 1; i--) {
 8         //將堆頂記錄和當前未經排序子序列中最後一個記錄相互交換
 9         //即每次將剩餘元素中的最大者list[0] 放到最後面 list[i]
10         List[i] = List[i] + List[0];
11         List[0] = List[i] - List[0];
12         List[i] = List[i] - List[0];
13         //從新篩選餘下的節點成爲新的大頂堆
14         heapAdjust(List, 0, i - 1);
15     }
16 }

測試數據

int source[8] = {49, 38, 65, 97, 76, 13, 27, 49};

 13   27   38   49   49   65   76   97  Program ended with exit code: 0

 

堆排序的時間複雜度和空間複雜度:

1.   對深度爲 k 的堆,「篩選」所需進行的關鍵字比較的次數至多爲 2(k-1);

2.   對 n 個關鍵字,建成深度爲  的堆,所需進行的關鍵字比較的次數至多 4n;

3.   調整「堆頂」 n-1 次,總共進行的關鍵字比較的次數不超過,所以,堆排序的時間複雜度爲 O(nlogn),與簡單選擇排序 O(n^2)  相比時間效率提升了不少。 

空間複雜度:S(n) = O(1) 

堆排序是一種速度快且省空間的排序方法。相對於快速排序的最大優勢:最壞時間複雜度和最好時間複雜度都是 O(n log n),適用於記錄較多的場景(記錄較少就不實用),相似折半插入排序,在 T(n)=O(n log n)的排序算法中堆排序的空間複雜度最小爲1。

堆排序的穩定性:不穩定排序算法

相關文章
相關標籤/搜索