扔硬幣
堆排序
選擇排序:
- 簡單選擇排序
- 堆排序
選擇排序:每一趟在待選擇元素中選取關鍵字最小(或最大)的元素加入有序子序列
難理解!!
什麼是“堆(Heap)”?
若n個關鍵字序列L[1...n] 滿足下面某一條性質,則稱為堆(Heap):
- 若滿足:L(i)≥L(2i) 且L(i)≥L(2i+1) (1≤i≤n/2) ——大根堆(大頂堆)
- 若滿足:L(i)≤L(2i) 且L(i)≤L(2i+1) (1≤i≤n/2) ——小根堆(小頂堆)
大根堆:完全二叉樹中,根≥左、右
相應的小根堆,就是根節點小於左右兩邊的結點。
如何基於“堆”進行排序
堆頂元素關鍵字最大
建立大根堆
根≥左、右
思路:把所有非終端結點都檢查一遍,是否滿足大根堆的要求,如果不滿足,則進行調整
在順序儲存的完全二叉樹中,非終端節點n/2
檢查當前節點是否滿足跟≥左、右
若不滿足,將當前結點與更大的一個孩子互換。
- i的左孩子——2i
- i的右孩子——2i+1
- i的父節點——i/2向上取整
更小的元素“下墜”可能導致下一層的子樹不符合大根堆的要求
程式碼實現
//建立大根堆 void BildMaxHeap(int A[],int len){ for(int i=len/2;i>0;i--) //從後往前調整所有非終端節點 HeadAdjust(A,i,len); } //將以k為根的子樹調整為大根堆 void HeadAdjust(int A[],int k,int len){ A[0] = A[k]; //A[0]暫存子樹的根節點 for(int i=2*k;i<=len;i*=2){ //沿key較大的子節點向下篩選 if(i<len&&A[i]<A[i+1]) i++; //取key較大的子節點的下標 if(A[0]>=A[i]) break; //篩選結束 else{ A[k]=A[i]; //將A[i]調整到雙親結點 k=1; //修改k值,以便 } } A[k]=A[0]; //被篩選結點的 }
基於大根堆進行排序
選擇排序:每一趟在待排序元素中選取關鍵字最大的元素加入有序子序列
堆排序:每一趟將堆頂元素加入有序子序列(與待排序序列中的最後一個元素進行交換)
並將待排序元素序列再此調整為大根堆(小元素不斷“下墜”)
注意:基於“大根堆”的堆排序得到“遞增序列”
程式碼實現
//建立大根堆 void BildMaxHeap(int A[],int len){ for(int i=len/2;i>0;i--) //從後往前調整所有非終端節點 HeadAdjust(A,i,len); } //將以k為根的子樹調整為大根堆 void HeadAdjust(int A[],int k,int len){ A[0] = A[k]; //A[0]暫存子樹的根節點 for(int i=2*k;i<=len;i*=2){ //沿key較大的子節點向下篩選 if(i<len&&A[i]<A[i+1]) i++; //取key較大的子節點的下標 if(A[0]>=A[i]) break; //篩選結束 else{ A[k]=A[i]; //將A[i]調整到雙親結點 k=1; //修改k值,以便 } } A[k]=A[0]; //被篩選結點的 } //堆排序的完整邏輯 void HeapSort(int A[],int len){ BuildMaxHeap(A,len); //初始建堆 for(int i=len;i>1;i--){ //n-1趟的交換和建堆過程 swap(A[i],A[1]); //堆頂元素和堆底元素交換 HeadAdjust(A,1,i-1); //把剩餘的待排序元素整理成堆 } }
i指向當前待排序元素序列中的最後一個(堆底元素)
演算法效率分析
下方有兩個孩子,則“下墜“一層,需要對比關鍵字兩次
下方只有一個孩子,則”下墜“一層,對比關鍵字一次
結論:一個結點,每”下墜“一層,最多隻需要對比關鍵字2次
若樹高為h,某節點在第1層,則將這個結點向下調整最多隻需要”下墜“h-i層,關鍵字對比次數不超過2(h-i)
\[n個結點的完全二叉樹樹高h=\lfloor log_2n \rfloor + 1 \]
第i層最多有2^i-1個結點,而只有第1~(h-1)層的結點才有可能需要”下墜“調整
將整棵樹調整為大根堆,關鍵字對比次數不超過
\[\sum_{i=h-1}^{1}2^{i-1}2(h-i)=\sum_{i=h-1}^{1}2^{i}(h-i)=\sum_{j=1}^{h-1}2^{h-j}j\leq\sum_{j=1}^{h-1}\frac{j}{2^j}\le4n \]
差比數列求和(錯位相減法)
建堆的過程中,關鍵字對比次數不超過4n,建堆時間複雜度=O(n)
共n-1趟
\[總的時間複雜度=O(nlog_2n) \]
\[堆排序的時間複雜度=O(n)+O(nlog_2n)=O(nlog_2n) \]
\[堆排序的空間複雜度 = O(1) \]
穩定性
堆排序是不穩定的
知識回顧
順便考了完全二叉樹和順序儲存