數(shù)據(jù)結(jié)構(gòu): 可合并堆-左偏樹 Leftist Tree

數(shù)據(jù)結(jié)構(gòu): 可合并堆-左偏樹


來自維基百科

左偏樹(英語: leftist tree或leftist heap), 也可稱為左偏堆, 左傾堆, 是計(jì)算機(jī)科學(xué)中的一種樹, 是一種優(yōu)先隊(duì)列實(shí)現(xiàn)方式, 屬于可并堆.
左偏堆的合并操作的最壞情況復(fù)雜度為O(log n), 而完全二叉堆為O(n), 所以左偏堆適合基于合并操作的情形.

本文圖片引自 圖解數(shù)據(jù)結(jié)構(gòu)(9)--左偏樹

左偏樹的結(jié)構(gòu)和性質(zhì)

左偏樹是可以合并的二叉堆, 首先滿足作為堆的基本性質(zhì):

  • 樹形結(jié)構(gòu)
  • 節(jié)點(diǎn)儲(chǔ)存信息, 有優(yōu)先級
  • 每個(gè)節(jié)點(diǎn)的優(yōu)先級大于其子節(jié)點(diǎn)的優(yōu)先級

此外, 左偏樹有一些額外的性質(zhì), 來保證它合并的效率.
左偏樹的節(jié)點(diǎn)至少需要保存:

  • 左右子樹節(jié)點(diǎn) lc, rc
  • 該節(jié)點(diǎn)到 有空子節(jié)點(diǎn)的節(jié)點(diǎn) 的最短距離 h
  • 優(yōu)先級權(quán)值 w

有空子節(jié)點(diǎn)的節(jié)點(diǎn), 就是指子節(jié)點(diǎn)數(shù)不為2的節(jié)點(diǎn)

除了堆的性質(zhì), 左偏樹還滿足 "左偏" 的性質(zhì), 這一點(diǎn)保證它的合并效率是O(log n)
即每個(gè)節(jié)點(diǎn)的左子節(jié)點(diǎn)的h不小于右子節(jié)點(diǎn)的h, 因而有 h[rt] = h[rc[rt]] + 1
下圖即是一棵左偏樹, 節(jié)點(diǎn)上即權(quán)值(優(yōu)先級), 小根堆, 藍(lán)色數(shù)字為h
(注意, 左偏樹的左子樹一定不小于右子樹)

1.png

左偏樹的操作

同樣, 左偏樹可以做到普通的堆可以做到的:

  • 插入元素 O(log n)
  • 查詢最值 O(1)
  • 刪除堆頂 O(log n)

而且還可以做到額外的:

  • 合并 O(log n)

并且, 插入和刪除都是基于合并的:

  • 插入即合并原樹和一個(gè)新節(jié)點(diǎn)
  • 刪除即合并根的兩個(gè)子樹

左偏樹的合并操作

以小根堆為例:
假如要合并兩個(gè)堆 A, B
首先令 w[A] < w[B], 然后進(jìn)行合并merge(A, B)
在該函數(shù)中:

  • 如果 w[rc[A]] < w[B], merage(rc[A], B)
  • 如果 w[rc[A]] > w[B], 交換 A的右子樹 和 B, 然后繼續(xù)merage(rc[A], B)
  • 如果 A的右子樹 或 B 為空, 則直接連接, 結(jié)束

如下圖演示:

2-1.png
2-2.png
2-3.png
2-4.png

可以看得出來, 這一番操作后失去了左偏的性質(zhì), 所以還要在遞歸回溯的過程中維護(hù)左偏性質(zhì):
如果節(jié)點(diǎn)左子節(jié)點(diǎn)的h小于右子節(jié)點(diǎn)的h, 則交換左右子樹, 維護(hù)該節(jié)點(diǎn)的h

2-5.png

合并操作的偽代碼

merge返回合并之后的樹根
仍然以小根堆為例

