(22)Go實(shí)現(xiàn)AVL樹-實(shí)現(xiàn)和測試

繼上一篇 《(21)Go實(shí)現(xiàn)AVL樹-算法解析》 的后續(xù)
http://www.reibang.com/p/943243f5ee1e

AVL樹的定義和實(shí)現(xiàn)
type node struct {
    key    int
    value  int
    height int
    left   *node
    right  *node
}

type avlTree struct {
    size int
    Root *node
}

func NewAvlTree() *avlTree {
    return new(avlTree)
}

func (tree *avlTree) GetSize() int {
    return tree.size
}

// 獲取高度
func (nd *node) getHeight() int {
    if nd == nil {
        return 0
    }
    return nd.height
}

// 獲取的平衡因子
func (nd *node) getBalanceFactor() int {
    if nd == nil {
        return 0
    }
    return nd.left.getHeight() - nd.right.getHeight()
}

func max(int1, int2 int) int {
    if int1 >= int2 {
        return int1
    }
    return int2
}

// 添加/更新節(jié)點(diǎn)
func (tree *avlTree) Add(key, val int) {
    isAdd, nd := tree.Root.add(key, val)
    tree.size += isAdd
    tree.Root = nd
}

// 遞歸寫法:向樹的root節(jié)點(diǎn)中插入key,val
// 返回1,代表加了節(jié)點(diǎn)
// 返回0,代表沒有添加新節(jié)點(diǎn),只更新key對應(yīng)的value值
func (nd *node) add(key, val int) (int, *node) {
    if nd == nil {
        return 1, &node{key, val, 1, nil, nil}
    }

    isAdd := 0
    if key < nd.key {
        isAdd, nd.left = nd.left.add(key, val)
    } else if key > nd.key {
        isAdd, nd.right = nd.right.add(key, val)
    } else { // nd.key == key
        // 對value值更新,節(jié)點(diǎn)數(shù)量不增加,isAdd = 0
        nd.value = val
    }

    // 更新節(jié)點(diǎn)高度和維護(hù)平衡
    nd = nd.updateHeightAndBalance(isAdd)
    return isAdd, nd
}

func (tree *avlTree) Remove(key int) error {
    if tree.Root == nil {
        return errors.New(
            "failed to remove,avlTree is empty.")
    }
    var isRemove int
    isRemove, tree.Root = tree.Root.remove(key)
    tree.size -= isRemove
    return nil
}

// 刪除nd為根節(jié)點(diǎn)中key節(jié)點(diǎn),返回更新了高度和維持平衡的新nd節(jié)點(diǎn)
// 返回值int == 1 表明有節(jié)點(diǎn)被刪除,0 表明沒有節(jié)點(diǎn)被刪除
func (nd *node) remove(key int) (int, *node) {
    // 找不到key對應(yīng)node,返回0,nil
    if nd == nil {
        return 0, nil
    }

    var retNode *node
    var isRemove int
    switch {
    case key < nd.key:
        isRemove, nd.left = nd.left.remove(key)
        retNode = nd
    case key > nd.key:
        isRemove, nd.right = nd.right.remove(key)
        retNode = nd
    default:
        switch {
        case nd.left == nil: // 待刪除節(jié)點(diǎn)左子樹為空的情況
            retNode = nd.right
        case nd.right == nil: // 待刪除節(jié)點(diǎn)右子樹為空的情況
            retNode = nd.left
        default:
            // 待刪除節(jié)點(diǎn)左右子樹均不為空的情況
            // 找到比待刪除節(jié)點(diǎn)大的最小節(jié)點(diǎn),即右子樹的最小節(jié)點(diǎn)
            retNode := nd.right.getMinNode()
            // TODO: 這步好好理解,維護(hù)平衡性
            _, retNode.right = nd.right.remove(key)

            retNode.left = nd.left
        }
        isRemove = 1
    }

    // 前面刪除節(jié)點(diǎn)后,返回retNode有可能為空,這樣在執(zhí)行下面的語句會產(chǎn)生異常,加這步判定避免異常
    if retNode == nil {
        return isRemove, retNode
    }

    retNode = retNode.updateHeightAndBalance(isRemove)
    return isRemove, retNode
}

