(22)Go实现AVL树-实现和测试

继上一篇 《(21)Go实现AVL树-算法解析》 的后续
https://www.jianshu.com/p/943243f5ee1e

AVL树的定义和实现
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
}

// 添加/更新节点
func (tree *avlTree) Add(key, val int) {
    isAdd, nd := tree.Root.add(key, val)
    tree.size += isAdd
    tree.Root = nd
}

// 递归写法:向树的root节点中插入key,val
// 返回1,代表加了节点
// 返回0,代表没有添加新节点,只更新key对应的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值更新,节点数量不增加,isAdd = 0
        nd.value = val
    }

    // 更新节点高度和维护平衡
    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为根节点中key节点,返回更新了高度和维持平衡的新nd节点
// 返回值int == 1 表明有节点被删除,0 表明没有节点被删除
func (nd *node) remove(key int) (int, *node) {
    // 找不到key对应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: // 待删除节点左子树为空的情况
            retNode = nd.right
        case nd.right == nil: // 待删除节点右子树为空的情况
            retNode = nd.left
        default:
            // 待删除节点左右子树均不为空的情况
            // 找到比待删除节点大的最小节点,即右子树的最小节点
            retNode := nd.right.getMinNode()
            // TODO: 这步好好理解,维护平衡性
            _, retNode.right = nd.right.remove(key)

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

    // 前面删除节点后,返回retNode有可能为空,这样在执行下面的语句会产生异常,加这步判定避免异常
    if retNode == nil {
        return isRemove, retNode
    }

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

// 对节点y进行向右旋转操作,返回旋转后新的根节点x
//        y                              x
//       / \                           /   \
//      x   T4     向右旋转 (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值: 旋转后只有x,y的子树发生变化,所以只更新x,y的height值
    // x的height值和y的height值相关,先更新y,再更新x
    y.height = 1 + max(y.left.getHeight(), y.right.getHeight())
    x.height = 1 + max(x.left.getHeight(), x.right.getHeight())
    return x
}

// 对节点y进行向左旋转操作,返回旋转后新的根节点x
//    y                             x
//  /  \                          /   \
// T1   x      向左旋转 (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节点
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是否仍然是一颗二分搜索树
// 思路: 二分搜索数如果用中序遍历时,所有元素都是从小到大排列
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是否是一颗平衡二叉树(每个节点的左右子树高度差不能超过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
    }
    // 能到这里说明:当前该节点满足平衡二叉树条件
    // 再去判断该节点的左右子树是否也是平衡二叉树
    return nd.left.isBalanced() && nd.right.isBalanced()
}

// 找出以nd为根节点中最小值的节点
func (nd *node) getMinNode() *node {
    if nd.left == nil {
        return nd
    }
    return nd.left.getMinNode()
}

// 更新节点高度和维护平衡
func (nd *node) updateHeightAndBalance(isChange int) *node {
    // 0说明无改变,不必更新
    if isChange == 0 {
        return nd
    }

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

    // 平衡维护
    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("测试结束")
    fmt.Println("is BST:", a.IsBST())
    fmt.Println("is Balanced:", a.IsBalanced())
}
测试结果
[]
----
测试结束
is BST: true
is Balanced: true

有bug欢迎指出

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342