golang 手?jǐn)] 平衡二叉樹

golang 手?jǐn)] 平衡二叉樹

樹是一種計(jì)算機(jī)數(shù)據(jù)結(jié)構(gòu)中非常常用的一種結(jié)構(gòu),其中就包含了:平衡二叉樹,這種樹是一種特殊的二叉查找樹(二叉查找樹也就是,右孩子大于其父結(jié)點(diǎn)遗菠,左孩子小于其父結(jié)點(diǎn)的樹),但是簡(jiǎn)單的二叉查找樹存在的問(wèn)題就是不平衡华蜒,最差的查找效率為O(n)辙纬,故就有人發(fā)明了一種平衡的額二叉查找樹。

特點(diǎn)

  1. 平衡二叉樹是一種二叉查找樹
  2. 每個(gè)結(jié)點(diǎn)的左子樹的高度減去右子樹的高度的絕對(duì)值不超過(guò)1
  3. 空樹和左右子樹都是平衡二叉樹
  4. 相比紅黑樹叭喜,平衡二叉樹比較適用于沒(méi)有刪除的情況

平衡因子

平衡二叉樹是在二叉查查找樹的基礎(chǔ)上進(jìn)行構(gòu)建了贺拣,為了維持平衡二叉樹的平衡,那么就需要一種機(jī)制來(lái)判斷平衡二叉樹是否是平衡的域滥。這種機(jī)制就叫做平衡因子纵柿。

平衡二叉樹的每個(gè)結(jié)點(diǎn)都會(huì)維持一個(gè)值,這個(gè)值就是平衡因子启绰,這個(gè)平衡因子就是這個(gè)結(jié)點(diǎn)的左子樹的高度減去右子樹的高度得到的值昂儒。如果這個(gè)平衡因子的值的絕對(duì)值大于1了,說(shuō)明這個(gè)樹就不平衡了委可,那么就需要調(diào)整樹的結(jié)構(gòu)了渊跋。

我們可以從如上這個(gè)這個(gè)圖中看的到:每個(gè)結(jié)點(diǎn)都維持了一個(gè)值腊嗡,比如左邊的AVL 樹根結(jié)點(diǎn)的值為-1,這個(gè)-1是怎么來(lái)的呢拾酝,就是結(jié)點(diǎn)3的左子樹的高度為 2, 右子樹的高度為 3, 左子樹的高度減去右子樹的高度就得到這個(gè)結(jié)點(diǎn)的平衡因子燕少。

如果某個(gè)結(jié)點(diǎn)的平衡因子的絕對(duì)值大于了 1 ,那么就說(shuō)明這個(gè)平衡二叉樹不平衡了蒿囤,就需要調(diào)整平衡二叉樹的結(jié)構(gòu)客们。

旋轉(zhuǎn)

由于AVL樹需要做到平衡,所以每次插入葉子節(jié)點(diǎn)材诽,如果發(fā)現(xiàn)不平衡底挫,都需要進(jìn)行旋轉(zhuǎn)以保持平衡。

找到了要給別人做的一個(gè)旋轉(zhuǎn)圖例脸侥,可以參考的看下:

我們來(lái)總結(jié)下以上圖片中出現(xiàn)的這幾種情況:

  1. 三個(gè)結(jié)點(diǎn)的單旋轉(zhuǎn)


  • 我們可以看到上途中結(jié)點(diǎn)3的平衡因子為2了建邓,這樣就不平衡橫了,那么就需要進(jìn)行一個(gè)對(duì)結(jié)點(diǎn)3的順時(shí)針旋轉(zhuǎn)睁枕,旋轉(zhuǎn)完的結(jié)果就是上圖中的右圖官边。
  1. 三個(gè)結(jié)點(diǎn)的雙旋轉(zhuǎn)


  • 針對(duì)這種情況其實(shí)就是上面一種情況的特殊版本:我們要先把這種情況先轉(zhuǎn)化為:三個(gè)結(jié)點(diǎn)的單旋轉(zhuǎn)這種情況。
  • 首先對(duì)結(jié)點(diǎn)11進(jìn)行一個(gè)逆時(shí)針的旋轉(zhuǎn)外遇,然后就變?yōu)榱耍?code>三個(gè)結(jié)點(diǎn)的單旋轉(zhuǎn)注簿。
  • 然后再像;三個(gè)結(jié)點(diǎn)的單旋轉(zhuǎn) 一樣的對(duì)結(jié)點(diǎn)3進(jìn)行右旋轉(zhuǎn)臀规。

