實現多個堆的合並——左偏樹學習筆記


在初學OI時,我們接觸了一種數據結構,叫做堆。

眾所周知的,我們可以使用 \(STL\)\(priority\_queue\) 來快速地實現一個堆。

\[\tiny\text{如圖,這就是一個普通的小根堆} \]

利用 \(priority\_queue\) ,我們可以很方便地進行堆的添加,刪除等操作。

然而,當題目需要你進行堆的合並時, \(priority\_queue\) 便不再那么適用了。因此我們需要學習一些新的算法——左偏樹

左偏樹是一種樹形結構,儲存了一個點的數值,左右兒子和距離(被定義為它子樹中離他最近的外節點到這個節點的距離)

\[\tiny\text{如圖,這是一個普通的左偏樹} \]

左偏樹有以下幾個性質:

  • 節點的權值小於等於它左右兒子的權值

  • 節點的左兒子的距離不小於右兒子的距離

  • 節點的距離等於右兒子的距離+1

  • 一個n個節點的左偏樹距離最大為 \(\log{(n+1)}-1\)

可以看出,左偏樹本身並不是平衡的,而是所有節點倒向左側,即名字中的左偏(需要說明的是,左偏指的不是左右兒子的大小,因此如果左兒子是一個點,而右兒子是一條很長的鏈,那也是滿足要求的左偏樹)


概念到此為止,我們來考慮一下如何把兩個堆合並

首先,我們只需要將堆2加入堆1倒數第二層的點的右兒子的左兒子,因此我們只看右兒子

然后將堆2變為堆1的左兒子

合並

還原到初始的狀態

最后,由於所得堆不符合左偏樹性質,交換左右兒子

至此,我們成功地將兩個堆合並在了一起

用語言口胡一下以上過程,是這樣的:

  1. 找到所需要的右兒子

  2. 將新堆變為右兒子的左兒子

  3. 找回原來的堆

  4. 如果不左偏,交換左右兒子

既然原理都明白,代碼就很好實現了吧

#define lson tree[x].son[0]
#define rson tree[x].son[1]

struct LeftTree
{
	int dis,v,son[2],root;
}tree[maxn];

inline void swap(int &x,int &y){x^=y^=x^=y;}

inline int merge(int x,int y)
{
	if(!x||!y) return x+y;
	if( tree[x].v>tree[y].v || (tree[x].v==tree[y].v && x>y) ) swap(x,y);//驗證是否需要交換
	rson=merge(rson,y);//遞歸右兒子
	if(tree[lson].dis<tree[rson].dis) swap(lson,rson);//左邊更淺就交換
	tree[lson].root=tree[rson].root=tree[x].root=x;
	tree[x].dis=tree[rson].dis+1;//樹的深度為右兒子深度+1
	return x;
}

inline void pop(int x)
{
	tree[x].v=-1;//數值清空
	tree[lson].root=lson;
	tree[rson].root=rson;
	tree[x].root=merge(lson,rson);
}

https://home.cnblogs.com/u/tqr06/

https://www.cnblogs.com/tqr06/p/10400144.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM