排序演算法:選擇排序
選擇排序的基本思想:每一趟從待排序的記錄中選出關鍵字最小的記錄,按順序放到已排序的記錄序列的最後,直到全部排完為止。
1. 簡單選擇排序
簡單選擇排序也稱作直接選擇排序。
演算法過程如圖:
演算法描述:
void SelectSort(SqList &L) { for (int i=1; i<L.length; i++) { int k = i; for (int j=i+1; j<=L.length; j++) { // 選擇後面序列中最小的值 if (L[j] < L[k]) k = j; } if (k != i) { // 交換L[i]和L[k] int temp = L[i]; L[i] = L[k]; L[k] = temp; } } }
演算法分析:
時間複雜度:\(O(n^2)\)
空間複雜度:\(O(1)\)
演算法特點:
- 選擇排序本身是種穩定的排序方法,但圖中表現出不穩定的現象,這是由於所採用的“交換記錄”的策略所造成的的,改變策略,可以寫出不產生“不穩定現象”的選擇排序演算法。
- 可用於鏈式結構
- 移動記錄次數較少,當每一記錄佔用空間較多時,此方法比直接插入排序快。
2. 樹形選擇排序
樹形選擇排序,又稱錦標賽排序。
演算法過程如圖所示:
這種排序方法尚有輔助儲存空間較多、和“最大值”進行多餘的比較等缺點。
為了改進這個缺點,威洛姆斯在1964年提出了另一種形式的選擇排序————堆排序。
3. 堆排序
堆排序是一種樹形選擇排序。
堆實質上是滿足如下性質的完全二叉樹:樹中所有非終端結點的值均不大於(或不小於)其左、右孩子結點的值。
實現堆排序需要解決如下兩個問題:
(1)建初堆:如何將一個無序序列建成一個堆?
(2)調整堆:去掉堆頂元素,在堆頂元素改變之後,如何調整剩餘元素成為一個新的堆?
1. 調整堆
調整堆過程如圖:
上述過程就像過篩子一樣,把較小的關鍵字逐層篩下去,較大的關鍵字逐層選上來。因此,稱此方法為“篩選法”。
篩選法調整堆演算法描述:
void HeapAdjust(SqList &L, int s, int m) { int c = L[s]; for (int j=2*s; j<=m; j*=2) { if (j<m && L[j].key < L[j+1].key) j++; // j為key較大的記錄的下標 if (r.key >= L[j].key) break; L[s] = L[j]; s = j; } L[s] = c; }
2. 建初堆
只有一個結點的樹必是堆;而在完全二叉樹中,所有序號大於\(\lfloor\)\(\frac{n}{2}\)\(\rfloor\)的節點都是葉子,因此只需利用篩選法,從最後一個非葉子結點\(\lfloor\)\(\frac{n}{2}\)\(\rfloor\)開始,依次將\(\lfloor\)\(\frac{n}{2}\)\(\rfloor\)、\(\lfloor\)\(\frac{n}{2}\)\(\rfloor-1\)...、1為序號的節點作為根,分別調整其為堆即可。
建初堆演算法描述:
void CreatHeap(SqList &L) { // 把無序序列L[1, ..., n]建成大根堆
int n = L.length;
for(int i=n/2; i>0; i--) {
HeapAdjust(L, i, n);
}
}
3. 堆排序演算法
堆排序的過程:
演算法描述:
void HeapSort(SqList &L) {
CreatHeap(L);
for (int i=L.length; i>1; i--) {
x = L[1];
L[1] = L[i];
L[i] = L[1];
HeapAdjust(L, 1, i-1);
}
}
演算法分析:
時間複雜度:\(O(nlog_2n)\)
空間複雜度:\(O(1)\)
演算法特點:
(1)不穩定排序
(2)只能用於順序結構,不能用於鏈式結構
(3)初始建堆需要的比較次數較多,因此 記錄數較少時不宜採用。堆排序在最壞情況下時間複雜度為\(O(nlog_2n)\),相對於快速排序最壞情況下\(O(n_2)\)而言是個優點, 當記錄較多時較為高效。