1. 程式人生 > 其它 >7.2 二叉樹與堆

7.2 二叉樹與堆

二叉樹與堆

二叉樹是一種特殊的、常見的樹

簡介

二叉樹的特點在於每個結點最多隻有兩個兒子
如果要使用更嚴格的遞迴定義,則是:

二叉樹要麼為空,要麼由根結點、左子樹、右子樹組成
而左子樹、右子樹分別是一棵二叉樹

二叉樹是使用範圍極廣的樹,一棵多叉樹也可以轉換為二叉樹

二叉樹型別

滿二叉樹:如果二叉樹中每個內部結點都有兩個兒子,這樣的二叉樹叫做滿二叉樹
完全二叉樹:如果一棵二叉樹除了最右邊的位置上有一個或幾個葉節點缺少外,其他都是豐滿的,那麼它就是完全二叉樹
我們可以把滿二叉樹理解為一種特殊的,極其完美的完全二叉樹
我們只需要用一個一維陣列就能儲存完全二叉樹

我們將完全二叉樹進行從上到下,從左到右

的編號,可以得到下面這個結論:
如果已知子結點的編號是x,那麼它父結點的編號就是x/2

符合所有父結點都比子結點要小的樹,就是最小堆
符合所有父結點都比子結點要大的樹,就是最大堆

那麼想要向堆中加入一個數據,需要怎麼做呢?
比如說我們想要把23加入到一個最小堆
1.我們把23放入堆頂,檢查是否合適
2.如果不合適,我們就將這個數和它的兩個兒子比較,並且選擇較小者交換位置
3.繼續向下交換,檢查。重複1-2直到符合條件

當新增加一個數被放置到堆頂時,如果此時不符合最小堆的特性,則需要將這個數向下調整,直到找到合適的位置為止

程式碼例項_向下調整

    void siftdown(int i)
    {
        //傳入一個需要向下調整的結點編號i
        //這裡傳入1,即從堆的頂點開始向下調整

        int t,flag = 0 ;
        //flag用於標記是否需要繼續向下調整  
        //當i結點有兒子(其實至少是有左兒子的情況下)並且有需要調整的時候,迴圈就執行
        while( i*2 <= n && flag == 0 ){
            //先判斷它和左兒子的關係,用t記錄值比較小的結點編號
            if(h[i] > h[i*2])
                t = i*2 ;
                //子節點是較小的
                //和/2得到父節點相對,*2自然得到子節點(左)
            else 
                t = 1  ;
                //1是較小值

            //如果有右兒子,再對右兒子(*2+1)進行討論
            if(i*2+1 <= n)
            {
                //如果右兒子的值更小,更新較小的結點編號
                if(h[t] > h[i*2 + 1])
                    t = i*2+1 ;
            }

            //如果發現最小的結點編號不是自己,那麼就說明子結點中,有比父結點更小的
            if(t!=i)
            {
                swap(t,i);//交換它們,這裡記得自己寫一個swap函式
                i = t ;
            }
            else{
                falg = 1 ;
            }
        }
        return ;
    }

如果只是想新增一個值,而不要刪除掉最小值,那麼該怎麼做呢?
直接將新元素插入到末尾,再根據情況判斷元素是否需要上移,直到滿足堆的特性為止

堆排序

與快速排序一樣,,堆排序的時間複雜度是O(NlogN)
進行堆排序,需要我們建立對應的堆,每次刪除頂部元素並將頂部元素輸出或者放到一個數組中,直到堆空
最後輸出的或者存放在新陣列中的數,就是已經排序好的
下面以從小到大排序為例

程式碼例項_從小到大堆排序

//先整個刪除最小元素的
int deletemin()
{
    int t ;
    t = h[1];//用一個臨時變數記錄堆頂點的值 
    h[1] = h[n];//將堆的最後一個點賦值到堆頂    
    n--;//使堆的元素減少1
    siftdown(1) ; //向下調整
    return t ; //返回在之前記錄的堆的頂點的最小值
}

完整的程式碼組成如下:

#include <stdio.h>
int h[101];//用來存放堆的陣列
int n ; //用來儲存堆中元素的個數,實質上就是堆的大小

//先整個交換函式
void swap(int x ,int y)
{
    int t ; 
    t = h[x];
    h[x] = h[y];
    h[y] = t ; 
    return 
}

//向下調整的函式,上文已經寫過了
void siftdown(int i)
{
    //傳入一個需要向下調整的結點編號i,這裡傳入1,即從堆的頂點開始向下調整

    int t,flag = 0 ;
    //flag用於標記是否需要繼續向下調整  
    //當i結點有兒子(其實至少是有左兒子的情況下)並且有需要調整的時候,迴圈就執行
    while( i*2 <= n && flag == 0 ){
        //先判斷它和左兒子的關係,用C記錄值比較小的結點編號
        if(h[i] > h[i*2])
            t = i*2 ;
        else 
            t = 1  ;

        //如果有右兒子,再對右兒子進行討論
        if(i*2+1 <= n)
        {
            //如果右兒子的值更小,更新較小的結點編號
            if(h[t] > h[i*2 + 1])
                t = i*2+1 ;
        }

        //如果發現最小的結點編號不是自己,那麼就說明子結點中,有比父結點更小的
        if(t!=i)
        {
            swap(t,i);//交換它們,這裡記得自己寫一個swap函式
            i = t ;
        }
        else{
            falg = 1 ;
        }
    }
    return ;
}

//建立堆的函式
void creat()
{
    int i ; //從最後一個非葉結點到第一個結點依次進行向下調整

    for(i=n/2 ; i>=1;i--)
    {
        siftdown(i);
    }

    return ; 
}

//刪除最大的元素
int deletmax()
{
    int t ; 
    t = h[1];
    h[1] = h[n]; 
    b-- ; 
    siftdown(1);//向下調整
    return t ; 
}

int main(){
    int i , num ; 
    //讀入要排序的數字個數
    scanf("%d",&num);

    //讀入數字
    for(i=1;i<num;i++)
        scanf("%d",&h[i]);
    n = num ;

    //建堆
    creat();s

    //刪除頂部元素,連續刪除n次,實際上就是從小到大把數輸出 
    for(i=1;i<=num;i++)
        printf("%d",deletemax());

    getchar();getchar();
    return 0 ;
}

小結

像這種,支援插入元素和尋找最值元素的資料結構被稱為優先佇列
堆就是一種優先佇列的實現