4. 數(shù)據(jù)結(jié)構(gòu) - AVL 樹

這篇文章收錄在我的 Github 上 algorithms-tutorial,另外記錄了些算法題解站欺,感興趣的可以看看迎瞧,轉(zhuǎn)載請注明出處。

(一) AVL 樹概念

AVL 樹(也可以稱為平衡樹)是一類數(shù)據(jù)結(jié)構(gòu)吴旋,是改進(jìn)后的二叉查找樹损肛。一般的二叉樹的查詢復(fù)雜度是跟目標(biāo)結(jié)點(diǎn)到樹根的距離 (即樹深) 有關(guān),因此如果當(dāng)結(jié)點(diǎn)的深度普遍比較大時(shí)荣瑟,查詢的成本就會(huì)上升治拿,所以 AVL 樹就應(yīng)運(yùn)而生。

AVL 樹可以理解為平衡樹樹的一種具體實(shí)現(xiàn)笆焰,兩者并無二致劫谅。這里介紹的平衡樹跟老師說的AVL 樹不一樣。

一、AVL 樹特性:

  1. AVL 樹本身首先得是一棵二叉搜索樹
  2. 每個(gè)結(jié)點(diǎn)的左右子樹的高度之差的絕對值 (平衡因子) 不超過 1
  3. 每個(gè)結(jié)點(diǎn)的左右子樹也要滿足特性 2捏检,也是平衡二叉樹

平衡因子:某結(jié)點(diǎn)的左子樹與右子樹的高度(深度)差即為該結(jié)點(diǎn)的平衡因子

AVL 樹上所有結(jié)點(diǎn)的平衡因子只可能是 -1荞驴,0 或 1。

比如:

AVL 樹:

1.png

non-AVL 樹:根節(jié)點(diǎn)無左子樹

2.png

二贯城、為什么使用 AVL 樹熊楼?

3.png

我們都知道二叉搜索樹可以具有良好的查詢,插入等操作冤狡,但是如果二叉樹退化成了鏈?zhǔn)綐?上圖右)孙蒙,那么其空間復(fù)雜度也會(huì)從原本 O(logn) 退化成了 O(n),這樣顯然是不想看到的結(jié)果悲雳。

如果可以讓樹維持矮矮胖胖的好身材, 也就是讓高度維持在 O(log n)左右, 完成上述工作就很省時(shí)間挎峦。能夠一直維持好身材, 不因新增刪除而長歪的搜尋樹, 叫做 AVL 樹。

(二) AVL 樹基本操作

相對于二叉查找樹的節(jié)點(diǎn)來說合瓢,我們需要用一個(gè)屬性二叉樹的高度坦胶,目的是維護(hù)插入和刪除過程中的旋轉(zhuǎn)算法。

JAVA 代碼實(shí)現(xiàn):

public class AVLTree{
    TreeNode leftChild = null;
    TreeNode rightChild = null;
    int height;
    String data;
}

那么我們在對一棵二叉查找樹進(jìn)行插入和刪除操作的時(shí)候晴楔,如何讓其保持 AVL 樹的特性呢顿苇?

一、旋轉(zhuǎn):

假設(shè)有一個(gè)結(jié)點(diǎn)的平衡因子為 2(在 AVL 樹中税弃,因?yàn)榻Y(jié)點(diǎn)是一個(gè)個(gè)插入到樹中纪岁,一旦出現(xiàn)不平衡的狀態(tài)就會(huì)立即調(diào)整,因此平衡因子最大不可能超過 2)

那么這時(shí)候就要進(jìn)行調(diào)整了则果,由于任意結(jié)點(diǎn)最多只有兩個(gè)兒子幔翰,所以當(dāng)高度不平衡時(shí)候,無非以下情況所造成:

  1. 對該結(jié)點(diǎn)的左兒子的左子樹進(jìn)行了一次插入西壮。
  2. 對該結(jié)點(diǎn)的左兒子的右子樹進(jìn)行了一次插入遗增。
  3. 對該結(jié)點(diǎn)的右兒子的左子樹進(jìn)行了一次插入。
  4. 對該結(jié)點(diǎn)的右兒子的右子樹進(jìn)行了一次插入款青。

