数据结构与算法----平衡二叉树(AVL)

在进入本次课题时,我们要先了解什么是二叉排序树,不了解的伙伴可以看我的博客[数据结构与算法------树(树、二叉树、哈夫曼树)];了解的伙伴可以跳过直接进行的我们的课题(往下看)
我已经了解了完全二叉树、哈夫曼树、二叉排序树,为什么还要学习平衡二叉树呢?学习它的好处是什么?我们知道树的查找效率是由树的高度决定的,而平衡二叉树的作用是将树的高度达到最小,因此学习平衡二叉树可以提高程序的运行效率

平衡二叉树是什么?

它是一棵二叉排序树,但是它(父节点)的左右两个子树的高度差(平衡因子)的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树;如下图,它是否是一棵平衡二叉树?
在这里插入图片描述
从图中可以看出虽然根节点40左右子树的高度差绝对值<=1,但是它的左子树不是平衡二叉树(依次轮询节点,判断节点的左右子树的高度差,如节点30,它的左子树高度为2,右子树高度为0,它的左右子树的高度差绝对值!<=1),所以它不是一棵二叉平衡树

判断左右子树是否是一棵平衡二叉树,我们可以从左右子树的根节点(父节点)开始依次轮询,判断它们各个节点左右子树的高度差绝对值,如上图中,它的左子树为:
在这里插入图片描述
从根节点30开始依次轮询每个节点的左右子树的高度,节点30的左子树高度为2,右子树的高度为0(2减0大于1);节点20的左子树的高度为1,右子树的高度为0(1减0等于1);节点10的左子树的高度为0,右子树的高度为0(0减0等于0),因为节点30它的左右子树的高度差绝对值不满足<=1,所以它不是一棵二叉平衡树
它的右子树为:
在这里插入图片描述
从根节点50开始依次轮询每个节点的左右子树高度进行求差比较,节点50的左子树高度为0,右子树高度为1( |0-1|<=1 );节点60的左子树高度为0,右子树的高度为0( |0-0| <=1 ),因此它是一棵平衡二叉树

平衡二叉树的结构图

在这里插入图片描述

平衡二叉树的调整(添加、删除节点)

调整的原则:根据插入节点与失衡节点的位置关系来划分

  • 添加节点

LL旋转(左左旋转): 插入节点在失衡节点的左子树的左边,这时失衡节点只需经过一次左旋即可达到平衡
在这里插入图片描述

图一是一棵平衡二叉树,在节点10上添加左节点5(如图二),这时,根节点30的左子树的高度为3,右子树的高度为1( | 3-1 | > 1 ),因此节点30已经失衡了(不是平衡二叉树),我们需要调整这棵二叉树回到平衡状态,调整的方法是失衡节点30左旋一次,使这棵二叉树的左子树根节点20作为这棵二叉树的根节点,而节点20原来的右节点25作为失衡节点30的左节点(如图三)(作为左节点的原因:因为25比30小),这时这棵二叉树的左子树的高度为2,右子树的高度为2( | 2-2 | <=1 )同时左子树、右子树又是二叉平衡树,因此旋转后这棵树又变成了平衡二叉树

RR旋转(右右旋转): 插入节点在失衡节点的右子树的右边,这时失衡节点只需经过一次右旋即可达到平衡
在这里插入图片描述

原图(图一)是一棵平衡二叉树,在这棵平衡二叉树的右子树的右边添加节点50(图图二),这时这棵二叉树已经失衡了,因为这棵树的左子树的高度为1,右子树的高度为3(树的高度:从根节点出发的最大路径长度),它不符合左、右子树的高度差绝对值<=1;因此我们需要失衡节点20右旋一次使之达到平衡(如图三),图二右旋一次时,原来的根节点20作为这棵二叉树左子树的根节点,而原来右子树的根节点30作为这棵树的根节点,原来右子树的根节点30有左节点25,这时我们需要把这个节点放到左子树的右边(如图三),调整后发现,这时这棵树的左子树的高度为2,右子树的高度为2并且左、右子树都是平衡二叉树,因此这时这棵二叉树回到了平衡状态,即平衡二叉树

