学习数据结构笔记(12) --- [平衡二叉搜索树(AVLTREE)]

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习数据结构笔记(12) --- [平衡二叉搜索树(AVLTREE)]相关的知识,希望对你有一定的参考价值。

B站学习传送门–>尚硅谷Java数据结构与java算法(Java数据结构与算法)



案例情景引入

比如给出一组数字1,2,3,4,5,6,要把它排成二叉搜索树;那是不是就得变成下面这样的树;

仔细看的话,比较像是单向链表;增删比较快,但是查询时比较慢;由于树在查询时还要检索,效率甚至会更慢;

平衡二叉搜索树(Self-balancing binary search tree);左右两棵子树的高度之差不会超过1;
平衡二叉树的常用实现有AVL树,红黑树,伸展树.

AVL树是最先发明的自平衡二叉搜索树(平衡二叉查找树)。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树;
前提是这棵树是一棵二叉搜索树

做几个案例图看看;

左旋转图解

例如,有个数列 4,3,6,5,7,8;
形成二分搜索树如下所示,但是不符合平衡树规则

那么就需要进行左旋转

首先将当前树的根结点,复制一份,创建一个新节点;4

然后,将当前节点 4左子节点作为新节点 4 的左子节点;

当前节点4右子节点6左子节点 5 作为新节点 4 的右子节点

然后再让当前节点4右子节点6替换当前结点4

当前结点6右子节点6右子节点7 作为当前节点6右子节点

新节点4作为当前结点6的左子节点;
平衡二叉搜索树构建完成


通过代码实现

注意,这左旋转时,仅考虑到右子树高于左子树的情况;后面会考虑右旋转与双旋转;
这里节点底层的话还是要用之前上一节的二分搜索树作为基础;
—>学习数据结构[12]之学习二分搜索树的实现
在内部类TreeNode下定义计算树的高度,以及左子树高度,右子树高度的方法

 /**
     * 计算左子树的高度;
     * @return 左子树的高度
     */
    public int getLeftHeight() 
        if (leftNode == null) 
            return 0;
         else 
            return leftNode.getHeight();
        
    

    /**
     * 计算右子树的高度
     * @return 右子树的高度
     */
    public int getRightHeight() 
        if (rightNode == null) 
            return 0;
         else 
            return rightNode.getHeight();
        
    

    /**
     * 计算当前结点的高度,(结点内底层实现)
     * @return   以当前结点出发,树的高度
     */
    public int getHeight() 
        //注意最终还要加上当前这个结点的层数 1;
        return Math.max( leftNode != null ? leftNode.getHeight() : 0,
                        rightNode != null ? rightNode.getHeight() : 0) + 1;
    

未定义使用左旋转方法之前;测试当前构造的二叉搜索树

public class AVLTreeTest 
    public static void main(String[] args) 
        //测试;
        int[] array = 4,3,6,5,7,8;
        AVLTree avlTree = new AVLTree();
        //将数组中的数据添加到平衡二叉树中;
        for (int j : array) 
            avlTree.addNode(new TreeNode(j));
        
        //中序遍历这棵树;
        System.out.println("中序遍历该二叉搜索树--");
        avlTree.infixList();
        System.out.println("------------------------");
        System.out.println("未进行左旋转之前--");
        System.out.println("树的高度"+avlTree.getRoot().getHeight());
        System.out.println("当前树的左子树高度"+avlTree.getRoot().getLeftHeight());
        System.out.println("当前树的右子树高度"+avlTree.getRoot().getRightHeight());
    

测试结果;

中序遍历该二叉搜索树--
TreeNodeval=3
TreeNodeval=4
TreeNodeval=5
TreeNodeval=6
TreeNodeval=7
TreeNodeval=8
------------------------
未进行左旋转之前--
树的高度4
当前树的左子树高度1
当前树的右子树高度3

那么定义左旋转方法之后;
同样,在内部类TreeNode下定义左旋转节点方法,就按照图解分析的思路进行逻辑处理;

/**
 * 左旋转二叉搜索树;
 */
public void RotateLeft()
    //1.首先创建新的节点;值为当前节点
    TreeNode newNode = new TreeNode(val);
    //2.然后会把当前节点的左子节点设置为新节点的左子节点;
    newNode.leftNode = this.leftNode;
    //3.当前节点的右子节点的左子节点设置为新节点的右子节点;
    newNode.rightNode = this.rightNode.leftNode;
    //4.用当前节点的右子节点值替换覆盖当前节点;
    this.val = this.rightNode.val;
    //5,注意当前节点的值此时已经变了;这里让当前节点的右节点的右节点;设置为当前节点的右节点;
    this.rightNode = this.rightNode.rightNode;
    //6.将新的节点挂接到当前节点的左子节点;
    this.leftNode = newNode;

