左偏樹
左偏樹是可合併的二叉堆,首先滿足所有的堆的性質,其外,它還可以合併。
左偏樹的樹節點需要保存的信息有:
1.左右子樹節點編號
2.此節點到有空子結點(子節點數不足2個的節點)的結點的最短距離dist
3.自身權值
性質
左偏樹除了堆的所有性質,它還要滿足的重要的性質就是“左偏”。
左偏
這個性質保證了它的操作都是O(logn)的。
左偏就是每個節點的左子節點的dist不小於右子節點的dist
(但並不代表左子節點數一定不小於右子節點數),
那麼可知dist[i] =dist[rc[i]]+1;
如圖(圈內是節點權值,藍字就是dist值。)
操作
堆可以做到的是:插入(O(logn)),查詢最值(O(1)),刪除堆頂(O(logn));
對於左偏樹,這些操作都是基於合併的(除了查詢最值),而且複雜度都仍然是O(logn)。
左偏樹合併操作合併的是兩棵左偏樹,對於堆的插入,就是合併一棵樹和一個節點,對於堆的刪除,就是合併根的兩棵子樹。
合併過程
以小根堆爲例,
合併A, B兩個堆
如果 A < B(這個不滿足的話swap(a,b))and兩棵樹的節點沒有包含關係(就是沒有相同的節點)。
比較B和A的右子樹大小,
如果B < A的右子樹,那麼swap B和A的右子樹,
接着將B看成剛剛的A,繼續swap;
如果B>A的右子樹,那麼繼續找這顆樹的右子樹。
而這樣可能會破壞左偏的性質,
所以需要在回溯的過程中維護左偏性質,
通過交換左右子樹完成。
總的來說,左偏樹的核心操作,合併(merge),是在右子樹上進行的,
又因爲要保證每個節點的左子節點的dist不小於右子節點的dist,
而且有dist[i] =dist[rc[i]]+1,
所以一棵左偏樹的效率是O(logn)的
下面附上圖會理解得更清晰:
// 左偏樹模板, 以大根堆爲例
struct node{
int w, lc, rc, h;
}t[M];//結構體
//核心操作 :合併(函數返回值:樹根)
int merge(int A, int B){
if(!A||!B) return A+B;
if(t[A].w < t[B].w) swap(A, B);
t[A].rc = merge(t[A].rc, B);
maintain(A); //維護A的一些信息(此模板結構體中並沒有),如:子節點數
if(t[t[A].lc].h < t[t[A].rc].h) swap(t[A].lc, t[A].rc);
if(t[A].rc) t[A].h = t[A].rc+1;
else t[A].h = 0;
return A;
}
//插入
void insert(int A, int x){
cnt++;
t[cnt].w = x;
merge(A, cnt);
}
//刪除
void del(int A){
merge(t[A].lc, t[A].rc);
}