// 對節(jié)點(diǎn)y進(jìn)行向右旋轉(zhuǎn)操作砸民,返回旋轉(zhuǎn)后新的根節(jié)點(diǎn)x
//        y                              x
//       / \                           /   \
//      x   T4     向右旋轉(zhuǎn) (y)        z     y
//     / \       - - - - - - - ->    / \   / \
//    z   T3                       T1  T2 T3 T4
//   / \
// T1   T2
func (y *node) rightRotate() *node {
    x := y.left
    y.left = x.right
    x.right = y

    // 更新height值: 旋轉(zhuǎn)后只有x,y的子樹發(fā)生變化,所以只更新x,y的height值
    // x的height值和y的height值相關(guān),先更新y,再更新x
    y.height = 1 + max(y.left.getHeight(), y.right.getHeight())
    x.height = 1 + max(x.left.getHeight(), x.right.getHeight())
    return x
}

// 對節(jié)點(diǎn)y進(jìn)行向左旋轉(zhuǎn)操作探熔,返回旋轉(zhuǎn)后新的根節(jié)點(diǎn)x
//    y                             x
//  /  \                          /   \
// T1   x      向左旋轉(zhuǎn) (y)       y     z
//     / \   - - - - - - - ->   / \   / \
//   T2  z                     T1 T2 T3 T4
//      / \
//     T3 T4
func (y *node) leftRotate() *node {
    x := y.right
    y.right = x.left
    x.left = y

    // 更新height值
    y.height = 1 + max(y.left.getHeight(), y.right.getHeight())
    x.height = 1 + max(x.left.getHeight(), x.right.getHeight())
    return x
}

func (tree *avlTree) Contains(key int) bool {
    return tree.Root.contains(key)
}

// 以root為根的樹中是否包含key節(jié)點(diǎn)
func (nd *node) contains(key int) bool {
    if nd == nil {
        return false
    }

    if nd.key == key {
        return true
    }

    if key < nd.key {
        return nd.left.contains(key)
    }
    return nd.right.contains(key)
}

// 中序遍歷打印出key,val,height
func (tree *avlTree) PrintInOrder() {
    resp := [][]int{}
    tree.Root.printInOrder(&resp)
    fmt.Println(resp)
}

func (nd *node) printInOrder(resp *[][]int) {
    if nd == nil {
        return
    }
    nd.left.printInOrder(resp)
    *resp = append(*resp, []int{nd.key, nd.value, nd.height})
    nd.right.printInOrder(resp)
}

// 中序遍歷所有key,用來輔助判斷是否是二分搜索樹
func (nd *node) traverseInOrderKey(resp *[]int) {
    if nd == nil {
        return
    }

    nd.left.traverseInOrderKey(resp)
    *resp = append(*resp, nd.key)
    nd.right.traverseInOrderKey(resp)
}

// 判斷avlTree是否仍然是一顆二分搜索樹
// 思路: 二分搜索數(shù)如果用中序遍歷時(shí),所有元素都是從小到大排列
func (tree *avlTree) IsBST() bool {
    buf := []int{}
    tree.Root.traverseInOrderKey(&buf)
    length := len(buf)
    for i := 1; i < length; i++ {
        if buf[i-1] > buf[i] {
            return false
        }
    }
    return true
}

// 判斷avlTree是否是一顆平衡二叉樹(每個節(jié)點(diǎn)的左右子樹高度差不能超過1)
func (tree *avlTree) IsBalanced() bool {
    return tree.Root.isBalanced()
}

func (nd *node) isBalanced() bool {
    if nd == nil {
        return true
    }

    if nd.getBalanceFactor() > 1 || nd.getBalanceFactor() < int(-1) {
        return false
    }
    // 能到這里說明:當(dāng)前該節(jié)點(diǎn)滿足平衡二叉樹條件
    // 再去判斷該節(jié)點(diǎn)的左右子樹是否也是平衡二叉樹
    return nd.left.isBalanced() && nd.right.isBalanced()
}