在添加节点的方法底层进行调用

/**
 * 添加节点的底层方法;
 * @param node 要添加的节点;
 */
public void addNode(TreeNode node) 
    //先判断当前的节点是否为空;
    if (node == null) return;

    if (node.val <= this.val) 
        //考虑挂到左子节点;若左节点已经有了,则向左继续递归;
        if (this.leftNode == null) 
            this.leftNode = node;
         else 
            this.leftNode.addNode(node);
        
     else 
        //右子节点;若有节点已经存在,则向右继续递归;
        if (this.rightNode == null) 
            this.rightNode = node;
         else 
            this.rightNode.addNode(node);
        
    
    //这里先考虑右子树高于左子树时, 进行左旋转的处理;
    if(getRightHeight() - getLeftHeight() >1)
        RotateLeft();
    

注意这里并不是最终的处理,仅考虑右子树高于左子树的情况;

同样进行测试使用;

当前树的左右高度之差符合平衡二叉树的规则了

中序遍历该二叉搜索树--
TreeNodeval=3
TreeNodeval=4
TreeNodeval=5
TreeNodeval=6
TreeNodeval=7
TreeNodeval=8
------------------------
左旋转处理之后--
树的高度3
当前树的左子树高度2
当前树的右子树高度2

右旋转图解

首先,就用一组数字10,8,12,7,9,6形成的二分搜索树作为案例;
这时想让他变成平衡的二分搜索树,就得改变一下情况;

首先,按照当前的根结点10复制创建一个新的节点10

然后将当前节点10右子节点12挂接到新节点10的右子节点

然后,将当前节点10左子节点8右子节点9设置为新节点10的左子节点

当前节点10左子节点8的值替换当前的节点的值

然后,将当前节点8左子节点8左子节点7 设置为当前节点8的左子节点

然后,将新节点10设置为当前节点8 的右子节点


右旋转的具体实现

没有进行处理右旋转之前;

大概结构即这样

在内部类TreeNode中添加右旋转的方法,就按照图解的思路进行逻辑处理;

/**
 * 右旋转二叉搜索树
 */
public void RotateRight()
    //1.首先创建新的节点,值为当前的节点;
    TreeNode newNode = new TreeNode(val);
    //2.将当前节点的右子节点作为新节点的右子节点;
    newNode.rightNode = this.rightNode;
    //3.将当前节点的左子节点的右子节点 就作为新节点的左子节点;
    newNode.leftNode = this.leftNode.rightNode;
    //4.让当前节点的左子节点的值 替换 当前节点的值;
    this.val = this.leftNode.val;
    //5.将当前的节点的左子节点的左子节点接到当前节点的左子节点位置;
    this.leftNode = this.leftNode.leftNode;
    //6.将新节点的设置为当前节点的右子节点;
    this.rightNode = newNode;

在添加方法中加入右旋转方法的调用

  /**
     * 添加节点的底层方法;
     * @param node 要添加的节点;
     */
    public void addNode(TreeNode node) 
        //先判断当前的节点是否为空;
        if (node == null) return;

        if (node.val <= this.val) 
            //考虑挂到左子节点;若左节点已经有了,则向左继续递归;
            if (this.leftNode == null) 
                this.leftNode = node;
             else 
                this.leftNode.addNode(node);
            
         else 
            //右子节点;若有节点已经存在,则向右继续递归;
            if (this.rightNode == null) 
                this.rightNode = node;
             else 
                this.rightNode.addNode(node);
            
        
        //这里在添加时考虑右子树高于左子树时, 进行左旋转的处理;
        if(getRightHeight() - getLeftHeight() > 1)
            RotateLeft();
        
        //添加节点时考虑左子树高于右子树时,进行右旋转的处理;
        if(getLeftHeight() - getRightHeight() > 1)
            RotateRight();
        
    

再进行测试处理;
注意这里的考虑也仅是左树高于右树的特殊情况;进行处理;


双旋转处理

比如下面这种树结构的话;

经过右旋转的处理;这个还是不符合平衡二叉树的规则

它实际上是进行了右旋转,变成了这样;

那么,这里就得考虑双旋转;字面意思,根据情况左旋转和右旋转结合起来;

分析一下思路;
首先看这棵树,当前根节点的左子节点的右子树的高于左子树,那么就对当前节点的左节点7 进行左旋转;之后挂接到根节点10下;
然后注意到此时的结构,当前根节点的左子节点的左子树高于右子树,那么对当前节点进行右旋转即可;

双旋转具体实现

这里就得考虑四种情况;

在符合左旋转时(当前节点的右子树高于左子树);

  • (1)当前节点的右子节点的左子树高于右子树;那么就得先对当前节点的右子节点进行右旋转;然后在对当前节点进行左旋转;
  • (2)若当前节点的右子节点的右子树高于左子树;直接对当前节点进行左旋转即可;

