1. 程式人生 > 其它 >平衡樹 - FHQ 學習筆記

平衡樹 - FHQ 學習筆記

平衡樹 - FHQ 學習筆記

主要參考萬萬沒想到 的 FHQ-Treap學習筆記

本片文章的姊妹篇:平衡樹 - Splay 學習筆記

感覺完全不會平衡樹,又重新學習了一遍 FHQ,一口氣把常見套路都學完了。

一、大致內容及分類

FHQ,全稱非旋轉 Treap,是一種可以用於維護按權值、排名分裂的資料結構。它相比與 Splay 雖然常數較大,但是實現起來程式碼難度相對容易,而且由於它非旋的特點,也可以用來實現可持久化。

既然叫做非旋 Treap,它兼有 Treap 的特點又有非旋轉獨特的優勢。

  • 從 Treap 角度看,他們同樣都是依賴修正值 rnd 是隨機的,用將他們按照 rnd 形成一個小根堆
    。與 Treap 相同,它也滿足笛卡爾樹的性質,它的中序遍歷和它的插入順序相同,即 \(1\)\(n\) 的序列。
  • 從非旋角度看,FHQ 直接通過 splitmerge 操作實現新增、刪除元素,不用再樹上旋轉了。

根據不同題目要求,將平衡樹分為序列平衡樹權值平衡樹

  • 序列平衡樹的中序遍歷為每個元素的下標,權值為每個元素具體的值,常見題型為區間翻轉等。
  • 權值平衡樹的中序遍歷是所有元素的排名,即按照中序遍歷提取所有元素後元素權值遞增,常見操作為全域性第 \(k\) 大等。

如果毒瘤出題人同時綜合了以上兩種操作,即區間翻轉 \(+\) 區間第 \(k\) 大,應該怎麼做呢?好吧,如果真是這樣,這篇文章可能不能夠幫到你,用用樹套樹吧。

二、基本操作

FHQ 的核心操作就是 split 出操作區間,操作完後 merge 回去。

下邊講解中預設的平衡樹型別為權值平衡樹,序列平衡樹其實是將某些 \(val\) 改為了 \(siz\)

分裂 split

無論是按照排名還是權值分裂,他們都是將原樹分為左右兩半,可以利用中序遍歷的性質進行分裂。

具體操作時,我們新建兩個臨時變數 \(x,y\) 分別表示分裂出來的左邊、右邊的那顆平衡樹。

如果我們遇到一個應該屬於 \(x\) 樹的節點,就將這個點以及這個點的左子樹加入 \(x\) 樹中,並遞迴分裂右子樹;如果遇到屬於 \(y\) 的,就將這個點與它的右子樹加入 \(y\) 樹中,並遞迴分裂左子樹。

可以寫出虛擬碼如下:

void split(int p,int k,int &x,int &y) // 分裂出 (-infty,k],(k,+infty) 
{ 
	 if(!p) { x=y=0; return; }
	 pushdown(p);
	 if(tree[p].val<=k) x=p,split(tree[p].pr,k,tree[x].pr,y);
	 else y=p,split(tree[p].pl,k,x,tree[y].pl);
	 pushup(p);
}

合併 merge

由於這是 FHQ 的 merge,需要在合併時既保證小根堆性質又不破壞中序遍歷的特點,對合並的兩棵樹有特殊的要求:左右區間不能夠相交或者順序顛倒!

所以我們在合併時必須按照順序從左到右合併。

具體操作時,可以直接將 rnd 小的作為新樹的根節點,如果這個根節點來自左子樹就遞迴右子樹,相反來自右子樹就遞迴左子樹(由於滿足上面區間不相交也不顛倒的特點)。

寫出虛擬碼:

int merge(int x,int y)
{
	 if(!x || !y) return x+y;
	 if(tree[x].rnd<tree[y].rnd)
	 	 pushdown(x),tree[x].pr=merge(tree[x].pr,y),pushup(x);
	 else
	 	 pushdown(y),tree[y].pl=merge(x,tree[y].pl),pushup(y);
}

新建節點 new

沒什麼可說的,就是給新節點附一個隨機的 rnd

inline int New(int Val)
{
	 tree[++cnt].rnd=rand();
	 tree[cnt].val=Val;
	 tree[cnt].siz=1;
	 tree[cnt].pl=tree[cnt].pr=0;
	 return cnt;
}

插入 insert

直接分裂出兩端區間,把新建的加點放到兩棵樹中間在合併即可。

inline void Insert(int val)
{
	 split(root,val,x,y);
	 root=merge(merge(x,New(val)),y);
}

刪除 delete

FHQ 可以實現刪除一個數或刪除這個值的所有數,唯一區別就在於分裂時的不同。

inline void Delete_one(int val)
{
	 split(root,val,x,z);
	 split(x,val-1,x,y);
	 root=merge(x,z);
}
inline void Delete_all(int val)
{
	 split(root,val,x,z);
	 split(x,val-1,x,y);
	 y=merge(tree[y].pl,tree[y].pr);
	 root=merge(merge(x,y),z);
}

查詢排名對應權值 Rank_to_Value

查詢權值對應排名 Value_to_Rank

三、可持久化平衡樹

四、常見優化技巧

五、模板

六、例題