LR旋转(左右旋转): 插入节点在失衡节点的左子树的右边,这时我们需要在失衡节点左子树的根节点做一次右旋,然后在失衡节点再做一次左旋(一共两次旋转)
在这里插入图片描述
RL旋转(右左旋转): 插入节点在失衡节点的右子树的左边,这时我们需要在失衡节点的右子树的根节点做一次左旋,然后在失衡节点再做一次右旋(一共两次旋转)
在这里插入图片描述

  • 删除节点

LL旋转(左左旋转): 平衡二叉树节点的最大高度在左子树的左边时,删除节点在平衡二叉树节点的右子树的左边或右边,这时失衡节点只需经过一次左旋即可达到平衡
在这里插入图片描述
RR旋转(右右旋转): 平衡二叉树节点的最大高度在右子树的右边时,删除节点在平衡二叉树节点的左子树的左边或右边,这时失衡节点只需经过一次右旋即可达到平衡
在这里插入图片描述

LR旋转(左右旋转): 平衡二叉树节点的最大高度在左子树的右边时,删除节点在平衡二叉树节点的右子树的右边或左边,这时我们需要在失衡节点左子树的根节点做一次右旋,然后在失衡节点再做一次左旋(一共两次旋转)
在这里插入图片描述
RL旋转(右左旋转): 平衡二叉树节点的最大高度在右子树的左边时,删除节点在平衡二叉树节点的左子树的左边或右边,这时我们需要在失衡节点的右子树的根节点做一次左旋,然后在失衡节点再做一次右旋(一共两次旋转)
在这里插入图片描述

代码实现二叉平衡树节点的添加、删除

import java.util.ArrayList;

/**
 * 平衡二叉树
 */
public class AVLTree {
    //记录第一次打印
    private static boolean print = true;
    private static ArrayList<Integer> list = new ArrayList<>(); //存储节点数据

    //节点
    static class Node {
        private int data; //数据
        private Node parent; //父节点
        private Node left; //左节点
        private Node right; //右节点
        private int height; //节点的高度

        //带参构造
        public Node(int data) {
            this.data = data;
        }
    }

    public static void printTree(Node node) {
        if (node == null) {
            System.out.println("空树");
        } else {
            if (print) {  //判断是否是第一次打印树
                System.out.println("[ root:" + node.data + ",高度:" + node.height + "]");  //输出这棵树的根节点和高度
                print = false;  //将print置成false
            } else {
                System.out.println(node.data + ",高度:" + node.height + "]"); //输出节点和节点的高度
            }
            if (node.left != null) { //如果左节点不为空
                System.out.print("[ " + node.data + ".left:");
                printTree(node.left);  //递归
            }
            if (node.right != null) { //如果右节点不为空
                System.out.print("[ " + node.data + ".right:");
                printTree(node.right);  //递归
            }
        }
    }

    //获取节点的高度
    public static int getHeight(Node node) {
        return node == null ? -1 : node.height;  //三目运算,如果节点为null,则高度为-1,否则返回这个节点的高度
    }

    /**
     * 平衡二叉树失衡的调整:
     * LL旋转
     * RR旋转
     * LR旋转
     * RL旋转
     */
    //LL旋转:插入节点在失衡节点的左子树的左边,这时失衡节点只需经过一次左旋即可达到平衡
    public static Node LLRotate(Node node) {
        Node root = node.left;  //记录失衡节点的左子树的根节点 root
        node.parent = root; //节点父节点的变换
        node.left = root.right; //将root的右子树作为失衡节点的左子树
        root.right = node; //将失衡节点作为root的右子树
        root.parent = null; //父节点的变换
        //root、node高度调整
        //因为Math.max()只是取最大值(左子树的高度或右子树的高度),如果要取节点这个的高度要+1
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
        root.height = Math.max(getHeight(root.left), node.height) + 1;
        return root;  //将root节点取代失衡节点的位置
    }

    //RR旋转:插入节点在失衡节点的右子树的右边,这时失衡节点只需经过一次右旋即可达到平衡
    public static Node RRRotate(Node node) {
        Node root = node.right; //记录失衡节点右子的根节点 root
        node.parent = root; //父节点的变换
        node.right = root.left;  //将root的左子树作为失衡节点的右子树
        root.left = node; //将失衡节点作为root的左子树
        root.parent = null; //父节点的变换
        //node、root的高度调整
        node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
        root.height = Math.max(node.height, getHeight(root.right)) + 1;
        return root; //将root节点取代失衡节点的位置
    }

