可並堆之左偏樹

2021-09-25 22:37:59 字數 4734 閱讀 1109

可並堆$(mergeable\ heap)$是一類抽象資料型別,它除了支援一般的優先佇列的基本操作以外,還支援額外的合併操作。而可並堆有多種,包括斜堆,左偏樹,二項堆,配對堆,斐波那契堆等。 

這裡我們只介紹左偏樹($leftist\ tree$),它是最常用的一種可並堆。至於為什麼說最常用,我們會在後面講到。(先挖個坑)

本文同步發表在博主的洛谷部落格裡。

感謝洛谷的巨佬planet6174提供的。

左偏樹是一種具有左偏性質的堆有序二叉樹(這裡要注意,堆有序二叉樹和二叉堆並不是同一種東西,因此左偏樹並不是堆)。每乙個節點儲存的資訊包括左右子節點、關鍵值以及距離(當然也有很多時候我們需要維護父節點)。

節點的距離可以這樣定義:

某個節點被稱為外節點,僅當這個節點的左子樹或右子樹為空。某乙個節點的距離即該節點到與其最近的外節點經過的邊數。易得,外節點的距離為$0$,空節點距離為$-1$。特別的,我們把根結點的距離稱為這棵左偏樹的距離。

下圖為一棵左偏樹:

首先說明,下文中的$val$表示節點的鍵值,$dist$表示節點的距離。

性質1

堆的性質,即對於任意節點$p$,$val[p]\geq$(或$\leq$) $val[lson],val[rson]$。

性質2

$dist[lson]\geq dist[rson]$,即左子樹距離一定大於等於右子樹距離。

左偏樹,顧名思義,就是向左偏的樹,就是這個性質在影象視覺化後的詮釋。

這個性質的存在主要是為了保證左偏樹的複雜度,因為左偏,因此我們把操作全部向右進行,這樣就可以極大地降低操作複雜度。

性質3

$dist[x]=dist[rson]+1$,也就是說,任意節點的距離等於其右子樹的距離$+1$。

很好理解,由性質二以及外節點的距離為$0$可以得出。

引理1

如果一棵左偏樹的距離為$k$,則這顆左偏樹節點數最少為$2^-1$。 證明:

因為左偏樹的左子樹距離一定大於等於右子樹的距離,並且左偏樹的距離等於右子樹距離加一,那麼一顆左偏樹的距離一定且要求節點數最少時,節點數最少時任意節點的左子樹大小等於右子樹大小,滿足這樣性質的二叉樹就是完全二叉樹。

定理1

對於乙個有$n$個節點的左偏樹,最大距離不超過$\log(n+1)-1$,即$max\\leq \log(n+1)-1$。

證明:設$max\=k$,那麼顯然節點數最少的左偏樹是一棵完全二叉樹,此時節點數為$2^-1$,故$n\geq 2^-1$,移項得:$k\leq \log(n+1)-1$。

1,合併$merge$

合併操作是可並堆最重要的操作,以小根堆為例,合併$x$與$y$。

令$x$為$x,y$中權值較小的乙個,然後繼續向下合併,在$x$的右子樹最右鏈中找到第乙個比$y$大的位置,將$y$作為其父親,然後繼續不斷遞迴呼叫合併$y$的右子樹和以該節點為根的右子樹即可,同時注意維護相關資訊。更新以後可能會發現,右子樹$dist$比左子樹大,只要交換兩個子樹即可。

下圖為合併過程:

y)2,刪除根節點$erase$將根節點的權值賦為$-1$,然後合併左右子樹,維護相關資訊即可。

inline void erase(int

x)

3,刪除任意節點$delete$這裡先解釋一下,一般來說可並堆是不支援刪除某一給定權值的點的,因此我們這裡說的任意節點是指知道編號的任意節點。

首先和根節點一樣,先把刪除節點的權值賦為$-1$,然後合併其左右子樹得到新的左偏樹,我們再把得到的這個左偏樹接到刪除節點的父節點上,同時維護父節點的子節點資訊。

但是,刪除掉節點後我們可能會發現不滿足左偏性質了,那麼我們就需要判斷是否需要交換左右子樹,並且要一直向上重複判斷,直到到了某一結點時左偏性質沒有被破壞了或者已經到了根節點。

int delete(int

x)

return

ka;}

4,建樹$build$如果我們直接乙個乙個的$merge$來建樹也沒有什麼問題,但是這樣顯然效率非常低下(算上$merge$,這樣建樹複雜度是$o(n\log n)$的)。

我們可以稍微優化一下,把每個節點當作節點數為$1$的左偏樹存入佇列中,然後每次取出隊首的兩個左偏樹合併,把合併後的左偏樹放入佇列,重複操作直到佇列中只剩下乙個左偏樹。

如圖:(圖中未註明權值)

也就是採用兩兩合併的方法,這樣就只需要合併$\log n$次,再算上$merge$的複雜度,這樣建樹的複雜度基本上可以看作$o(n)$(請讀者自證,~~還是作者太懶了~~)。

int

build()

return

q.front();

}

一般來說左偏樹能處理所有的二叉堆的問題和所有的可並堆的問題。

