定義:【最大樹(最小樹)】每個節點的值都大於(小於)或等於其子節點(如果有的話)值的
最大樹與最小樹的例子如下所示,雖然這些樹都是二叉樹,但最大樹不必須是二叉樹,最大樹或最小樹節點的子節點個數可以大於等於2.
               

                                                                                                   1-1最大樹 
                            

                                                                                                 1-2 最小樹
 定義:【最大堆(最小堆)】最大(最小)的完全二叉樹。由於堆是完全二叉樹,所以1-1b 不是最大堆,1-2b不是最小堆。注意到堆是完全二叉樹(從滿二叉樹中刪除K個元素之後爲完全二叉樹),擁有n個元素的堆其高度爲log2(n+1)(與滿二叉樹有相同的高度),因此可以在O(height)時間內完成插入和刪除操作,則這些操作複雜性爲O(log2n)。


最大堆的插入

圖1-3a給出了一個具有5個元素的最大堆,由於堆是完全二叉樹,當插入一個元素形成6元素時,其結構必如1-3b所示。如果插入的元素值爲1,則插入的該元素成爲了2的左孩子,相反,若元素的值爲5,則該元素不能成爲2的左孩子(否則將改變最大樹的特性),應把2下移爲左孩子,如圖1-3c所示,同時還得決定在最大堆中5是否佔據2原來的位置,由於父節點上的值爲20,大於子節點新插入元素5,因此可以在原2的位置插入新的元素5。假設要插入的元素值不是5而是21,這時,同1-3c一樣,把2下移爲左孩子,由於21大於根節點20,因此不能在2的位置放入新元素21,因此這時需要再次下移根節點20元素,將其移到其右孩子的位置(2的位置),再將新元素21插入根節點如圖1-3d所示。

                                                                                   圖1-3 最大堆的插入 
由此可見,插入策略從葉到根只有單一路徑,每一層的工作需要耗時Θ(1),因此實現此策略的時間複雜度O(height)=O(log2n),n爲節點個數。

最大堆的刪除

從最大堆中刪除一個元素時,該元素從根部移出,例如從1-3d的最大堆中進行刪除操作即是移去元素21,因此最大堆只剩下5個元素。此時1-3d的二叉樹需要重新構造,以便仍然爲完全二叉樹。爲此可以移動6中的元素,即2。這樣就得到了正確的結構如圖1-4a中,但此時根節點爲空且元素2不在堆中,如果2直接插入根節點,得到的二叉樹不是最大樹,根節點的元素應該爲大於左右子節點值得元素,這個元素值應爲20,把它移到根節點,3的位置空了,2可以插入,最後形成的最大堆如1-3a所示。

現在假設要刪除20,在刪除之後,堆的二叉樹結構如圖1-4b所示,爲得到這個結構,10從位置5移出,如果10放在根節點,結果並不是最大堆。把根節點的兩個孩子(15和2)中較大一個移到根節點。假設將10插入2的位置,結果仍然不是最大堆,因此將14上移,10插入4位置,最後結果如1-4c所示。

                                                                      1-4 最大堆的刪除

刪除策略從堆的根到葉節點的單一路徑,每一層的工作需要耗時Θ(1),因此實現此策略的時間複雜度O(height)=O(log2n),n爲節點個數,與插入相同。

最大堆的初始化

假設開始數組a中有n個元素,另有n=10,a[1:10]中元素的關鍵值爲[20,12,35,15,10,80,30,17,2,1],這個數組可以用來表示圖1-5a的完全二叉樹,這棵二叉樹不是最大樹。爲了將其轉化成最大堆,從第一具有孩子的節點開始(即節點10),這個元素在數組中的位置爲i=[n/2],如果以這個元素爲根的子樹已經是最大堆,則此時不需要調整,否則必須調整子樹使其成爲最大堆。隨後繼續檢查以i-1,i-2等節點爲根的子樹,直到檢查到整個二叉樹的根節點(其位置爲1)。

過程如下,最初i=5,由於10大於其子節點的元素值1,所以位置i=5爲根的子樹已經是最大堆。

                 檢查i=4的子樹,由於15<17,因此不是最大堆,將15與17進行交換得樹如圖1-5b;

                 檢查i=3的子樹,由於35<80,因此不是最大堆,將35與80進行交換;

                 檢查i=2的子樹,因爲12<17,17成爲重構子樹的根,下一步將12與位置4的兩個孩子中的較大者進行比較,由於12<15,15被移到4位置,12移到15的位置。形成二叉樹如圖1-5c;

                 檢查i=1子樹,這時以位置2或者位置3爲根的子樹已是最大堆了,然而,20<max(17,80),80移入根,位置3空出,由於20<max(35,30),較大者35移入作爲子樹根,20代替35的位置。如圖1-5d。

                    
                                                                         圖1-5最大堆的初始化

  代碼實現

以下程序給出了最大堆的類定義。n 是私有成員,代表目前堆中元素的個數; MaxSize是堆的最大容量;heap爲存貯堆元素的數組,省缺堆的大小爲1 0個元素 

