學習筆記 左偏樹

2022-04-29 05:48:06 字數 1382 閱讀 5658

左偏樹是一種可並堆,除了堆的基本功能,最大的特點就是支援合併堆,甚至比普通堆好寫。

下面敘述以小根堆為例,大根堆對稱。

\(o(\log n)\) 求乙個數所在堆的根

\(o(1)\) 求最小值

\(o(\log n)\) 合併兩個堆

\(o(\log n)\) 刪除最小值

\(o(\log n)\) 插入乙個數

\(n\) 是插入的總節點數(或當前堆的節點數)。

struct t t[n];
基本的結構還是堆,即對於任意節點,它的權值小於等於其子樹中任意權值,因此查詢最小值只需 \(o(1)\) 訪問根即可。

左偏的意義就是:對於任意乙個節點的左兒子 \(ls\) 和右兒子 \(rs\),都有 \(t[ls].d \ge t[rs].d\),感性理解就是左子樹深的更長。

性質:對於一棵根節點 \(rt\) 滿足 \(t[rt].d = k\) 的堆而言,至少有 \(2^k - 1\) 個節點,即乙個高度為 \(k\) 的滿二叉樹的節點樹,因為這些節點必不可少,否則 \(d\) 就小於 \(k\) 了。因此對於乙個有 \(n\) 個節點的堆,根節點的 \(d\) 就是 \(\log n\) 級別的。

樸素上我們可以乙個個跳 \(t[x].f\)。不過我們可以把 \(t[x].f\) 看做乙個並查集 \(fa\) 陣列,路徑壓縮一下:

int find(int x)
這樣只要保證我們之後的賦值 \(fa\) 操作都是類似並查集的合併操作,那麼複雜度就是 \(o(\log n)\) 的。

找到乙個數所在根,直接訪問根節點值即可。

合併 \(merge(x, y)\) 分別以 \(x, y\) 為根的兩個小根堆,並返回合併完的根編號:不妨設 \(t[x].v < t[y].v\)(若不滿足 \(\text\) ) ,接著只需遞迴 \(merge(t[x].r, y)\)。回溯時檢查 \(x\) 左右兒子的 \(d\),若不滿足左偏樹關係交換,返回 \(x\) 即可。

時間複雜度,每次遞迴,\(x, y\) 之一的 \(d\) 必然減少 \(1\),做多減少到 \(0\),而 \(d\) 是 \(\log n\) 量級的,所以複雜度是 \(o(\log n)\)。

貌似網上的複雜度都不是很對,不能每次都賦 \(t[x].fa = x\),這樣複雜度就假了,而是函式呼叫之前把 \(t[y].f = x\),然後內部合併不改變 \(f\) 的值,這樣相當於合併兩個聯通塊,複雜度是對的。

int merge(int x, int y) 

int work(int x, int y)

找到根節點後,合併兩個子樹。

void del(int x)
直接單開乙個節點,合併就好了。

左偏樹學習筆記

左偏樹是一種基於二叉樹的可並堆。定義乙個節點的 距離 dis xdis x disx 為它到空節點的最短路長度,左偏樹強制要求 dis lson dis rson dis ge dis dislso n d isrs on 所以 dis x di srso n 1dis x dis 1 disx d...

左偏樹 學習筆記

首先要知道左偏樹是用來幹什麼的。如果給我們兩個優先序列,然後讓我把這兩個優先佇列合併成乙個優先佇列。如果直接用堆,就是將乙個佇列裡面的數不斷彈出然後扔到另乙個佇列裡。複雜度是 o n n為佇列中數的個數。但是用左偏樹就可以做到 log n 1 n 2 ps 為了便於討論,本文所有的左偏樹均已小根樹為...

左偏樹 學習筆記

左偏樹是可並堆的一種。除了支援堆的所有操作之外,左偏樹還支援合併兩個堆並把複雜度維持在 o n log n 級別。dist 外節點 對於一棵二叉樹,我們定義外節點為左兒子或右兒子為空的節點。dist 我們定義某乙個節點的 text 為該點到最近的外節點的距離。外節點的 text 為 1 我們不難發現...