二叉堆的構建、插入、刪除等操作

完全二叉樹

若設二叉樹的深度爲h,除第h層外,其它各層 (1 ~ h-1) 的結點數都達到最大個數,第h層的所有節點都集中在左邊的若干位置,這就是完全二叉樹




二叉樹和二叉堆

  • 二叉樹一般採用鏈表的形式實現
  • 二叉堆一般採用數組的形式實現



二叉堆

  • 具有完全二叉樹的特性
  • 二叉堆的根節點稱爲堆頂
  • 堆中任何一個父節點的值都 >=/<= 它的左右孩子節點
    最大堆:父節點的值 >= 左右孩子節點的值
    在這裏插入圖片描述
    最小堆
    在這裏插入圖片描述



以小根堆舉例

構建二叉堆:

  • 即一個有 n 個節點的 無序的完全二叉樹,把它構建成二叉堆
  • 非葉子節點–從下往上-依次下沉。從下面的非葉子節點開始,讓所有非葉子節點依次下沉(從下往上開始)
    在這裏插入圖片描述



插入節點:

具有完全二叉樹的特性,插入一個節點的時候,需要保證節點插入後,仍然是一顆完全二叉樹,然後在進行調整,使它滿足二叉堆的另一個特性

  • 插入。把新節點插入到完全二叉樹的最後一個位置
  • 調整–上浮。把新插入的節點與它的父節點進行比較,如果新節點小於父節點的值,則讓新節點上浮,即和父節點交換位置




刪除節點(一般刪除根節點):

具有完全二叉樹的特性,刪除一個幾點的時候,要馬上恢復它具有完全二叉樹特性

  • 刪除。把根節點刪除
  • 最後一個元素頂替刪除的根節點爲止。用二叉堆最後一個元素頂替上來
  • 調整----頂替後的根節點下沉。把頂替上來的根節點和它的左右孩子比較,如果根節點大於子節點,則讓根節點下沉,和子節點交換位置
  • 如何選取子節點?因爲是最小堆,要保證父節點比子節點小,因此選擇左右節點中小的元素進行交換




舉個例子:
數組形式:1 2 3 5 4 6 7 8 9
二叉堆形式:
1
2 3
5 4 6 7
8 9

此處 n 是數組下標
節點位置:n
左孩子位置:2 * n + 1
右孩子位置:2 * n + 2

舉例:
節點[2]下標:1
節點[5]下標:3 = 2 * 1 + 1
節點[4]下標:4 = 2 * 1 + 2




看一個代碼更加了解一下:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;
// 下沉:即把當前節點和孩子節點進行比較
void DownAdjust(int * a , int parent , int indexSize)
{
    int temp = a[parent];
    // 左孩子節點先賦值
    int child = 2 * parent + 1;                 // 找到左右節點中的最小值
    
    // 下沉要一直下沉,直到葉子節點
    while(child < indexSize)
    {   
        // 如果存在右節點,找出左右節點中最小的那個進行交換
        if ((child + 1 < indexSize) && (a[child] > a[child + 1]))
        {
            child = child + 1;
        }

        // 當父節點比子節點都小或者相等時,下沉結束
        if(temp <= a[child])
        {
            break;
        }

        // 單向賦值(爲啥不是)
        a[parent] = a[child];
        parent = child;
        child = 2 * parent + 1;
    }
    
    a[parent] = temp;
}


// 上浮:對插入的節點執行上浮操作  indexSize插入前最後一個元素的下標
void UpAdjust(int * a , int indexSize)
{
    // 插入的節點的下標
    int chlid = indexSize + 1;
    int temp = a[chlid];

    int parent = (chlid - 1)/2;                 // 爲什麼要 (child - 1)/2,可以去試試,插入的節點是左節點或者右節點通過-1/2計算出來的值都一樣
    while(chlid > 0 && temp < a[parent])        // 即浮到最頂上了
    {
        // 單向賦值。不用每次都交換,因爲你發現交換後還得給temp重新賦值,還是之前那個值,直接用temp值去比較就行了
        a[chlid] = a[parent];
        chlid = parent;
        parent = (chlid - 1)/2;
    }
    
    a[chlid] = temp;
}


// 構建二叉堆
void BuildHeap(int * arr , int indexSize)
{
    for(int i = (indexSize - 2)/2 ; i >= 0 ;--i)
    {
        DownAdjust(arr , i , indexSize);
    }
}



void PrintArr(int * a , int size)
{
    for(int i = 0 ; i < 10; ++i)
    {
        printf("%d " , a[i]);
    }
    printf("\n");
}

int main()
{
    int a[10] = {10 , 12, 7 , 8 , 6 ,3 ,5 , 2 ,1 , 9};
    PrintArr(a , 10);
	
	// 本例都是以小根堆爲例子
	// 構建二叉堆:即從下往上對非葉子節點進行下沉,每次都沉到底/或父節點比左右子節點都小爲止
    BuildHeap(a , 9);

    PrintArr(a , 10);

    return 0;
}

輸出結果:

10 12 7 8 6 3 5 2 1 9 
1 2 3 8 6 7 5 12 10 9 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章