什么時(shí)候怎樣旋轉(zhuǎn)呢?

  1. 我們插入一個(gè)結(jié)點(diǎn)后滩援,某個(gè)結(jié)點(diǎn)p的平衡因子由原來(lái)的1變成了2栅隐。

    • 那就可能是這兩種情況:
    1. 新插入的結(jié)點(diǎn)插入到了結(jié)點(diǎn)p的左子樹的左孩子下

      • golang 代碼實(shí)現(xiàn)

        // 順時(shí)針旋轉(zhuǎn)塔嬉,右旋
        func (avlNode *AVLNode) RightRotate() *AVLNode {
            headNode := avlNode.Left
            avlNode.Left = headNode.Right
            headNode.Right = avlNode
        
            // 更新旋轉(zhuǎn)后結(jié)點(diǎn)的高度
            avlNode.height = Max(avlNode.Left.GetHeight(), avlNode.Right.GetHeight())+1
            headNode.height = Max(headNode.Left.GetHeight(), headNode.Right.GetHeight())+1
        
            return headNode
        }
        
    2. 新插入的結(jié)點(diǎn)插入到了結(jié)點(diǎn)p的左子樹的右孩子下

      • 從以上例子中我們就可以發(fā)現(xiàn),順時(shí)針旋轉(zhuǎn)和逆時(shí)針旋轉(zhuǎn)的規(guī)律所在

      • golang 代碼實(shí)現(xiàn)

        // 先逆時(shí)針旋轉(zhuǎn)再順時(shí)針旋轉(zhuǎn)租悄,先左旋谨究,在右旋
        func (avlNode *AVLNode) LeftThenRightRotate() *AVLNode {
            // 先把左孩子結(jié)點(diǎn)進(jìn)行左旋
            avlNode.Left = avlNode.Left.LeftRotate()
            // 然后把自己右旋
            return avlNode.RightRotate()
        }
        
    • 從以上的三張圖中我們就可以發(fā)現(xiàn)這樣的一個(gè)容易忽略的問(wèn)題
      1. 假設(shè)有一個(gè)結(jié)點(diǎn),這個(gè)結(jié)點(diǎn)是P泣棋,我們?cè)谀鏁r(shí)針旋轉(zhuǎn)一個(gè)結(jié)點(diǎn)的時(shí)候需要把結(jié)點(diǎn)P的右孩子的左子樹移動(dòng)為結(jié)點(diǎn)P的右子樹
      2. 假設(shè)有一個(gè)結(jié)點(diǎn)胶哲,這個(gè)結(jié)點(diǎn)是P,我們?cè)陧槙r(shí)針旋轉(zhuǎn)時(shí)潭辈,需要把P結(jié)點(diǎn)的左孩子的右子樹移動(dòng)為P結(jié)點(diǎn)的左子樹
  2. 我們插入一個(gè)結(jié)點(diǎn)后鸯屿,某個(gè)結(jié)點(diǎn)p的平衡因子由原來(lái)的-1變成了-2。

    • 那就可能是這兩種情況:
    1. 新插入的結(jié)點(diǎn)插入到了結(jié)點(diǎn)p的右子樹的右孩子下

      • 那就應(yīng)該進(jìn)行對(duì)結(jié)點(diǎn)p的逆時(shí)針旋轉(zhuǎn)
      • 旋轉(zhuǎn)過(guò)程可以參考上圖
      • golang 實(shí)現(xiàn)
        // 逆時(shí)針旋轉(zhuǎn)把敢,左旋
        func (avlNode *AVLNode) LeftRotate() *AVLNode {
            headNode := avlNode.Right
            avlNode.Right = headNode.Left
            headNode.Left = avlNode
        
            // 更新結(jié)點(diǎn)的高度
            // 這里應(yīng)該注意的俄式應(yīng)該先更新avlNode 的高度寄摆,因?yàn)閔eadNode結(jié)點(diǎn)在avlNode結(jié)點(diǎn)的上面
            // headNode計(jì)算高度的時(shí)候要根據(jù)avlNode的高度來(lái)計(jì)算
            avlNode.height = Max(avlNode.Left.GetHeight(), avlNode.Right.GetHeight())+1
            headNode.height = Max(headNode.Left.GetHeight(), headNode.Right.GetHeight())+1
        
            return headNode
        }
        
    2. 新插入的結(jié)點(diǎn)插入到了結(jié)點(diǎn)p的右子樹的左孩子下

      • 那就因該先對(duì)結(jié)點(diǎn)p的右孩子進(jìn)行順時(shí)針旋轉(zhuǎn)
      • 然后在對(duì)結(jié)點(diǎn)p進(jìn)行逆時(shí)針旋轉(zhuǎn)
      • 旋轉(zhuǎn)過(guò)程可以參考上圖
      • golang 實(shí)現(xiàn)
        // 先順時(shí)針旋轉(zhuǎn)再逆時(shí)針旋轉(zhuǎn),先右旋修赞,再左旋
        func (avlNode *AVLNode) RightThenLeftRotate() *AVLNode {
            // 先把右孩子進(jìn)行右旋
            avlNode.Right = avlNode.Right.RightRotate()
            // 然后把自己右旋
            return avlNode.LeftRotate()
        }
        