但是通常遇到的較難的左偏樹題都不是直接進行對集合的操作,這些集合的關係可能更加複雜。最常見的例子就是樹上點的集合關係維護的問題。

派遣

題目鏈結

一句話題意:從樹中選出乙個節點作為管理者,然後在它的子樹中(包括它自己)選出若干節點,要求使花費總和小於$m$,並且使得收益最大。

【思路】我們可以用左偏樹維護點的關係。

除了左右子節點和高度以外,這個左偏樹一共還需要維護該節點花費,總花費,總人數三條資訊。

從根節點開始$dfs$,然後從下往上遞迴轉移。當轉移到第$x$個節點時,我們將它與它所有子節點形成的左偏樹合併,然後進行判斷,將花費大的節點全部彈出直到花費小於等於$m$為止,然後更新答案即可。當然,這裡左偏樹要建立大根堆,因為小根堆維護花費和不大於$m$會非常麻煩。然後注意一些細節就行了。

棘手的操作

題目鏈結

一句話題意,給你一棵樹然後進行一堆蛇皮怪物一般的操作。

【思路】一堆操作真是令人頭大,實際上仔細思考的話這些操作還是不難實現的。

需要維護兩個左偏樹,第乙個維護正常的操作資訊,第二個維護所有點中的最大值。

【總結】

如果對左偏樹不太熟悉,那麼第一道例題中是較難看出左偏樹的解法的。一般出題人如果考察左偏樹的知識點的話,往往不會直接給你裸的集合關係讓你維護。(出題人:我怎麼能這麼容易讓你把題給切了呢?)

一般這些集合或者集合內的元素一開始就有很多條件限制(上題中是樹狀結構),因此往往需要我們自己推導這些集合的關係,然後得出左偏樹維護的方法。

而第二題這種純碼農題就不用多說了,全靠一雙眼睛$debug$。

左偏樹的另外乙個重要的應用就是可持久化。

為什麼左偏樹可以可持久化呢?

因為左偏樹具有二叉樹結構且能動態合併,因此能夠可持久化。但是因為左偏樹也是帶有均攤複雜度的,因此是不能完全可持久化的。

可持久化左偏樹實現起來也非常簡單,構建方法類似於可持久化線段樹,動態開點就行了。

模板**:

struct

node t[n*20]//

可持久化資料結構套路,空間開大點

int siz;//

動態開點用

int merge(int x,int y,int opt)//

opt控制是否新建節點

其餘操作基本不變,按題目要求變化。

(填上上面的坑)

有這麼多的可並堆可以選擇,為什麼偏偏就是左偏樹最常見呢?

先看一張表:

這裡還要解釋一下,斜堆是一種與左偏樹非常類似的可並堆。為了降低操作複雜度,左偏樹採用的方法是將重量集中在左子樹,而斜堆則是採取一種隨機的思想,每次合併完都要交換左右子樹,這使得左右子樹的大小是隨機分配的,因此一般來說,斜堆的均攤複雜度為$o(\log n)$,而左偏樹則是最壞複雜度$o(\log n)$。所以實現的時候,左偏樹往往會比斜堆稍快。(當然具體也要看資料咯)

然後二項堆、斐波那契堆雖然複雜度極其優秀,但是程式設計複雜度極大(尤其是斐波那契堆),考場上選擇它們實在是不理智。 (當然如果你是平板電視julao,這話當我沒說)

另外關於配對堆,實際上配對堆確實非常優秀,不僅有和斐波那契堆相當的時間複雜度,而且**實現比較容易,空間需求也大大減小(在不帶$decrease\_key$操作的時候甚至比左偏樹小)。不過現在配對堆可能並沒有那麼普及,而且由於均攤複雜度,使得配對堆無法實現可持久化,而且配對堆的複雜度承受在$pop$操作上,所以在$pop$操作頻繁的題目中不建議使用。(具體的資料可以參見wikipedia,這裡博主就不放了)

因此左偏樹往往是考場上需要使用可並堆時的第一選擇。

可並堆之左偏樹總結

左偏樹,顧名思義,是左邊的結點權值較大的樹形資料結構。主要用於兩個優先佇列的快速合併,是可並堆的一種實現方式。定義與性質 外結點 乙個結點的右子結點為空就為外結點 距離 結點一直向右,直到外結點所經歷的步數,每個結點距離等於右兒子的距離 1。左偏樹的父親結點的優先順序高於兒子結點 父親結點的左子節點...

堆之左偏樹

左偏樹 左偏堆 優勢 兩個左偏樹合併效率高o logn 外結點 乙個結點的孩子數不滿兩個,就稱之為外結點。距離 dist 每個節點上有個距離 dist 的屬性,dist的值為該結點到最近的外結點所經過的邊的個數。外結點的dist 0。空結點的dist 1。左偏樹定義 1.是一顆二叉樹。2.是個堆。3...

左偏樹(可並堆)

左偏樹其實是一種可並堆,它可以 o log2 n o l og2n 合併兩個堆。那左偏?也就是說他左邊肯定有什麼東西比右邊大 別著急,在左偏樹上有乙個叫距離的東西 個點的距離,被定義為它子樹中離他最近的外節點到這個節點的距離 這與樹的深度不同 其中我們定義乙個節點為外節點,當且僅當這個節點的左子樹和...