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

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 ---[实现平衡树(AVL Tree)]相关的知识,希望对你有一定的参考价值。

写在前面
实际上,之前已经更新过一篇关于平衡树的笔记了;—>笔记链接;
学习数据结构笔记(12) — [平衡二叉搜索树(AVLTREE)]

之前的实现方式需要计算出当前节点的父节点的索引;…左旋右旋实现方式步骤比较明确;
不足之处是仅实现了添加节点时维持二分树的平衡;

本次的平衡树具体操作方式和之前的那种略有不同;所以记录一下吧…
这里的基础会以之前的二分搜索树实现作为基础,在基础上进行改动;笔记链接—>
数据结构 (五) — [二分搜索树 (Java)]
这里首先会在节点内定义属性height表示高度;
然后在树中定义基础方法来获取树的高度;
基础方法获取树的平衡因子:—>左右子树的高度差;
定义了方法 判断当前树是否为二分搜索树(判断中序遍历的结果是否为由小到大排序);
当前树是否为平衡树(平衡因子是否超过1);
以及添加/删除节点时左旋右旋操作维持二分树的平衡;

如图,这不是平衡树


右旋转

这棵树左子树偏高;要进行右旋转

那么,仅需两步;
首先将取出节点8的右子节点9;将节点10作为节点8的右子节点

然后让节点9作为节点10的左子节点

左旋转

这棵树右子树偏高,需要左旋转

只需两步;
首先将节点6的左子节点5取出,记录;
将节点4作为节点6的左子节点

然后将节点5作为节点4的右子节点

双旋转

有特殊情况的,就得出动双旋转了;
像下面这种就是需要先进行左旋转处理;然后进行右旋转处理;

先对节点7进行左旋转处理

然后对节点10进行右旋转处理


具体代码

package com.company.e_avltree;
import java.util.*;
/**
 * @author 小智RE0 
 * @Date: 2021/12/10/
 */
