1. 程式人生 > 實用技巧 >扔硬幣

扔硬幣

堆排序

選擇排序:

  1. 簡單選擇排序
  2. 堆排序

選擇排序:每一趟在待選擇元素中選取關鍵字最小(或最大)的元素加入有序子序列

難理解!!

什麼是“堆(Heap)”?

若n個關鍵字序列L[1...n] 滿足下面某一條性質,則稱為堆(Heap):

  1. 若滿足:L(i)≥L(2i) 且L(i)≥L(2i+1) (1≤i≤n/2) ——大根堆(大頂堆)
  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) \]

穩定性

堆排序是不穩定的

知識回顧

順便考了完全二叉樹順序儲存

練習

基於”小根堆“如何建堆、排序