情況 1 和 4 是關(guān)于該點(diǎn)的鏡像對稱做修,同樣,情況 2 和 3 也是一對鏡像對稱抡草。

當(dāng)那個(gè)結(jié)點(diǎn)高度出現(xiàn)不平衡饰及,我們就要對那個(gè)結(jié)點(diǎn)進(jìn)行操作,這里我們稱這個(gè)不平衡結(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn)康震。

二旋炒、單旋轉(zhuǎn):

情況 1 和情況 4類似,我們可以理解為發(fā)生在 “外邊” 的情況 (即左-左签杈,右-右的情況) 該情況只需要通過一次旋轉(zhuǎn)來完成調(diào)整

情況 1:對該結(jié)點(diǎn)的左兒子的左子樹進(jìn)行了一次插入

4.png

此時(shí)結(jié)點(diǎn) k2 出現(xiàn)了不平衡瘫镇,所以我們以 k2 為當(dāng)前節(jié)點(diǎn)進(jìn)行右旋鼎兽,并將 Y 移到 k2 左子結(jié)點(diǎn),從而變成右圖(因?yàn)樽筮吀叨雀呦吵晕覀儗⒆笞訕渖弦蒲枰В瑏砥胶馄涓叨炔睿芎美斫猓?/p>

代碼實(shí)現(xiàn):

public AVLTree singleRotateWithRight(AVLTree unbalancedNode){
    AVLTree node = unbalancedNode.leftChild;
    unbalancedNode.leftChild = node.rightChild;
    node.rightChild = unbalancedNode;
    node.height = MAX(node.leftChild.height, node.rightChild.height) + 1;
    unbalancedNode.height = MAX(unbalancedNode.leftChild.height, unbalancedNode.rightChild.height) + 1;
    return node;   //返回新的根結(jié)點(diǎn)
}

實(shí)例:插入結(jié)點(diǎn) 6

1.gif

情況 4:對該結(jié)點(diǎn)的右兒子的右子樹進(jìn)行了一次插入

6.png

思路和情況 1 類似尚粘,操作剛好相反過來

代碼實(shí)現(xiàn):

public AVLTree singleRotateWithLeft(AVLTree unbalancedNode){
    AVLTree node = unbalancedNode.rightChild;
    unbalancedNode.rightChild = node.leftChild;
    node.leftChild = unbalancedNode;
    node.height = MAX(node.leftChild.height, node.rightChild.height) + 1;
    unbalancedNode.height = MAX(unbalancedNode.leftChild.height, unbalancedNode.rightChild.height) + 1;
    return node;   //返回新的根結(jié)點(diǎn)
}

實(shí)例:插入結(jié)點(diǎn) 6

2.gif

三择卦、雙旋轉(zhuǎn):

情況 2 和情況 3 (由于其形狀可以稱為 dog-leg)是插入發(fā)生在“內(nèi)部”的情況(即左-右的情況或右-左的情況),該情況要通過稍微復(fù)雜些的雙旋轉(zhuǎn)來處理郎嫁。

情況2:對該結(jié)點(diǎn)的左兒子的右子樹進(jìn)行了一次插入秉继。

這種情況是單旋轉(zhuǎn)調(diào)整不回來的,如下圖泽铛,旋轉(zhuǎn)后還是不平衡

8.png

這時(shí)候需要旋轉(zhuǎn)兩次來達(dá)到平衡效果:

9.png

以 k1 為當(dāng)前結(jié)點(diǎn)進(jìn)行左旋尚辑,再以 k3 為當(dāng)前結(jié)點(diǎn)進(jìn)行右旋

代碼實(shí)現(xiàn):

public AVLTree doubleRotateWithRight(PAVLNode unbalancedNode){    //之所以稱為 Right,是因?yàn)樽詈蟛黄胶饨Y(jié)點(diǎn)變?yōu)橛易訕淇唬@樣比較好記
    /* Rotate between K1 and K2 */
    AVLTree node = singleRotateWithLeft(unbalancedNode.leftChild); //返回值為 K2 
    node.parent = unbalancedNode;
    /* Rotate between K3 and K2 */
    return singleRotateWithRight(unbalancedNode);  //最后返回的是 k2 結(jié)點(diǎn)杠茬,即根結(jié)點(diǎn)
}