上面我們展示了針對(duì)具體的四種情況婶恼,是怎么怎么旋轉(zhuǎn)的,但是我們要寫一個(gè)函數(shù)用來(lái)判斷這四種情況:

// 調(diào)整AVL樹的平衡
func (avlNode *AVLNode) adjust() *AVLNode {
    // 如果右子樹的高度比左子樹的高度大于2
    if avlNode.Right.GetHeight() - avlNode.Left.GetHeight() == 2 {
        // 如果 avlNode.Right 的右子樹的高度比avlNode.Right的左子樹高度大
        // 直接對(duì)avlNode進(jìn)行左旋轉(zhuǎn)
        // 否則先對(duì) avlNode.Right進(jìn)行右旋轉(zhuǎn)然后再對(duì)avlNode進(jìn)行左旋轉(zhuǎn)
        if avlNode.Right.Right.GetHeight() > avlNode.Right.Left.GetHeight() {
            avlNode = avlNode.LeftRotate()
        }else{
            avlNode = avlNode.RightThenLeftRotate()
        }
        // 如果左子樹的高度比右子樹的高度大2
    }else if avlNode.Right.GetHeight() - avlNode.Left.GetHeight() == -2 {
        // 如果avlNode.Left的左子樹高度大于avlNode.Left的右子樹高度
        // 那么就直接對(duì)avlNode進(jìn)行右旋
        // 否則先對(duì)avlNode.Left進(jìn)行左旋,然后對(duì)avlNode進(jìn)行右旋
        if avlNode.Left.Left.GetHeight() > avlNode.Left.Right.GetHeight() {
            avlNode = avlNode.RightRotate()
        }else {
            avlNode = avlNode.LeftThenRightRotate()
        }
    }

    return avlNode
}

golang 實(shí)現(xiàn)

AVLNode

type AVLNode struct {
    Left, Right *AVLNode    // 表示指向左孩子和右孩子
    Data        interface{} // 結(jié)點(diǎn)存儲(chǔ)數(shù)據(jù)
    height      int         // 記錄這個(gè)結(jié)點(diǎn)此時(shí)的高度
}