在符合右旋转时(当前节点的左子树高于右子树);

  • (3)若当前节点的左子节点的右子树高于左子树;先对当前节点的左子节点进行左旋转;然后在对当前节点进行右旋转;
  • (4)若当前节点的左子节点的左子树高于右子树,直接对当前节点进行右旋转即可;

这次的话,在内部类TreeNode的底层添加方法处进行修改即可;

/**
 * 添加节点的底层方法;
 * @param node 要添加的节点;
 */
public void addNode(TreeNode node) 
    //先判断当前的节点是否为空;
    if (node == null) return;

    if (node.val <= this.val) 
        //考虑挂到左子节点;若左节点已经有了,则向左继续递归;
        if (this.leftNode == null) 
            this.leftNode = node;
         else 
            this.leftNode.addNode(node);
        
     else 
        //右子节点;若有节点已经存在,则向右继续递归;
        if (this.rightNode == null) 
            this.rightNode = node;
         else 
            this.rightNode.addNode(node);
        
    
    //添加节点时考虑右子树高于左子树, 进行左旋转的处理;
    if(getRightHeight() - getLeftHeight() > 1)
        //若当前节点的右子节点的左子树高于右子树; 先对当前节点的右子节点进行右旋转;
        if(this.rightNode!=null && this.rightNode.getLeftHeight() > this.rightNode.getRightHeight())
            this.rightNode.RotateRight();
            //在对当前节点进行左旋转;
            this.RotateLeft();
        else
            //否则直接进行左旋转;
            RotateLeft();
        
        return;
    
    //添加节点时考虑左子树高于右子树时,进行右旋转的处理;
    if(getLeftHeight() - getRightHeight() > 1)
        //若当前节点的左子节点的右子树高于左子树;先对当前节点的左子节点进行左旋转;
        if(this.leftNode!=null && this.leftNode.getRightHeight() > this.leftNode.getLeftHeight())
            this.leftNode.RotateLeft();
            //然后在对当前节点进行右旋转;
            this.RotateRight();
        else
            RotateRight();
        
    

测试;和分析的一致;

平衡二叉搜索树代码总结

最终的总结;

/**
 * @author by CSDN@小智RE0
 * @date 2021-11-20 11:22
 * 平衡二叉树
 */
//平衡二叉树;
class AVLTree
    //根节点;
    private TreeNode root;
    //获取当前根结点;
    public TreeNode getRoot() 
        return root;
    

    /**
     * 在二分搜索树中添加新节点;
     * @param node 需要添加的新节点;
     */
    public void addNode(TreeNode node) 
        //若根结点为空,则让新添加的第一个节点暂时作为根结点;
        if (root == null) root = node;

        else 
            root.addNode(node);
        
    

    /**
     * 中序遍历该二分搜索树;
     */
    public void infixList() 
        if (root == null)
            throw new RuntimeException("the tree is empty");
        root.infixList();
    
    /**
     * 根据给定的值找到要删除的节点;(在二分树下定义)
     *
     * @param val 给定的数值
     * @return 查找到要删除的节点;
     */
    public TreeNode getTreeNode(int val) 
        if (root == null) 
            return null;
         else 
            return root.getTreeNode(val);
        
    

    /**
     * 需要找到被删除节点的父亲节点;(在二分树下定义)
     *
     * @param val 要被删除的节点;
     * @return 要被删除节点的父亲节点;
     */
    public TreeNode getParentByDelete(int val) 
        if (root == null) 
            return null;
         else 
            return root.getParentByDelete(val);
        
    

    /**
     * 删除结点;
     *
     * @param val 指定删除结点的值;
     */
    public void deleteNode(int val) 
        if (root == null) return;

        //1.先找要被删除的节点;
        TreeNode wantDeleteNode = getTreeNode(val);
        if (wantDeleteNode == null) 
            //未找到节点;
            throw new RuntimeException("The node to be deleted was not found");
        
        if ((root.leftNode == null && root.rightNode == null)) 
            //若只有根节点,删除即可
            root = null;
            return;
        
        //2.找删除结点的父亲节点;
        TreeNode parentNode = getParentByDelete(val);

        //一: 被删除的节点是叶子节点;
        if (wantDeleteNode.leftNode == null && wantDeleteNode.rightNode == null) 
            //然后确认这个删除结点是它父节点的左节点还是右结点;
            if (parentNode.leftNode != null && parentNode数据结构之二叉搜索树和二叉平衡树学习笔记

数据结构 ---[实现平衡树(AVL Tree)]

平衡树学习笔记

算法笔记-6:平衡二叉树(理论篇)

玩转数据结构5之二分搜索树(学习笔记)

恋上数据结构与算法第一季笔记—— 平衡搜索二叉树