選擇類的排序算法算法
簡單選擇排序算法函數
採用最簡單的選擇方式,從頭至尾掃描待排序列,找一個最小的記錄(遞增排序),和第一個記錄交換位置,再從剩下的記錄中繼續反覆這個過程,直到所有有序。測試
具體過程:spa
首先經過 n –1 次關鍵字比較,從 n 個記錄中找出關鍵字最小的記錄,將它與第一個記錄交換。.net
再經過 n –2 次比較,從剩餘的 n –1 個記錄中找出關鍵字次小的記錄,將它與第二個記錄交換。3d
如圖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。
堆排序的穩定性:不穩定排序算法