AVL樹還需要一些簡(jiǎn)單的獲取和設(shè)置結(jié)點(diǎn)性質(zhì)的方法

注意:每次NewAVLNode的時(shí)候height一定是1勾邦,因?yàn)橐粋€(gè)簡(jiǎn)單的高度最低就是1

// 定義comparer 指針類型
// 用來(lái)比較兩個(gè)結(jié)點(diǎn)中Data的大小
type comparator func(a, b interface{}) int

// compare 指針
var compare comparator

// 新建一個(gè)結(jié)點(diǎn)
func NewAVLNode(data interface{}) *AVLNode {
    return &AVLNode{
        Left:   nil,
        Right:  nil,
        Data:   data,
        height: 1,
    }
}

// 新建AVL 樹
func NewAVLTree(data interface{}, myfunc comparator) (*AVLNode, error) {
    if data == nil && myfunc == nil {
        return nil, errors.New("不能為空")
    }
    compare = myfunc
    return NewAVLNode(data), nil
}

// 獲取結(jié)點(diǎn)數(shù)據(jù)
func (avlNode *AVLNode) GetData() interface{} {
    return avlNode.Data
}

// 設(shè)置結(jié)點(diǎn)數(shù)據(jù)
func (avlNode *AVLNode) SetData(data interface{}) {
    if avlNode == nil {
        return
    }
    avlNode.Data = data
}

// 獲取結(jié)點(diǎn)的右孩子結(jié)點(diǎn)
func (avlNode *AVLNode) GetRight() *AVLNode {
    if avlNode == nil {
        return nil
    }
    return avlNode.Right
}

// 獲取結(jié)點(diǎn)的左孩子結(jié)點(diǎn)
func (avlNode *AVLNode) GetLeft() *AVLNode {
    if avlNode == nil {
        return nil
    }
    return avlNode.Left
}

// 獲取結(jié)點(diǎn)的高度
func (avlNode *AVLNode) GetHeight() int {
    if avlNode == nil {
        return 0
    }
    return avlNode.height
}

//比較兩個(gè)子樹高度的大小
func Max(a, b int) int {
    if a >= b {
        return a
    } else {
        return b
    }
}

查找結(jié)點(diǎn)

查找指定結(jié)點(diǎn)

// 查找指定值
func (avlNode *AVLNode) Find(data interface{}) *AVLNode {
    var finded *AVLNode
    // 調(diào)用比較函數(shù)比較兩個(gè)結(jié)點(diǎn)的指的大小
    switch compare(data, avlNode.Data) {
    case -1:
        finded = avlNode.Left.Find(data)
    case 1:
        finded = avlNode.Right.Find(data)
    case 0:
        return avlNode
    }

    return finded
}

查找最大結(jié)點(diǎn)

// 查找最大值
func (avlNode *AVLNode) FindMax() *AVLNode {
    var finded *AVLNode

    if avlNode.Right != nil {
        finded = avlNode.Right.FindMin()
    } else {
        finded = avlNode
    }

    return finded
}

查找最小結(jié)點(diǎn)

// 查找最小值
func (avlNode *AVLNode) FindMin() *AVLNode { // 遞歸寫法
    var finded *AVLNode

    if avlNode.Left != nil {
        finded = avlNode.Left.FindMin()
    } else {
        finded = avlNode
    }

    return finded
}

插入和刪除

插入

// 插入數(shù)據(jù)
// 因?yàn)闆](méi)有定義 結(jié)點(diǎn)的parent指針蚣录,所以你插入數(shù)據(jù)就只能遞歸的插入,因?yàn)槲乙{(diào)整樹的平衡和高度
func (avlNode *AVLNode) Insert(value interface{}) *AVLNode {
    if avlNode == nil {
        return NewAVLNode(value)
    }

    switch compare(value, avlNode.Data) {
    case -1:
        // 如果value 小于 avlNode.Data 那么就在avlNode的左子樹上去插入value
        avlNode.Left = avlNode.Left.Insert(value)
        avlNode = avlNode.adjust() // 自動(dòng)調(diào)整平衡
    case 1:
        avlNode.Right = avlNode.Right.Insert(value)
        avlNode = avlNode.adjust()
    case 0:
        fmt.Print("數(shù)據(jù)已經(jīng)存在")
    }
    // 修改結(jié)點(diǎn)的高度
    avlNode.height = Max(avlNode.Left.GetHeight(), avlNode.Right.GetHeight()) + 1

    return avlNode
}

