1. 程式人生 > >左偏樹 學習筆記

左偏樹 學習筆記

img 情況 cin tle ffd cst 合並操作 ble main

左偏樹(Leftist Tree)是一種可並堆的實現。左偏樹是一棵二叉樹,它的節點除了和二叉樹的節點一樣具有左右子樹指針( left, right)外,還有兩個屬性,鍵值和距離(dist)。

鍵值:是用於比較節點的大小。

距離:節點i稱為外節點(external node),當且僅當節點i的左子樹或右子樹為空 ( left(i) = NULL或right(i) = NULL );節點i稱為外節點(external node),當且僅當節點i的左子樹或右子樹為空 ( left(i) = NULL或right(i) = NULL );特別的,如果節點i本身是外節點,則它的距離為0;而空節點的距離規定為-1 (dist(NULL) = -1)。

//以上來自 黃源河神犇2005國家集訓隊論文%%%

可並堆(Mergeable Heap)也是一種抽象數據類型,它除了支持優先隊列的三個基本操作(Insert, Minimum,Delete-Min),還支持一個額外的操作--合並操作;

部分摘自:

基本性質

[性質1] 節點的鍵值小於或等於它的左右子節點的鍵值。

即key(i)≤key(parent(i)) 這條性質又叫堆性質。符合該性質的樹是堆有序的(Heap-Ordered)。有了性質1,我們可以知道左偏樹的根節點是整棵樹的最小節點,於是我們可以在O(1) 的時間內完成取最小節點操作。

[性質2] 節點的左子節點的距離不小於右子節點的距離。

即dist(left(i))≥dist(right(i)) 這條性質稱為左偏性質。性質2是為了使我們可以以更小的代價在優先隊列的其它兩個基本操作(插入節點、刪除最小節點)進行後維持堆性質。在後面我們就會看到它的作用。

這兩條性質是對每一個節點而言的,因此可以簡單地從中得出,左偏樹的左右子樹都是左偏樹。

由這兩條性質,我們可以得出左偏樹的定義:左偏樹是具有左偏性質的堆有序二叉樹。

我們知道,一個節點必須經由它的子節點才能到達外節點。由於性質2,一個節點的距離實際上就是這個節點一直沿它的右邊到達一個外節點所經過的邊數,也就是說,我們有

[性質3] 節點的距離等於它的右子節點的距離加1。

即dist( i ) = dist( right( i ) ) + 1 外節點的距離為0,由於性質2,它的右子節點必為空節點。為了滿足性質3,故前面規定空節點的距離為-1。

我們的印象中,平衡樹是具有非常小的深度的,這也意味著到達任何一個節點所經過的邊數很少。左偏樹並不是為了快速訪問所有的節點而設計的,它的目的是快速訪問最小節點以及在對樹修改後快速的恢復堆性質。從圖中我們可以看到它並不平衡,由於性質2的緣故,它的結構偏向左側,不過距離的概念和樹的深度並不同,左偏樹並不意味著左子樹的節點數或是深度一定大於右子樹。

//以上來自百度百科

左偏樹的左右子樹都是左偏樹;

我們的印象中,平衡樹是具有非常小的深度的,這也意味著到達任何一個節點所經過的邊數很少。左偏樹並不是為了快速訪問所有的節點而設計的,它的目的是快速訪問最小節點以及在對樹修改後快速的恢復堆性質。

Merge( ) 把A,B兩棵左偏樹合並,返回一棵新的左偏樹C,包含A和B中的所有元素。在本文中,一棵左偏樹用它的根節點的指針表示。

在合並操作中,最簡單的情況是其中一棵樹為空(也就是,該樹根節點指針為NULL)。這時我們只須要返回另一棵樹。

若A和B都非空,我們假設A的根節點小於等於B的根節點(否則交換A,B),把A的根節點作為新樹C的根節點,剩下的事就是合並A的右子樹right(A) 和B了。

合並了right(A) 和B之後,right(A) 的距離可能會變大,當right(A) 的距離大於left(A) 的距離時, 只需交換right(A)於left(A);

插入節點 :直接當做插入兩個左偏樹處理;

刪除最小(大)值, 直接刪除根節點, 然後Merge(left, right);

剩下的操作有點麻煩以後更新!

留下模板:

技術分享圖片
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 100010

int n, m;

int ch[maxn][2], val[maxn], dis[maxn], fa[maxn];

int Merge(int x, int y)
{
    if(x * y == 0) return x + y;
    if(val[x] > val[y] || (val[x] == val[y] && x > y)) swap(x, y);
    ch[x][1] = Merge(ch[x][1], y);
    fa[ch[x][1]] = x;
    if(dis[ch[x][0]] < dis[ch[x][1]]) swap(ch[x][0], ch[x][1]);
    dis[x] = dis[ch[x][1]] + 1;
    return x;
}

int getf(int x)
{
    while(fa[x]) x = fa[x];
    return x;
}

void del(int x)
{
    val[x] = -1;
    fa[ch[x][0]] = fa[ch[x][1]] = 0;
    Merge(ch[x][0], ch[x][1]);
}

int main()
{
    cin >> n >> m;
    
    dis[0] = -1;
    
    for(register int i = 1 ; i <= n ; i ++) scanf("%d", &val[i]);
    
    while(m--)
    {
        int opt;
        scanf("%d", &opt);
        int x, y; 
        if(opt == 1)
        {
            scanf("%d%d", &x, &y);
            if(val[x] == -1 || val[y] == -1) continue;
            
            if(x == y) continue;
            
            Merge(getf(x), getf(y));
        }
        else if(opt == 2)
        {
            scanf("%d", &x);
            if(val[x] == -1) 
            {
                printf("-1\n");
                continue;
            }
            int fx = getf(x);
            printf("%d\n", val[fx]);
            del(fx);
        }
    }
    return 0;
    
}
zZhBr

左偏樹 學習筆記