template<class T>
class MaxHeap {
public:
    MaxHeap(int MaxHeapSize = 10);
    ~MaxHeap() {delete [] heap;}
    int Size() const {return CurrentSize;}
    T Max() {if (CurrentSize == 0) throw OutOfBounds();
    return heap[1];}
    MaxHeap<T>& Insert(const T& x);
    MaxHeap<T>& DeleteMax(T& x);
    void Initialize(T a[], int size, int ArraySize);
private:
    int CurrentSize, MaxSize;
    T *heap; // 元素數組
} ;
template<class T>
MaxHeap<T>::MaxHeap(int MaxHeapSize){
    // 構造函數
    MaxSize = MaxHeapSize;
    heap = new T[MaxSize+1];
    CurrentSize = 0;
}

插入

template<class T>
MaxHeap<T>& MaxHeap<T>::Insert(const T& x){
    // 把 x 插入到最大堆中
    if (CurrentSize == MaxSize)
    throw NoMem(); // 沒有足夠空間
    / /爲 x尋找應插入位置
    // i 從新的葉節點開始,並沿着樹上升
    int i = ++CurrentSize;
    while (i != 1 && x > heap[i/2]) {
        // 不能夠把 x 放入 h e a p [ i ]
        heap[i] = heap[i/2]; // 將元素下移
        i /= 2; // 移向父節點
    }
    heap[i] = x;
    return *this;
}

在插入代碼中,i 從新創建的葉節點位置CurrentSize開始,對從該位置到根的路徑進行遍歷。對於每個位置i,都要檢查是否到達根( i = 1)或在i 處插入新元素不會改變最大樹的性質(x .key≤h e a p [i/ 2 ] . key)。只要這兩個條件中有一個滿足,就可以在 i 處插入x,否則,將執行while 循環體,把位於i / 2處的元素移到i 處並把i 處元素移到父節點(i / 2)。對於一個具有n 個元 素的最大堆(即CurrentSize = n),while 循環的執行次數爲O(height) =O( log2n),且每次執行所需時間爲 ( 1 ),因此Insert 的時間複雜性爲O( log2n)。

刪除

template<class T>
MaxHeap<T>& MaxHeap<T>::DeleteMax(T& x)
{
    // 將最大元素放入 x ,並從堆中刪除最大元素
    // 檢查堆是否爲空
    if (CurrentSize == 0)
    throw OutOfBounds(); // 隊列空
    x = heap[1]; // 最大元素
    // 重構堆
    T y = heap[CurrentSize--]; // 最後一個元素
    // 從根開始,爲y 尋找合適的位置
    int i = 1, // 堆的當前節點
    ci = 2; // i的孩子
    while (ci <= CurrentSize) {
        // heap[ci] 應是 i的較大的孩子
        if (ci < CurrentSize && heap[ci] < heap[ci+1]) ci++;
        // 能把 y 放入h e a p [ i ]嗎?
        if (y >= heap[ci]) break; // 能
        // 不能
        heap[i] = heap[ci]; // 將孩子上移
        i = ci; //下移一層
        ci *= 2;
    }
    heap[i] = y;
    return *this;
}

在DeleteMax操作中,堆的根(即最大元素)heap [ 1 ]被保存到變量x中,堆的最後一個元素heap [ CurrentSize ]被保存到變量y中,堆的大小(CurrentSize)被減1。在while 循環中,開始查找一個合適的位置以便重新將 y插入。從根部開始沿堆向下查找,對於具有 n 個元素的堆,while 循環的執行次數爲O ( lg2n),且每次執行所花時間爲 (1),因此,DeleteMax 操作總的時間複雜性爲O( log2n)。注意到即使堆的元素個數爲 0, 代碼也能正確執行,在這種情況下不執行While 循環,對堆的位置1進行賦值是多餘的。 

初始化

template<class T>
void MaxHeap<T>::Initialize(T a[], int size, int ArraySize){
    // 把最大堆初始化爲數組 a .
    delete [] heap;
    heap = a;
    CurrentSize = size;
    MaxSize = ArraySize;
    // 產生一個最大堆
    for (int i = CurrentSize/2; i >= 1; i--) {
    T y = heap[i]; // 子樹的根
    // 尋找放置 y的位置
    int c = 2*i; // c的父節點是y的目標位置
    while (c <= CurrentSize) {
        // heap[c] 應是較大的同胞節點
        if (c < CurrentSize &&
        heap[c] < heap[c+1]) c++;
        // 能把 y 放入h e a p [ c / 2 ]嗎?
        if (y >= heap[c]) break; // 能
        // 不能
        heap[c/2] = heap[c]; // 將孩子上移
        c *= 2; // 下移一層
    }
    heap[c/2] = y;
    }
}

初始時刪除私有成員heap當前所指的數組,並使 heap指向a [ 0 ]。size爲數組a中的元素個數,ArrarySize是假設從a [1]算起數組a 中所能容納的最大元素個數。程序 的最初4行代碼重新設置了最大堆的私有成員,使數組a 代替數組heap。在for 循環中,從數組heap(即數組a)的二叉樹表示中最後一個具有一個孩子的節點開始進行初始化,直至到達根節點。對於每個位置 i,在while 循環中都保證根節點爲i的子樹已是最大堆。請注意for循環體與DeleteMax代碼的相似性。                                                            

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章