merge(A, B)
{
    if(A == NULL) return B;
    if(B == NULL) return A;

    if(w[A] > w[B]) swap(A, B);

    rc[A] = merge(rc[A], B);

    /* 維護(hù)左偏性質(zhì) */
    if(h[lc[A]] < h[rc[A]]) swap(lc[A], rc[A]);

    /* 維護(hù)節(jié)點(diǎn) A 的 h值 */
    if(rc[A] == NULL) h[A] = 0;
    else h[A] = h[rc[A]] + 1;

    return A;
}

完整代碼

NODE_NUM 為最大節(jié)點(diǎn)數(shù), 所有的樹的節(jié)點(diǎn)都在lt數(shù)組里
若改成大根堆只需要修改結(jié)構(gòu)體里為 w > k.w

  • 新建一棵樹: rt = new_node(w);
  • 合并兩棵樹: rt = merge(rt, rt2);
  • 插入新節(jié)點(diǎn): rt = insert(rt, w);
  • 刪除根節(jié)點(diǎn): rt = del(rt);
  • 返回堆頂值: lt[rt].w

#define NODE_NUM 1000005

struct node {
    int lc, rc;
    int w, h;
    bool operator < (const node &k) {
        return w < k.w;
    }
}lt[NODE_NUM];

int cnt, root;

//合并以A為根和以B為根的樹
int merge(int A, int B)
{
    if(!A) return B;
    if(!B) return A;

    if(lt[B] < lt[A]) swap(A, B);

    if(lt[B] < lt[lt[A].rc]) swap(lt[A].rc, B);
    lt[A].rc = merge(lt[A].rc, B);

    if(lt[lt[A].lc].h < lt[lt[A].rc].h) {
        swap(lt[A].lc, lt[A].rc);
    }
    if(lt[A].rc) lt[A].h = lt[lt[A].rc].h + 1;
    else lt[A].h = 0;

    return A;
}

//新建一個(gè)節(jié)點(diǎn)
int new_node(int w)
{
    lt[++cnt].w = w;
    lt[cnt].h = lt[cnt].rc = 0;
    return cnt;
}

//插入一個(gè)權(quán)值為w的節(jié)點(diǎn)
int insert(int A, int w)
{
    return merge(A, new_node(w));
}

//刪除堆頂
int del(int A)
{
    return merge(lt[A].lc, lt[A].rc);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琐谤,一起剝皮案震驚了整個(gè)濱河市陶衅,隨后出現(xiàn)的幾起案子尤揣,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扣讼,死亡現(xiàn)場離奇詭異鲁冯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)中姜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門消玄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丢胚,你說我怎么就攤上這事翩瓜。” “怎么了携龟?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵兔跌,是天一觀的道長。 經(jīng)常有香客問我峡蟋,道長坟桅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任蕊蝗,我火速辦了婚禮仅乓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蓬戚。我一直安慰自己夸楣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布碌更。 她就那樣靜靜地躺著裕偿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痛单。 梳的紋絲不亂的頭發(fā)上嘿棘,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音旭绒,去河邊找鬼鸟妙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挥吵,可吹牛的內(nèi)容都是我干的重父。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼忽匈,長吁一口氣:“原來是場噩夢啊……” “哼房午!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起丹允,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤郭厌,失蹤者是張志新(化名)和其女友劉穎袋倔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體折柠,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宾娜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扇售。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片前塔。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖承冰,靈堂內(nèi)的尸體忽然破棺而出华弓,到底是詐尸還是另有隱情,我是刑警寧澤巷懈,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布该抒,位于F島的核電站,受9級特大地震影響顶燕,放射性物質(zhì)發(fā)生泄漏凑保。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一涌攻、第九天 我趴在偏房一處隱蔽的房頂上張望欧引。 院中可真熱鬧,春花似錦恳谎、人聲如沸芝此。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婚苹。三九已至,卻和暖如春鸵膏,著一層夾襖步出監(jiān)牢的瞬間膊升,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工谭企, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廓译,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓债查,卻偏偏與公主長得像非区,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子盹廷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容