11.5 左偏樹總結(可並斜堆)


前言

可並堆有多種方法實現,例如斐波那契堆,配對堆等,以下記錄左偏樹,也稱爲斜堆。是一種基於非平衡二叉樹的數據結構,其在合併堆的時候可以達到O(log(m+1))O(log(m+1))的速度。當然,左偏樹還分好幾種,如左偏高樹左偏重樹等。關於左偏,實際上就是讓左子樹的特徵數量不小於右子樹,例如左偏高樹就是左子樹的高度不小於右子樹。


定義

左偏高樹的定義是左子樹高度不小於右子樹,即height(lson)>=height(rson)height(lson)>=height(rson),關於這個高度要區別普通二叉樹的定義:

  • 普通二叉樹的高度定義是左右子樹高度的最大值+1,即h(cur)=max(h(left),h(right))+1h(cur)=max(h(left),h(right))+1
  • 左偏高樹的定義則是左右子樹中高度最小的那個高度值+1,即h(cur)=min(h(left),h(right))+1h(cur)=min(h(left),h(right))+1。顯然根據定義,一棵左偏樹根節點的高度等於右子樹的高度+1。實際上,還有一種數據結構的書引入一種NPLNPL的概念,但和王曉東書裏介紹的高度異曲同工,只不過那個更便於理解,關於NPL可以去看鄧老師的數據結構。

關於左偏高樹這樣定義的好處是能夠讓每次合併的時候直接在右側鏈上的遞歸深度只有log(m+1)log(m+1),從而達到高效地合併。


性質

1,根節點優先級最高,即根的優先級不小於左右孩子的優先級(堆性
2,左孩子的高度不小於右孩子的高度
3,根節點的高度等於右孩子的高度+1(因爲高度的定義是兩子樹中取最小的一個+1)
4,若有m個結點,則右側鏈的長度不超過log2^(m+1)
5,假設右側鏈的長度爲s,則樹的結點個數不小於2^s-1(證明思路:以右側鏈長度爲高的樹是一個滿二叉樹
雖然左偏樹地定義是左子樹的高度大於等於右子樹,但實際上右邊的二叉樹高度有可能比左邊大,要區別兩者概念

例如,在{1,2,3}形成左偏高樹後插入一個{4}形成如下:
在這裏插入圖片描述


實現

左偏樹結構

關鍵的兩個部分:height使整個結構左偏,less(<運算符重載)運算子賦予結構堆性

struct Leftist_Tree {
#define ElemType int

    int height; //高度
    ElemType elem; //權值
    Leftist_Tree *lson, *rson; //左右孩子

    Leftist_Tree(const ElemType& x, int h):lson(NULL), rson(NULL) {
        elem = x;
        height = h;
    }

    bool operator < (const Leftist_Tree& A) const {
        return elem < A.elem;
    }
};
typedef Leftist_Tree LT;

合併

不斷地找右側進行合併,右側合併完成後調整根結點的左傾性,即比較左右子樹的高度,是一個基於後序遍歷的算法

LT* Merge(LT* &x, LT* &y) {
    if(!x) return y; //如果某一個爲空堆就沒合併的必要
    if(!y) return x;

    if(*x < *y) swap(x, y); //以x的root作爲根,顯然根的優先級取兩者最大的那個
    x->rson = Merge(x->rson, y); //取右側鏈進行合併

    if(!x->lson) { //發現整個斜堆右傾,左調
        swap(x->lson, x->rson);
        x->height = 1;
    } else { //否則檢查一下是否符合左偏樹的定義,左調
        if(x->rson->height > x->lson->height) swap(x->lson, x->rson);
        x->height = x->rson->height + 1;
    }
    return x;
}

堆操作

由於左偏高樹能做到高效地合併,所以堆操作都基於合併操作完成

  • 插入,可以構造一個單節點的堆與原堆合併
  • 刪除,刪除掉根節點,剩餘左右子堆合併
  • 查詢,直接返回根節點的elem

完整代碼

#include <bits/stdc++.h>
using namespace std;

struct Leftist_Tree {
#define ElemType int
    int height; //高度
    ElemType elem; //權值
    Leftist_Tree *lson, *rson; //左右孩子

    Leftist_Tree(const ElemType& x, int h):lson(NULL), rson(NULL) {
        elem = x;
        height = h;
    }

    bool operator < (const Leftist_Tree& A) const {
        return elem < A.elem;
    }
};
typedef Leftist_Tree LT;

LT* Merge(LT* &x, LT* &y) {
    if(!x) return y; //如果某一個爲空堆就沒合併的必要
    if(!y) return x;

    if(*x < *y) swap(x, y); //以x的root作爲根,顯然根的優先級取兩者最大的那個
    x->rson = Merge(x->rson, y); //取右側鏈進行合併

    if(!x->lson) { //發現整個斜堆右傾,左調
        swap(x->lson, x->rson);
        x->height = 1;
    } else { //否則檢查一下是否符合左偏樹的定義,左調
        if(x->rson->height > x->lson->height) swap(x->lson, x->rson);
        x->height = x->rson->height + 1;
    }
    return x;
}

bool isEmpty(LT* &x) { //判左偏樹是否爲空
    if(!x) return true;
    return false;
}

bool getTop(LT* &x, ElemType& val) { //得到優先級最高的根
    if(isEmpty(x)) return false;
    val = x->elem;
    return true;
}

bool pop(LT* &x) { //刪除優先級最高的根
    if(isEmpty(x)) return false;
    LT* l = x->lson;
    LT* r = x->rson;
    delete x;
    x = Merge(l, r); //先斷掉根在合併兩個子堆
    return true;
}

bool push(LT* &x, int val) { //入堆
    LT* y = new LT(val, 1);
    if(!y) return false;
    x = Merge(x, y);
    return true;
}

bool heapify(ElemType T[], int n, LT* &x) { //建堆 O(n*Σ(i/2^i)) = O(n)
    queue<LT*> q; while(!q.empty()) q.pop();
    LT *a = NULL, *b = NULL;
    for(int i=0; i<n; i++) q.push(new LT(T[i], 1)); //隊列中加入n個高度爲1的斜堆
    for(int i=1; i<n; i++) { //合併n-1次 方法和哈夫曼樹合併類似 取兩個 壓一個
        a = q.front(); q.pop();
        b = q.front(); q.pop();
        a = Merge(a, b);
        q.push(a);
    }
    while(!q.empty()) q.pop();
    x = a;
    return true;
}

int main() {
    LT *root = NULL, *t;
    ElemType val;
    int arr[10]={10,9,8,7,6,5,4,3,2,1};

    heapify(arr, 10, root); 

    for(int i=0; i<10; i++) cout << arr[i] <<' ' ; cout << endl;
    
    if( getTop(root, val) == true)
        cout << "val = " << val << '\n';

    if( pop(root) == true)
        if( getTop(root, val) == true)
            cout << "val = " << val << '\n';

    if( push(root, 4) == true)
        if( getTop(root, val) == true)
            cout << "val = " << val << '\n';

    heapify(arr, 10, t);

    root = Merge(root, t);

    while(getTop(root, val)) {
        cout << val << ' ';
        pop(root);
    }
    cout << '\n';

    return 0;
}
發佈了397 篇原創文章 · 獲贊 72 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章