【数据结构】平衡二叉树(AVL树)详解

平衡二叉树,又称AVL树。是一种高度平衡的二叉排序树。这里的”高度“ 大家理解为 height。也就是节点的左子树和右子树的高度差绝对值不超过1。

重点一:高度平衡

指的是 节点 左子树高 与 右子树高,差值绝对值不超过1。

重点二:AVL的插入函数

先按照二叉树原理插入节点,然后再看当前树是否平衡。

 

******* 一定要自己写一遍 ,重点看插入函数********

 

https://github.com/ai2101039/AVLTree

先上一下结果图

 


好了,开始一步步的写代码。

大家要是了解了原理,其实很简单。真的很简单。

(tree 为树中任意一节点,K1为其子树,K2为插入的节点)

图1:tree 的 左节点 K1,插入K2为K1左孩子,导致Tree节点不平衡。针对这种节点形式,我们使用函数

left_left_rotation(tree); 

图2:tree 的 左节点 K1,插入K2为K1右孩子,导致Tree节点不平衡。

left_right_rotation(tree);

图3:tree 的 右节点K1,插入K2为K1右孩子,导致Tree节点不平衡。

right_right_rotation(tree);

图4:tree 的 右节点K1,插入K2为K1左孩子,导致Tree节点不平衡。

right_left_rotation(tree);

 

重点:这里我们需要记住,函数名称的命名,指的是节点虚拟关系符合如图展示。

为了方式大家被绕晕,我需要解释一波

left_left_rotation(tree);  针对图1展现样式。

那么这时候的旋转 叫做 右旋,即tree 围绕K1,往右旋转,即顺时针旋转。

left_right_rotation(tree);  针对图2展现样式。

这时候不能简单的进行 右旋转,需要先判断 K1 与 K2的关系,这步骤很重要, 在后续函数里面也会做这一步判断。此时可以发现,Tree 与 K1子树 属于 左左样式,而K1与K2属于右右关系,所以需要先将K1进行左旋转,然后再将Tree进行右旋转。

right_right_rotation(tree);  针对图3展现样式。

那么这时候的旋转 叫做 左旋,即tree 围绕K1,往左旋转,即逆时针旋转。

right_left_rotation(tree);  针对图1展现样式。

和图2类似,这时候我们先判断了,tree 与 K1 的样式,符合右右关系,但是K1 与K2 符合 左左关系,这时候需要先将K1进行右旋转,再将Tree进行左旋转。

到了这一步是否还有模糊的地方,没关系,我之前也是有,但是自从我写了一遍代码我就知道,其实很简单。最主要就是一个。

递归思想

我们来看代码

先看插入代码,再看旋转代码。

插入

Node基类,其中Key 是继承自 Comparable,其实很好理解,Node得需要排序。 

class BaseNode<T extends Comparable> {
        BaseNode left;
        BaseNode right;
        int height;
        T key;
    }

我尽量把每一行的解释写的清楚,大家一定要用递归的想法去理解。

   /**
     * 插入
     *
     * @param root 需要和Node进行比较的根节点
     * @param node 需要插入的节点
     * @return
     */
    private BaseNode insertNode(BaseNode root, BaseNode node) {
        if (root != null) {
            /*
             * 如果root不为空,那么就需要比较 root 和 node 的大小
             * 以便于知道 node下一步 是和 root 的左子树做比较 还是右子树比较
             * 这里我们举个例子,如果 node 应该插入 root 的左侧,是不是下一步应该 root.left和node 做比较
             * 而如果 root.left 如果是null,那么 是不是 root.left = node,也就是把node赋值给 root.left
             */

            //比较 node 和 root 中 key 的值,这里的返回值是我们 实现接口Comparable,去做的,参考 AVLNode.AVLComparable
            int cmp = node.key.compareTo(root.key);

            //cmp < 0,说明root的key值小于node的key值,node 插入到 root 的左子树,也就是node 要和 root.left做比较
            if (cmp < 0) {
                /*
                 * 这里就开始使用递归了,同时需要看到有赋值的动作
                 * 这里需要好好理解一下,递归的核心思想是往能终结递归运算的方向进行,那我们想,如何才能结束
                 * 只有当root 为null 时,这时候递归才算走到终结方向。
                 */
                root.left = insertNode(root.left, node);
                /*
                 * ***********************   重要   ***************************
                 * 这里插入完成了,然后我们就看是否打破的平衡
                 * 因为是插入到左子树所以使用 height(root.left) - height(root.right)
                 * 如果等于2,那当前root 也就是最小不平衡子树。需要将 root节点进行旋转。然后再赋值给root
                 * 另外,(左子树 - 右子树 = 2) 的情况有两种。
                 * 所以需要判断 root 的左子树K1,看看 K2插入到 K1的那个地方(root,k1,k2 看文章内解释)
                 * 这时候如果 (K1 的右孩子 - K1的左孩子 == 1)
                 * 说明K2插入到K1的右侧了,这时候 tree  K1  K2 三者符合 左右关系图,所以调用 left_right_Rotation(root)
                 *
                 * else 使用 left_left_Rotation(root);
                 * 一定要记得无论怎么旋转都要给 root赋值
                 * ***********************   重要   ***************************
                 */
                if (height(root.left) - height(root.right) == 2) {
                    //符合 左子树的右子树导致不平衡
                    if (height(root.left.right) - height(root.left.left) == 1) {
                        root = left_right_Rotation(root);
                    } else {
                        root = left_left_Rotation(root);
                    }
                }
            } else if (cmp > 0) {
                /*
                 * 与上面解释异曲同工
                 */
                root.right = insertNode(root.right, node);
                //右子树的插入导致不平衡
                if (height(root.right) - height(root.left) == 2) {
                    //右子树的左子树导致不平衡
                    if (height(root.right.left) - height(root.right.right) == 1) {
                        root = right_left_Rotation(root);
                    } else {
                        root = right_right_Rotation(root);
                    }
                }
            } else {
                //节点相同
            }
        } else {
            //如果root为空,则将node赋值给root
            root = node;
        }
        /*
         * 这一步很重要
         * 我们说节点插入完成后,高度就需要有个改变,那高度怎么计算,当然是左子树的高和右子树的高,取最大值,然后 + 1
         */
        root.height = max(height(root.left), height(root.right)) + 1;
        return root;
    }

    /**
     * 外部使用,因为无论如何插入,首先肯定要和 mRoot进行比较,同时mRoot也要随时等待被重新赋值
     *
     * @param node
     */
    public void insert(BaseNode node) {
        mRoot = insertNode(mRoot, node);
    }

剩下的就是旋转的代码了,其实旋转大家都了解了。我就不解释了。

我把代码放github了

https://github.com/ai2101039/AVLTree

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值