// 找出以nd為根節(jié)點(diǎn)中最小值的節(jié)點(diǎn)
func (nd *node) getMinNode() *node {
    if nd.left == nil {
        return nd
    }
    return nd.left.getMinNode()
}

// 更新節(jié)點(diǎn)高度和維護(hù)平衡
func (nd *node) updateHeightAndBalance(isChange int) *node {
    // 0說明無改變,不必更新
    if isChange == 0 {
        return nd
    }

    // 更新高度
    nd.height = 1 + max(nd.left.getHeight(),
        nd.right.getHeight())

    // 平衡維護(hù)
    if nd.getBalanceFactor() > 1 {
        // 左左LL
        if nd.left.getBalanceFactor() >= 0 {
            nd = nd.rightRotate()
        } else { // 左右LR
            nd.left = nd.left.leftRotate()
            nd = nd.rightRotate()
        }
        return nd
    }

    if nd.getBalanceFactor() < int(-1) {
        // 右右RR
        if nd.right.getBalanceFactor() <= 0 {
            nd = nd.leftRotate()
        } else { // 右左RL
            nd.right = nd.right.rightRotate()
            nd = nd.leftRotate()
        }
    }
    return nd
}
測試代碼
    a := avltree1.NewAvlTree()
    nums := []int{}
    a.PrintInOrder()
    fmt.Println("----")
    for i := 0; i < 100000; i++ {
        a.Add(100000, 0)
        if !a.IsBST() || !a.IsBalanced() {
            fmt.Println("代碼有錯誤", a.IsBST(), a.IsBalanced())
            break
        }
    }

    for i := 0; i < 200000; i++ {
        nums = append(nums, rand.Intn(200000))
    }
    for _, v := range nums {
        a.Remove(v)
        if !a.IsBST() || !a.IsBalanced() {
            fmt.Println("代碼有錯誤", a.IsBST(), a.IsBalanced())
            break
        }
    }

    fmt.Println("測試結(jié)束")
    fmt.Println("is BST:", a.IsBST())
    fmt.Println("is Balanced:", a.IsBalanced())
}
測試結(jié)果
[]
----
測試結(jié)束
is BST: true
is Balanced: true

有bug歡迎指出

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拢蛋,隨后出現(xiàn)的幾起案子玄妈,更是在濱河造成了極大的恐慌凰荚,老刑警劉巖曲伊,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叽讳,死亡現(xiàn)場離奇詭異,居然都是意外死亡坟募,警方通過查閱死者的電腦和手機(jī)岛蚤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懈糯,“玉大人灭美,你說我怎么就攤上這事“豪” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵铁坎,是天一觀的道長蜂奸。 經(jīng)常有香客問我,道長硬萍,這世上最難降的妖魔是什么扩所? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮朴乖,結(jié)果婚禮上祖屏,老公的妹妹穿的比我還像新娘。我一直安慰自己买羞,他們只是感情好袁勺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畜普,像睡著了一般期丰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天钝荡,我揣著相機(jī)與錄音街立,去河邊找鬼。 笑死埠通,一個胖子當(dāng)著我的面吹牛赎离,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播端辱,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼梁剔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了掠手?” 一聲冷哼從身側(cè)響起憾朴,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喷鸽,沒想到半個月后众雷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡做祝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年砾省,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片混槐。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡编兄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出声登,到底是詐尸還是另有隱情狠鸳,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布悯嗓,位于F島的核電站件舵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脯厨。R本人自食惡果不足惜铅祸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望合武。 院中可真熱鬧临梗,春花似錦、人聲如沸稼跳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汤善。三九已至茫经,卻和暖如春巷波,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卸伞。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工抹镊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荤傲。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓垮耳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遂黍。 傳聞我的和親對象是個殘疾皇子终佛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348