例如:往該樹中插入結(jié)點(diǎn) 9

4.gif

情況3:對該結(jié)點(diǎn)的右兒子的左子樹進(jìn)行了一次插入。

類似需要旋轉(zhuǎn)兩次來達(dá)到平衡效果:

10.png

以 k3 為當(dāng)前結(jié)點(diǎn)進(jìn)行右旋弛随,再以 k2 為當(dāng)前結(jié)點(diǎn)進(jìn)行左旋

代碼實(shí)現(xiàn):

public AVLTree doubleRotateWithLeft(PAVLNode unbalancedNode){   
    /* Rotate between K3 and K2 */
    AVLTree node = singleRotateWithRight(unbalancedNode.rightChild); //返回值為 K2 
    node.parent = unbalancedNode;
    /* Rotate between K1 and K2 */
    return singleRotateWithLeft(unbalancedNode);  //最后返回的是 k2 結(jié)點(diǎn)瓢喉,即根結(jié)點(diǎn)
}

例如:往該樹中插入結(jié)點(diǎn) 11

3.gif

插入操作:

插入的核心思路是通過遞歸找到合適的位置,插入新結(jié)點(diǎn)舀透,然后看新結(jié)點(diǎn)是否平衡(平衡因子是否為2)栓票,如果不平衡的話,就對其進(jìn)行旋轉(zhuǎn)操作:

  1. 在結(jié)點(diǎn)的左兒子(X < T->item):
    在左兒子的左子樹(X < T->l-> item): “外邊”愕够,要做單旋轉(zhuǎn)逗载。
    在左兒子的右子樹(X > T->l-> item): “內(nèi)部”,要做雙旋轉(zhuǎn)链烈。
  2. 在結(jié)點(diǎn)的右兒子(X > T->item):
    在右兒子的左子樹(X < T->r-> item),“內(nèi)部”挚躯,要做雙旋轉(zhuǎn)强衡。
    在右兒子的右子樹(X > T->r-> item),“外邊”码荔,要做單旋轉(zhuǎn)漩勤。
  3. (X == T->item) ,對該節(jié)點(diǎn)的計(jì)數(shù)進(jìn)行更新缩搅。

課上的兩個(gè)例題:構(gòu)建一棵 AVL 樹

往樹中按順序插入 342, 206, 444, 523, 607, 301, 142, 183, 102, 157, 149

5.gif

往樹中按順序插入 47, 23, 52, 14, 59, 29, 36, 38, 26

6.gif

參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市硼瓣,隨后出現(xiàn)的幾起案子究飞,更是在濱河造成了極大的恐慌置谦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亿傅,死亡現(xiàn)場離奇詭異媒峡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)葵擎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門谅阿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酬滤,你說我怎么就攤上這事签餐。” “怎么了盯串?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵氯檐,是天一觀的道長。 經(jīng)常有香客問我嘴脾,道長男摧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任译打,我火速辦了婚禮耗拓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奏司。我一直安慰自己乔询,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布韵洋。 她就那樣靜靜地躺著竿刁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搪缨。 梳的紋絲不亂的頭發(fā)上食拜,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音副编,去河邊找鬼负甸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛痹届,可吹牛的內(nèi)容都是我干的呻待。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼队腐,長吁一口氣:“原來是場噩夢啊……” “哼蚕捉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柴淘,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤迫淹,失蹤者是張志新(化名)和其女友劉穎秘通,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體千绪,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡充易,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了荸型。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盹靴。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瑞妇,靈堂內(nèi)的尸體忽然破棺而出稿静,到底是詐尸還是另有隱情,我是刑警寧澤辕狰,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布改备,位于F島的核電站,受9級特大地震影響蔓倍,放射性物質(zhì)發(fā)生泄漏悬钳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一偶翅、第九天 我趴在偏房一處隱蔽的房頂上張望默勾。 院中可真熱鬧,春花似錦聚谁、人聲如沸母剥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽环疼。三九已至,卻和暖如春朵耕,著一層夾襖步出監(jiān)牢的瞬間炫隶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工阎曹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伪阶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓芬膝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親形娇。 傳聞我的和親對象是個(gè)殘疾皇子锰霜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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