    //LR旋转:插入节点在失衡节点的左子树的右边,这时我们需要在失衡节点左子树的根节点做一次右旋,
    // 然后在失衡节点再做一次左旋(一共两次旋转)
    public static Node LRRotate(Node node) {
        //失衡节点的左子树做右旋转,并将旋转后生成新的树作为失衡节点的左子树(为什么是左子树?因为是对失衡节点左子树进行旋转)
        node.left = RRRotate(node.left);
        //失衡节点做左旋转
        return LLRotate(node);
    }

    //RL旋转:插入节点在失衡节点的右子树的左边,这时我们需要在失衡节点的右子树的根节点做一次左旋,
    // 然后在失衡节点再做一次右旋(一共两次旋转)
    public static Node RLRotate(Node node) {
        //失衡节点的右子树做左旋转,并将旋转后生成新的树作为失衡节点的右子树
        node.right = LLRotate(node.right);
        //失衡节点做右旋转
        return RRRotate(node);
    }

    public static Node insert(Node root, int data) {
        if (root == null) { //如果传入的节点为null
            return new Node(data); //返回新节点
        }
        //root:如果没有递归调用,则为树的根节点,否则为传入节点
        if (data <= root.data) {  //如果传入的数据<=传入节点的数据
            root.left = insert(root.left, data); //生成新的节点并将这个节点作为root节点的左子树(递归方法)
            root.left.parent = root; //节点的父节点
            //判断是否失衡
            int value = Math.abs(getHeight(root.left) - getHeight(root.right)); //root节点左子树高度和右子树高度差值的绝对值
            if (value > 1) {  //节点失衡
                if (data <= root.left.data) {  //插入节点在失衡节点左子树的左边
                    //LL旋转
                    System.out.println("进行了LL旋转");
                    root = LLRotate(root);  //将旋转后返回的节点赋给根节点
                } else {  //插入节点在失衡节点左子树的右边
                    //LR旋转
                    System.out.println("进行了LR旋转");
                    root = LRRotate(root);
                }
            }
        } else { //如果传入的数据> 传入节点的数据
            root.right = insert(root.right, data);  // //生成新的节点并将这个节点作为root节点的右子树(递归方法)
            root.right.parent = root; //节点的父节点
            int value = Math.abs(getHeight(root.left) - getHeight(root.right));
            if (value > 1) {  //失衡
                if (data <= root.right.data) { //插入节点在失衡节点右子树的左边
                    //RL旋转
                    System.out.println("进行了RL旋转");
                    root = RLRotate(root);
                } else { //插入节点在失衡节点右子树的左边
                    //RR旋转
                    System.out.println("进行了RR旋转");
                    root = RRRotate(root);
                }
            }
        }
        //设置节点的高度
        root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
        return root;
    }

    /**
     * 删除节点
     */
    private static Node del(Node root, int data) {
        list.clear();  //将存储的数据清空
        //遍历节点
        list = saveData(root);
        Node node = null;
        if (list.contains(data)) {  //如果节点包含这个数据
            list.remove((Object) data);  //要将int类型转换成Object类型,如果没有的话int就当作了下标
            for (int i = 0; i < list.size(); i++) {  //从新构建二叉树
                node = insert(node, (Integer) list.get(i));  //构建平衡二叉树
            }
        }else {
            System.out.println("二叉平衡树中没有数据为"+data+"的节点");
            node = root;
        }
        return node;
    }

    //存储阶段数据
    private static ArrayList saveData(Node root) {
        if (root != null) {
            list.add(root.data);
            saveData(root.left); //遍历左子树
            saveData(root.right); //遍历右子树
        }
        return list;
    }

    public static void main(String[] args) {
        Node root = null;
        root = insert(root, 30);
        root = insert(root, 20);
        root = insert(root, 40);
        root = insert(root, 10);
        root = insert(root, 25);
        //插入节点在失衡结点的左子树的左边
        root = insert(root, 5);
        root = insert(root, 50);
        root = del(root, 20);
        root = del(root,1);
        //打印树,按照先打印左子树,再打印右子树的方式
        System.out.println();
        System.out.println("二叉平衡树为:");
        printTree(root);
    }
}

结果截图:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值