插入數(shù)據(jù)的時(shí)候我們應(yīng)該注意的是眷篇,我們要針對(duì)插入點(diǎn)相關(guān)的父結(jié)點(diǎn)萎河,都要判斷是否平衡,然后就行平衡的調(diào)整蕉饼。

刪除

// 刪除數(shù)據(jù)
func (avlNode *AVLNode) Delete(value interface{}) *AVLNode {
    // 沒(méi)有找到匹配的數(shù)據(jù)
    if avlNode == nil {
        //fmt.Println("不存在", value)
        return nil
    }

    switch compare(value, avlNode.Data) {
    case 1:
        avlNode.Right = avlNode.Right.Delete(value)
    case -1:
        avlNode.Left = avlNode.Left.Delete(value)
    case 0:
        // 找到數(shù)據(jù)公壤,刪除結(jié)點(diǎn)
        if avlNode.Left != nil && avlNode.Right != nil { // 結(jié)點(diǎn)有左孩子和右孩子
            avlNode.Data = avlNode.Right.FindMin().Data
            avlNode.Right = avlNode.Right.Delete(avlNode.Data)
        } else if avlNode.Left != nil { // 結(jié)點(diǎn)只有左孩子,無(wú)右孩子
            avlNode = avlNode.Left
        } else { // 結(jié)點(diǎn)只有右孩子或者無(wú)孩子
            avlNode = avlNode.Right
        }

    }

    // 自動(dòng)調(diào)整平衡, 如果avlNode!=nil說(shuō)明執(zhí)行了對(duì)avlNode 的某個(gè)子樹執(zhí)行了刪除結(jié)點(diǎn)椎椰,那么就需要重新調(diào)整樹的平衡
    if avlNode != nil {
        avlNode.height = Max(avlNode.Left.GetHeight(), avlNode.Right.GetHeight()) + 1
        avlNode = avlNode.adjust() // 自動(dòng)平衡
    }

    return avlNode
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末厦幅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子慨飘,更是在濱河造成了極大的恐慌确憨,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓤的,死亡現(xiàn)場(chǎng)離奇詭異休弃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)圈膏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門塔猾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人稽坤,你說(shuō)我怎么就攤上這事丈甸。” “怎么了尿褪?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵睦擂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我杖玲,道長(zhǎng)顿仇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任摆马,我火速辦了婚禮臼闻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘囤采。我一直安慰自己述呐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布斑唬。 她就那樣靜靜地躺著市埋,像睡著了一般黎泣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缤谎,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天抒倚,我揣著相機(jī)與錄音,去河邊找鬼坷澡。 笑死托呕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的频敛。 我是一名探鬼主播项郊,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斟赚!你這毒婦竟也來(lái)了着降?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拗军,失蹤者是張志新(化名)和其女友劉穎任洞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體发侵,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡交掏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刃鳄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盅弛。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖叔锐,靈堂內(nèi)的尸體忽然破棺而出挪鹏,到底是詐尸還是另有隱情,我是刑警寧澤掌腰,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布狰住,位于F島的核電站张吉,受9級(jí)特大地震影響齿梁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肮蛹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一勺择、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伦忠,春花似錦省核、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)邻储。三九已至,卻和暖如春旧噪,著一層夾襖步出監(jiān)牢的瞬間吨娜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工淘钟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宦赠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓米母,卻偏偏與公主長(zhǎng)得像勾扭,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铁瞒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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