//由于二分搜索树,需要对元素的大小进行比较;
public class AVLStartTree<T extends Comparable<T>> 
    //定义内部类结点;
    class Node 
        T val;
        //左子树,右子树;
        Node leftNode;
        Node rightNode;

        //树的高度;
        int height;

        public Node(T val) 
            //初始化时,树的高度为1;
            this.height = 1;
            this.val = val;
            this.leftNode = null;
            this.rightNode = null;
        
    

    //定义树的根结点;
    private Node root;

    //定义树结点的个数;
    private int size;

    public AVLStartTree() 
        this.root = null;
        this.size = 0;
    

    //判空;
    public boolean isEmpty() 
        return this.root == null;
    

    /**
     * 获取以指定结点为树的高度
     *
     * @param node 节点
     */
    public int getHeight(Node node) 
        if (node == null) 
            return 0;
         else 
            return node.height;
        
    

    /**
     * 获取到以指定结点出发,树的高度因子;左右子树的高度差;
     */
    public int getHeightFactor(Node node) 
        if (node == null) 
            return 0;
        
        //这里没有取绝对值,可能会出现高度因子为负数的情况
        return getHeight(node.leftNode) - getHeight(node.rightNode);
    

    /**
     * 判断是否为二分搜索树;
     */
    public boolean isBSTTree() 
        return isBSTTree(root);
    

    /**
     * 判断是否为二分搜索树
     */
    private boolean isBSTTree(Node node) 
        List<T> values = new ArrayList<>();
        //调用递归方法;将值存到values里面;
        inordertraversal(node, values);
        //对遍历的值进行遍历,若为二分树,实际上会由小到大排序,若不符合,直接结束;
        //注意;由于是让当前节点和后一个结点进行比较,所以循环的次数实际上会少一次;
        for (int i = 0; i < values.size() - 1; i++) 
            //若出现前面的比后面大,直接返回;
            if (values.get(i).compareTo(values.get(i + 1)) > 0) 
                return false;
            
        
        return true;
    

    /**
     * 中序遍历,将遍历值存入集合
     *
     * @param node   节点
     * @param values 遍历的值
     */
    private void inordertraversal(Node node, List<T> values) 
        //递归终止条件;
        if (node == null) 
            return;
        
        //左中右;
        inordertraversal(node.leftNode, values);
        values.add(node.val);
        inordertraversal(node.rightNode, values);
    


    /**
     * 判断是否为平衡树;
     */
    public boolean isAVLTree() 
        return isAVLTree(root);
    

    /**
     * 判断是否为平衡树
     *
     * @param node 节点
     */
    private boolean isAVLTree(Node node) 
        //递归终止的条件;
        if (node == null) 
            return true;
        

        //判断当前节点的平衡因子;
        if (Math.abs(getHeightFactor(node)) > 1) 
            return false;
        

        //递归计算左右子树的高度因子;
        return isAVLTree(node.leftNode) && isAVLTree(node.rightNode);
    


    /**
     * 左旋转处理
     *
     * @param curNode 当前节点
     */
    public Node RotateLeft(Node curNode) 
        //取得当前节点的右子节点;
        Node rNode = curNode.rightNode;
        //将右子节点的左子节点取出,标记;
        Node rLNode = rNode.leftNode;
        //将当前节点作为它右子节点的左子节点;
        rNode.leftNode = curNode;
        //将当前节点的右子节点的 左子节点 挂接到当前节点的右子节点下;
        curNode.rightNode = rLNode;

        //更改树的高度;
        curNode.height = Math.max(getHeight(curNode.leftNode), getHeight(curNode.rightNode)) + 1;
        rNode.height = Math.max(getHeight(rNode.leftNode), getHeight(rNode.rightNode)) + 1;

        return rNode;
    

    /***
     * 右旋转处理
     * @param curNode  当前节点
     */
    public Node RotateRight(Node curNode) 
        //取得当前节点的左子节点;
        Node lNode = curNode.leftNode;
        //将左子节点的右子节点取出,标记;
        Node lRNode = lNode.rightNode;
        //将当前节点作为它左子节点的右子节点;
        lNode.rightNode = curNode;
        //将当前节点的左子节点 的 右子节点挂接到到当前节点的左子节点下;
        curNode.leftNode = lRNode;

        //更改树的高度;
        curNode.height = Math.max(getHeight(curNode.leftNode), getHeight(curNode.rightNode)) + 1;
        lNode.height = Math.max(getHeight(lNode.leftNode), getHeight(lNode.rightNode)) + 1;

        return lNode;
    


    /**
     * 添加元素
     *
     * @param ele 指定的元素
     */
    public void add(T ele) 
        //判断节点是否存在;
        if (contains(ele) != null) 
            return;
        
        //每次将创建的根节点返回;
        root = add(root, ele);
        this.size += 1;
    

    //递归添加元素的底层;
    private Node add(Node node, T ele) 
        //不存在就创建;
        if (node == null) 
            return new Node(ele);
        
        //添加的元素和之前的根结点进行比较;
        if (ele.compareTo(node.val) > 0) 
            node.rightNode = add(node.rightNode, ele);
         else 
            node.leftNode = add(node.leftNode, ele);
        


        //添加之后;需要更新当前树的高度,----->计算左右子树的高度+1 作为当前树的高度;
        node.height = Math.max(getHeight(node.leftNode), getHeight(node.rightNode)) + 1;

        //左旋转,右旋转,以及左右旋转情况判断;
        Node resNode = null;
        //维持平衡;
        //极度偏左;
        if (getHeightFactor(node) > 1 && getHeightFactor(node.leftNode) >= 0) 
            //直接右旋;
            resNode = RotateRight(node);
        
        //极度偏右;
        else if (getHeightFactor(node) < -1 && getHeightFactor(node.rightNode) <= 0) 
            //直接左旋;
            resNode = RotateLeft(node);
        
        //左树先偏高,后面右子树偏高;
        else if (getHeightFactor(node) > 1 && getHeightFactor(node.leftNode) < 0) 
            //先左旋后右旋;
            node.leftNode = RotateLeft(node.leftNode);
            resNode = RotateRight(node);
        
        //右树先偏高,后面左子树偏高;
        else if (getHeightFactor(node) < -1 && getHeightFactor(node.rightNode) > 0) 
            //先右旋再左旋;
            node.rightNode = RotateRight(node.rightNode);
            resNode = RotateLeft(node);
         else 
            //若平衡,就直接用node节点的值;
            resNode = node;
        
        return resNode;
    


    /**
     * 删除指定数值;
     * @param val 指定数值;
     */ 
    public T removeAssignVal(T val) 
        if (root == null) 
            System.out.println("空树不用删除");
            return null;
        
        //先去找是否存在; 要去新建方法;
        Node node = findAssignNode(root, val);
        //是否找到;
        if (node != null) 
            //删除后的剩余结点挂到根结点之后;
            root = removeAssignVal(root, val);
            //元素个数减少;
            this.size -= 1;
            return node.val;
        
        return null;
    

    /**
     * 删除指定结点的底层实现;在根结点为node的二分树中删除指定结点;
     *
     * @param node 指定根结点出发
     * @param val  指定值
     */
    private Node removeAssignVal(Node node, T val) 
        //找到结点时;
        if (node.val.compareTo(val) == 0) 
            //第一种情况==> 该删除结点的左树为空;
            if (node.leftNode == null) 
                //删除当前的结点后;把原来后面的右子树挂到删了结点的位置;
                Node RNode = node.rightNode;
                node.rightNode = null;
                return RNode;
            
            //第二种情况,右树为空;
            else if (node.rightNode == null) 
                //删除当前的结点后;把原来后面的左子树挂到删了结点的位置;
                Node LNode = node.leftNode;
                node.leftNode = null;
                return LNode;
             else 
                //情况三;  左树 右树 都不为空时,可以选择找前驱结点(左子树那块区域的最大值)  或  后继结点(右子树那块区域的最小值)代替删除结点;
                //这里选择找后继节点;

                Node minR = findMinNodeRecurve(node.rightNode);

                Node minRNode = removeMinNode(node.rightNode);

                //后继节点放到删除的结点位置;

                minR.leftNode = node.leftNode;
                minR.rightNode = minRNode;

                node.leftNode = null;
                node.rightNode = null;

                return minR;
            
        

        //递归;
        //找结点时,若当前的根结点比指定的值还大,就去左边找;  否则去右边找;
        if (node.val.compareTo(val) > 0) 
            node.leftNode = removeAssignVal(node.leftNode, val);
         else 
            node.rightNode = removeAssignVal(node.rightNode, val);
        

         Node resNode = node;
        //更新高度;
        resNode.height = Math.max(getHeight(resNode.leftNode),getHeight(resNode.rightNode))+1;
        //维持平衡操作;
        //极度偏左;
        if (getHeightFactor(resNode) > 1 && getHeightFactor(resNode.leftNode) >= 0) 
            //直接右旋;
            resNode = RotateRight(resNode);
        
        //极度偏右;
        else if (getHeightFactor(resNode) < -1 && getHeightFactor(resNode.rightNode) <= 0) 
            //直接左旋;
            resNode = RotateLeft(resNode);
        
        //左树先偏高,后面右子树偏高;
        else if (getHeightFactor(resNode) > 1 && getHeightFactor(resNode.leftNode) < 0) 
            //先左旋后右旋;
            resNode.leftNode = RotateLeft(resNode.leftNode);
            resNode = RotateRight(resNode);
        
        //右树先偏高,后面左子树偏高;
        else if (getHeightFactor(resNode) < -1 && getHeightFactor(resNode.rightNode) > 0) 
            //先右旋再左旋;
            resNode.rightNode = RotateRight(resNode.rightNode);
            resNode = RotateLeft(resNode);
        

        return resNode;
    


    //中序遍历; 稍作修改,让这个遍历结果存入字符串;
    public List<String> middleOrder() 
        //若二叉树为空,则直接返回null空值;
        if (root == null) 
            return null;
        
        //遍历的结果存入集合中;
        List<String> list = new ArrayList<>();
        middleOrder(root, list);
        return list;
    

    //中序遍历底层;
    private void middleOrder(Node node, List<String> list) 
        //递归结束点,若到达最后一层的叶子节点就停止;
        if (node == null) 
            return;
        

        //中序遍历=  先遍历左子树 ==> 获取中间结点 ==> 遍历右子树
        middleOrder(node.leftNode, list);
        list.add(node.val + "<---高度因子--->" + getHeightFactor(node));
        middleOrder(node.rightNode, list);
    

    /**
     * 输出打印树的中序遍历信息;
     */
    @Override
    public String toString() 
        List<String> list = middleOrder();
        list.forEach(a -> System.out.println(a + "\\t"));
        return "";
    


    //查询元素;
    public Node contains(T ele) 
        if (root == null) 
            return null;
        
        return contains(root, ele);
    

    //查询元素的底层;
    private Node contains(Node root, T ele) 
        //递归结束点;
        if以上是关于数据结构 ---[实现平衡树(AVL Tree)]的主要内容,如果未能解决你的问题,请参考以下文章

看动画学算法之:平衡二叉搜索树AVL Tree

平衡二叉树(Balanced Binary Tree 或 Height-Balanced Tree)又称AVL树

Root of AVL Tree(二叉平衡树的建立)

平衡二叉树(AVL Tree)

PAT.1066 Root of AVL Tree(平衡树模板题)

PAT.1066 Root of AVL Tree(平衡树模板题)