数据结构复习之AVL树(AVL树的概念、左旋、右旋操作保证平衡)Java实现

数据结构复习之AVL树(AVL树的概念、左旋、右旋操作保证平衡)

一、平衡查找二叉树

由于二叉查找树在某些极端情况下可能会形成一个链表,使其性能大大降低,故AVL树作为对其的一种改进
AVL改进的方式,数据的插入仍然遵循二叉查找树的特性,但是需要在元素插入时候保证其左右子树的高度之差<=1,如果不满足,则需要通过旋转来保证树的平衡性

二、如何旋转

对于元素插入导致AVL树失衡的情况有四种
故需要针对四种情况分别设计平衡旋转方式,在插入元素时进行检查

  •  LL:在node的左子结点的左子结点插入元素导致失衡
    
  •  RR:在node的右子结点的右子结点插入元素导致失衡
    
  •  LR:在node的左子结点的右子结点插入元素导致失衡
    
  •  RL:在node的右子结点的左子结点插入元素导致失衡
    

旋转方式针对以上情况分为四种
左旋->LL
在这里插入图片描述

		//1)记录不平衡结点的左子结点
        Node node_left = node.left;
        //2)将左子结点的右子结点作为不平衡结点的左子结点
        node.left = node_left.right;
        //3)将不平衡结点作为其左子结点的右子结点
        node_left.right = node;

右旋->RR
在这里插入图片描述

		//1)临时结点指向失衡结点的右子结点
        Node node_right = node.right;
        //2)失衡结点的右子结点指向临时结点的左子结点
        node.right = node_right.left;
        //3)临时结点的左子结点指向失衡结点
        node_right.left = node;

左右旋->LR
在这里插入图片描述

		//1)可以拆分为一次对失衡结点的左子结点的右旋操作
        node.left = rightRotate(node.left);
        //2)之后进行对失衡结点的一次左旋操作
        node = leftRotate(node);

右左旋->RL
在这里插入图片描述

		//1)对失衡结点的右子结点进行左旋
        node.right = leftRotate(node.right);
        //2)对失衡结点进行右旋
        node = rightRotate(node);

三、参考代码

package TreeTest;

/**
 * AVL树 平衡查找二叉树,左右子树的高度差小于等于1,树的高度为左右子树高度的最大值+1
 * 为了解决由于二叉查找树可能会出现6 5 4 3 2 1的类似单链表的情况,降低效率的问题
 * 当二叉查找树出现了高度差为2的时候需要对二叉查找树进行平衡化
 * 平衡化的情况分为:LL LR RR RL四种
 * 分别对应:
 *      LL:在node的左子结点的左子结点插入元素导致失衡
 *      RR:在node的右子结点的右子结点插入元素导致失衡
 *      LR:在node的左子结点的右子结点插入元素导致失衡
 *      RL:在node的右子结点的左子结点插入元素导致失衡
 * 左旋:简单来说将node的左子结点作为当前树的根结点
 * 右旋:将node的右子结点作为当前树的根结点
 */
public class AVLTree {
    private Node root;

    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();
        //左旋测试
        //int[] arr = {8,4,6,2,1,12};
        //右旋测试
        //int[] arr = {8,4,12,10,14,13};
        //LR旋转测试
        //int[] arr = {8,4,12,2,6,5};
        //RL旋转测试
        int[] arr = {8,4,12,10,14,11};
        for (int i : arr) {
            avlTree.put(i);
        }
        System.out.println("整棵树的高度:"+avlTree.getRootHeight());
        System.out.println("root左子树高度:"+avlTree.getNodeHeight(avlTree.root.left));
        System.out.println("root右子树高度:"+avlTree.getNodeHeight(avlTree.root.right));
        avlTree.midErgodic();
    }

    /**
     * LL 左旋处理树不平衡的情况
     * 1)记录不平衡结点的左子结点
     * 2)将左子结点的右子结点作为不平衡结点的左子结点
     * 3)将不平衡结点作为其左子结点的右子结点
     * 4)将不平衡结点的左子结点作为当前树的根结点(一切围绕此展开)
     */
    public Node leftRotate(Node node){
        //1)记录不平衡结点的左子结点
        Node node_left = node.left;
        //2)将左子结点的右子结点作为不平衡结点的左子结点
        node.left = node_left.right;
        //3)将不平衡结点作为其左子结点的右子结点
        node_left.right = node;
        //4)将不平衡结点的左子结点作为当前树的根结点(一切围绕此展开)
        return node_left;
    }
    /**
     * RR 右旋处理
     * 1)临时结点指向失衡结点的右子结点
     * 2)失衡结点的右子结点指向临时结点的左子结点
     * 3)临时结点的左子结点指向失衡结点
     * 4)将临时结点提升为当前根结点
     */
    public Node rightRotate(Node node){
        //1)临时结点指向失衡结点的右子结点
        Node node_right = node.right;
        //2)失衡结点的右子结点指向临时结点的左子结点
        node.right = node_right.left;
        //3)临时结点的左子结点指向失衡结点
        node_right.left = node;
        //4)将临时结点提升为当前根结点
        return node_right;
    }
    /**
     * LR 左右旋转:由于在左子树的右子树上插入元素导致失衡,所以称为LR旋转
     * 步骤:
     * 1)可以拆分为一次对失衡结点的左子结点的右旋操作
     * 2)之后进行对失衡结点的一次左旋操作
     */
    public Node leftRightRotate(Node node){
    	//1)可以拆分为一次对失衡结点的左子结点的右旋操作
        node.left = rightRotate(node.left);
        //2)之后进行对失衡结点的一次左旋操作
        node = leftRotate(node);
        return node;
    }
    /**
     * RL 右左旋转:由于右子树的左子树插入结点导致失衡
     * 步骤:
     * 1)对失衡结点的右子结点进行左旋
     * 2)对失衡结点进行右旋
     */
    public Node rightLeftRotate(Node node){
    	//1)对失衡结点的右子结点进行左旋
        node.right = leftRotate(node.right);
        //2)对失衡结点进行右旋
        node = rightRotate(node);
        return node;
    }
    /**
     * 向树中插入元素(重载方法)
     * @param data
     */
    public void put(int data){
        root = put(data,root);
    }

    /**
     * 向树中插入元素
     * @param data  待插入的元素
     * @param node  需要检查的结点
     * @return  插入成功后的树结构
     */
    public Node put(int data,Node node){
        if(node == null){
            return new Node(data,null,null);
        }
        //如果不为空 开始寻找要插入的位置
        if(data < node.data){
            node.left = put(data,node.left);
        }else if (data > node.data){
            node.right = put(data,node.right);
        }else{
            System.out.println("要插入的元素已存在");
            return null;
        }
        //元素插入成功后,逐层向上寻找失衡结点
        if (getNodeHeight(node.left) - getNodeHeight(node.right) > 1){
            //如果插入的结点数据大于失衡结点的左子树的值,证明该结点是在失衡结点的左子树的右子树插入,需要左旋右旋
            if(data > node.left.data){
                node = leftRightRotate(node);
            }else{
                //否则证明插入的数据位于失衡结点的左子树的左子树,左旋即可
                node = leftRotate(node);
            }
        }else if (getNodeHeight(node.right) - getNodeHeight(node.left) > 1){
            //如果插入的结点数据小于失衡结点的右子树的值,证明该节点是在失衡结点的右子树的左子树插入,需要右旋左旋
            if(data < node.right.data){
                node = rightLeftRotate(node);
            }else{
                //否则证明插入的数据位于失衡结点的右子树的右子树,右旋即可
                node = rightRotate(node);
            }
        }
        return node;
    }

    /**
     * 获取根结点的树的高度
     * @return
     */
    public int getRootHeight(){
        return getNodeHeight(root);
    }

    /**
     * 获取当前结点的树的高度
     * 如果当前结点为null,则高度为0,否则继续递归判断高度
     * @param node
     * @return
     */
    public int getNodeHeight(Node node){
        if(node == null){
            return 0;
        }
        int leftHeight = node.left == null ? 0:getNodeHeight(node.left);
        int rightHeight = node.right == null ? 0 : getNodeHeight(node.right);
        return Math.max(leftHeight,rightHeight)+1;
    }

    /**
     * 重载中序遍历方法,方便调用
     */
    public void midErgodic(){
        midErgodic(root);
    }
    /**
     *  平衡树的中序遍历
     */
    public void midErgodic(Node node){
        if(node == null){
            return ;
        }
        midErgodic(node.left);
        System.out.println(node.data);
        midErgodic(node.right);
    }
    /**
     * 结点结构定义
     */
    public class Node{
        private int data;
        private Node left;
        private Node right;
        private int height;

        public Node(int data, Node left, Node right, int height) {
            this.data = data;
            this.left = left;
            this.right = right;
            this.height = height;
        }

        public Node(int data, Node left, Node right) {
            this(data,left,right,0);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
做一门精致,全面详细的 java数据结构与算法!!!让天下没有难学的数据结构,让天下没有难学的算法,不吹不黑,我们的讲师及其敬业,可以看到课程视频,课件,代码的录制撰写,都是在深夜,如此用心,其心可鉴,他不掉头发,谁掉头发???总之你知道的,不知道的,我们都讲,并且持续更新,走过路过,不要错过,不敢说是史上最全的课程,怕违反广告法,总而言之,言而总之,这门课你值得拥有,好吃不贵,对于你知识的渴求,我们管够管饱话不多说,牛不多吹,我们要讲的本门课程内容:稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫问题、栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式、递归与回溯、迷宫问题、八皇后问题、算法的时间复杂度、冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序、排序速度分析、二分查找、插值查找、斐波那契查找、散列、哈希表、二叉树二叉树与数组转换、二叉排序树(BST)、AVL树、线索二叉树、赫夫曼树、赫夫曼编码、多路查找树(B树B+树和B*树)、图、图的DFS算法和BFS、程序员常用10大算法、二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法马踏棋盘算法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值