二叉平衡树之AVL树【手动实现代码】

news2024/11/24 6:28:44

目录

1、AVL树的概念

2、AVL树定义节点

3、AVL树的插入

4、AVL树的旋转

4.1、新节点插入较高左子树的左侧——右单旋

4.2、新节点插入较高右子树的右侧——左单旋

4.3、新节点插入较高左子树的右侧——左右双旋

4.4、新节点插入较高右子树的左侧——右左双旋

5、AVL树的验证

代码汇总:

6、AVL树的删除

7、AVL树性能分析


1、AVL树的概念

AVL树可能是一颗空树,或者是一颗具有性质的二叉搜索树:

  • 左右子树都是AVL树
  • 左右子树高度之差【即平衡因子】的绝对值不超过1

注:

  • 如果一棵二叉搜索树是高度平衡的,他就是AVL树
  • 如果他有n个节点,其高度可保持在O(logn),搜索时间复杂度:O(logn)
  • 当前节点的平衡因子 = 右子树高度 - 左子树高度

例:


2、AVL树定义节点


public class AVLTree {
    static class TreeNode {
        public int val;
        public int bf;//平衡因子
        public TreeNode left;//左孩子引用
        public TreeNode right;//右孩子引用
        public TreeNode parent;//父节点引用

        public TreeNode(int val) {
            this.val = val;
        }
    }
    public TreeNode root;//根节点
}

3、AVL树的插入

        AVL树就是在二叉搜索树的基础上引入了平衡因子,因此,AVL树的插入,咱们呢,就可以分为两步:

  • 按照二叉搜索树的方式插入新的节点
  • 调整节点的平衡因子

代码:

     /**
     * 插入新的节点
     * @param val
     * @return 插入成功,返回true,失败返回false
     */
    public boolean insert(int val) {
        //1、按照二叉搜索树的方式插入新的节点
        if(root == null) {
            root = new TreeNode(val);
            return true;
        }
        TreeNode node = new TreeNode(val);
        TreeNode parent = null;
        TreeNode cur = root;
        while(cur != null) {
            if(cur.val < val){
                parent = cur;
                cur = cur.right;
            } else if(cur.val == val) {
                return false;
            } else {
                parent = cur;
                cur = cur.left;
            }
        }
        if(parent.val < val) {
            parent.right = node;
        } else {
            parent.left = node;
        }
        node.parent = parent;
        cur = node;


        //2、调节平衡因子

    }

看一个例子:

调节平衡因子,需要注意的点:

        cur插入后,parent的平衡因子一定需要调整,在插入之前,parent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

  • 如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
  • 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可

         平衡因子更新时,存在一个问题,我们需要更新到什么时候停止呢?难道每次都要更新到根节点?

此时:parent的平衡因子可能有三种情况:0,正负1, 正负2

  •  如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功
  •  如果parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,插入后被更新成正负1,此时以parent为根的树的高度增加,需要继续向上更新
  •  如果parent的平衡因子为正负2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理

先看目前我们所能写出来的代码:

        //2、修改平衡因子

        //循环条件,暂时不看
        while () {
            //先看cur是parent的左还是右,决定平衡因子是加1还是减1
            if(cur == parent.right) {
                //右树高度增加,平衡因子++
                parent.bf++;
            } else {
                //左树高度增加,平衡因子--
                parent.bf--;
            }

            if(parent.bf == 0) {
                break;//已经平衡了
            } else if(parent.bf == 1 || parent.bf == -1) {
                //需要继续向上检查
                 cur = parent;
                 parent = parent.parent;
            } else {
                //需要左/右旋转
                if(parent.bf == 2) {
                    //右树高----需要降低右树的高度
                    if(parent.bf == 1) {

                    } else {
                        //parent.bf == -1

                    }
                } else{
                    //parent.bf == -2
                    //左树高----需要降低左树的高度
                    if(parent.bf == -1) {

                    } else {
                        //parent.bf == 1

                    }
                }
            }
        }

接下来,需要做的是,填充各个情况下,是需要将树进行左旋还是右旋


4、AVL树的旋转

4.1、新节点插入较高左子树的左侧——右单旋

流程:

 具体实现:

另外p还需要考虑的情况:

  

 代码:

    /**
     * 右单旋
     * @param parent
     */
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;

        parent.left = subLR;
        subL.right = parent;
        //没有subLR
        if(subLR != null) {
            subLR.parent = parent;
        }
        //记录p的父节点
        TreeNode pParent = parent.parent;
        parent.parent = subL;
        //检查当前是不是根节点
        if(parent == root) {
            root = subL;
            root.parent = null;
        } else {
            //不是根节点
            //判断左右树
            if(pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        subL.bf = 0;
        parent.bf = 0;
    }

4.2、新节点插入较高右子树的右侧——左单旋

流程:

具体实现:

同上述的右旋,p单独考虑的情况【p也可能会是某个节点的左/右孩子】

 

代码:

     /**
     * 左单旋
     * @param parent
     */
    private void rotateLeft(TreeNode parent) {

        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;

        parent.right = subRL;
        if(subRL != null) {
            subRL.parent = parent;
        }

        TreeNode pParent = parent.parent;
        parent.parent = subR;

        if(root == parent) {
            root = subR;
            root.parent = null;
        } else {
            if (pParent.left == parent) {
                parent.left = subR;
            } else {
                parent.right = subR;
            }
            subR.parent = pParent;
        }
         subR.bf = 0;
        parent.bf = 0;
    }

4.3、新节点插入较高左子树的右侧——左右双旋

流程:

        当插入的值是55的时候,就是插入在50的右边,也是左右双旋,但是最终的平衡因子的更改略有差异,可以自己试着画一画。 

 代码:

    /**
     * 左右双旋
     * @param parent
     */
    private void rotateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;

        //左
        rotateLeft(parent.left);
        //右
        rotateRight(parent);

        if(bf == -1) {
            subL.bf = 0;
            subLR.bf = 0;
            parent.bf = 1;
        } else if(bf == 1) {
            subL.bf = -1;
            subLR.bf = 0;
            parent.bf = 0;
        }
    }

4.4、新节点插入较高右子树的左侧——右左双旋

流程:

         当插入的值是40的时候,就是插入在50的左边,也是右左双旋,但是最终的平衡因子的更改略有差异,可以自己试着画一画。 

代码:

    /**
     * 右左双旋
     * @param parent
     */
    private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;
        
        rotateRight(parent.right);
        rotateLeft(parent);
        
        if(bf == 1) {
            parent.bf = -1;
            subR.bf = 0;
            subRL.bf = 0;
        } else {
            parent.bf = 0;
            subR.bf = 1;
            subRL.bf = 0;
        }
    }

5、AVL树的验证

同插入类似,由于它是特殊的二叉搜索树,所以验证时,分两步:

  • 验证是否为二叉搜索树
  • 验证是否为平衡树

代码:

    //中序遍历的结果是有序的 就能说明当前树 一定是搜索树
    public void inorder(TreeNode root) {
        if(root == null) return;
        inorder(root.left);
        System.out.print(root.val+"  ");
        inorder(root.right);
    }

    //计算高度

    private int height(TreeNode root) {
        if(root == null) return 0;
        int leftH = height(root.left);
        int rightH = height(root.right);

        return leftH > rightH ? leftH+1 : rightH+1;
    }

       //是否高度平衡:

    public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        int leftH = height(root.left);
        int rightH = height(root.right);

        if(rightH-leftH != root.bf) {
            System.out.println("这个节点:"+root.val+" 平衡因子异常");
            return false;
        }

        return Math.abs(leftH-rightH) <= 1
                && isBalanced(root.left)
                && isBalanced(root.right);
    }

代码汇总:

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User:龙宝
 * Date:2023-01-10
 * Time:17:38
 */
public class AVLTree {
    static class TreeNode {
        public int val;
        public int bf;//平衡因子
        public TreeNode left;//左孩子引用
        public TreeNode right;//右孩子引用
        public TreeNode parent;//父节点引用

        public TreeNode(int val) {
            this.val = val;
        }
    }
    public TreeNode root;//根节点

    /**
     * 插入新的节点
     * @param val
     * @return 插入成功,返回true,失败返回false
     */
    public boolean insert(int val) {
        //1、按照二叉搜索树的方式插入新的节点
        if(root == null) {
            root = new TreeNode(val);
            return true;
        }
        TreeNode node = new TreeNode(val);
        TreeNode parent = null;
        TreeNode cur = root;
        while(cur != null) {
            if(cur.val < val){
                parent = cur;
                cur = cur.right;
            } else if(cur.val == val) {
                return false;
            } else {
                parent = cur;
                cur = cur.left;
            }
        }
        if(parent.val < val) {
            parent.right = node;
        } else {
            parent.left = node;
        }
        node.parent = parent;
        cur = node;


        //2、修改平衡因子


        while (parent != null) {
            //先看cur是parent的左还是右,决定平衡因子是加1还是减1
            if(cur == parent.right) {
                //右树高度增加,平衡因子++
                parent.bf++;
            } else {
                //左树高度增加,平衡因子--
                parent.bf--;
            }

            if(parent.bf == 0) {
                break;//已经平衡了
            } else if(parent.bf == 1 || parent.bf == -1) {
                //需要继续向上检查
                 cur = parent;
                 parent = parent.parent;
            } else {
                if(parent.bf == 2) {
                    //右树高----需要降低右树的高度
                    if(cur.bf == 1) {
                        //左旋
                        rotateLeft(parent);
                    } else {
                        //cur.bf == -1
                        rotateRL(parent);
                    }
                } else{
                    //parent.bf == -2
                    //左树高----需要降低左树的高度
                    if(cur.bf == -1) {
                        //右旋
                        rotateRight(parent);
                    } else {
                        //cur.bf == 1
                        rotateLR(parent);
                    }
                }
                break;
            }
        }
        return  false;
    }

    /**
     * 右单旋
     * @param parent
     */
    private void rotateRight(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;

        parent.left = subLR;
        subL.right = parent;
        //没有subLR
        if(subLR != null) {
            subLR.parent = parent;
        }
        //记录p的父节点
        TreeNode pParent = parent.parent;
        parent.parent = subL;
        //检查当前是不是根节点
        if(parent == root) {
            root = subL;
            root.parent = null;
        } else {
            //不是根节点
            //判断左右树
            if(pParent.left == parent) {
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        subL.bf = 0;
        parent.bf = 0;
    }

    /**
     * 左单旋
     * @param parent
     */
    private void rotateLeft(TreeNode parent) {

        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;

        parent.right = subRL;
        if(subRL != null) {
            subRL.parent = parent;
        }

        TreeNode pParent = parent.parent;
        parent.parent = subR;

        if(root == parent) {
            root = subR;
            root.parent = null;
        } else {
            if (pParent.left == parent) {
                parent.left = subR;
            } else {
                parent.right = subR;
            }
            subR.parent = pParent;
        }
         subR.bf = 0;
        parent.bf = 0;
    }

    /**
     * 左右双旋
     * @param parent
     */
    private void rotateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;

        //左
        rotateLeft(parent.left);
        //右
        rotateRight(parent);

        if(bf == -1) {
            subL.bf = 0;
            subLR.bf = 0;
            parent.bf = 1;
        } else if(bf == 1) {
            subL.bf = -1;
            subLR.bf = 0;
            parent.bf = 0;
        }
    }

    /**
     * 右左双旋
     * @param parent
     */
    private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;

        rotateRight(parent.right);
        rotateLeft(parent);

        if(bf == 1) {
            parent.bf = -1;
            subR.bf = 0;
            subRL.bf = 0;
        } else {
            parent.bf = 0;
            subR.bf = 1;
            subRL.bf = 0;
        }
    }

    //验证:

    //中序遍历的结果是有序的 就能说明当前树 一定是AVL树吗?  不一定的
    public void inorder(TreeNode root) {
        if(root == null) return;
        inorder(root.left);
        System.out.print(root.val+"  ");
        inorder(root.right);
    }

    private int height(TreeNode root) {
        if(root == null) return 0;
        int leftH = height(root.left);
        int rightH = height(root.right);

        return leftH > rightH ? leftH+1 : rightH+1;
    }

    public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        int leftH = height(root.left);
        int rightH = height(root.right);

        if(rightH-leftH != root.bf) {
            System.out.println("这个节点:"+root.val+" 平衡因子异常");
            return false;
        }

        return Math.abs(leftH-rightH) <= 1
                && isBalanced(root.left)
                && isBalanced(root.right);
    }

}

6、AVL树的删除

        同插入类似,先删除节点,在更新平衡因子,不同的是,删除节点后平衡因子的持续更新时,最差情况下需要一直调整到根节点处


7、AVL树性能分析

  • 如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
     
  • 如果要AVL树做一些结构修改的操作,性能非常低下,例:插入时要维护绝对平衡,旋转次数较多,特别是在删除的时候,可能会一直让旋转持续到根的位置

好啦,本期结束咯,咱们下期见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetCode】1807. 替换字符串中的括号内容

1807. 替换字符串中的括号内容 题目描述 给你一个字符串 s &#xff0c;它包含一些括号对&#xff0c;每个括号中包含一个 非空 的键。 比方说&#xff0c;字符串 “(name)is(age)yearsold” 中&#xff0c;有 两个 括号对&#xff0c;分别包含键 “name” 和 “age” 。 你…

2023 年 The Sandbox 生态系统将迎来什么?

2022 年对于 The Sandbox 来说是多么美好的一年&#xff01;不仅是对我们的团队来说&#xff0c;对所有与我们建立业务的合作伙伴、才华横溢的创作者、工作室和代理机构来说也是这样。感谢大家让今年最喜欢的时刻成为现实&#xff0c;并成为这个社区的一部分。我们正在共同构建…

25w粉拿下1600w播放,仅用一周时间出圈B站!

2022年&#xff0c;B站举办了第四次跨年晚会《最美的夜》&#xff0c;艾薇儿登台唱起《Complicated》的瞬间&#xff0c;B站跨晚的直播间人气峰值到达3亿。在过去的一年里&#xff0c;平台持续变化带来亮眼成绩&#xff0c;月活用户破3亿、直播开通购物专区、竖屏模式增长播放等…

LaoCat带你认识容器与镜像(四【上】)

Dockerfile是实际项目生产中&#xff0c;比较常用的一个知识点&#xff0c;故也准备分成上下俩节来讲解。 本章内容 如何查询相关Dockerfile与Dockerfile基础命令介绍 本文实操全部基于Ubuntu 20.04 宿主机 > linux服务器本身 业务不断的扩充累积中&#xff0c;大多数企业应…

Python import自定义模块报错、自定义异常、字符串处理、截取

一、python import自定义的模块报错 问题现象&#xff1a;pycharm中运行一切正常&#xff0c;但是到命令行中&#xff08;cmd命令行或pycharm的Terminal窗口&#xff09;运行py文件&#xff0c;就会报错No module named xxx 问题原因&#xff1a; pycharm在每次运行时&#x…

【C进阶】指针的进阶

家人们欢迎来到小姜的世界&#xff0c;<<点此>>传送门 这里有详细的关于C/C/Linux等的解析博客&#xff0c;家人们赶紧冲鸭&#xff01;&#xff01;&#xff01; 客官&#xff0c;码字不易&#xff0c;来个三连支持一下吧&#xff01;&#xff01;&#xff01;关注…

祝大家兔年 新春快乐Happy new year

春节&#xff0c;也被称为农历新年。对于中国人来说&#xff0c;这是规模最大&#xff0c;最重要的传统节日。The Spring Festival is also called Chinese Lunar New Year. Being one of the traditional Chinese festivals, it is the grandest and most important festival …

Ue4 Insights的使用

1.运行UnrealInsights.exe 2.执行独立进程或者打包exe 这时会发现Insights自动创建并开始运行了一个Trace Sessions&#xff0c;持续记录到.utrace文件中 .utrace文件路径 3.也可以通过连接IP地址&#xff0c;获取到该计算机的UE程序。状态为LIVE实时 4.点击右下角Open按钮…

Redis客户端命令基础操作一

查看所有key 语法: keys * 是否存在key 语法: exists [key] 获取包含指定字符串的key 语法&#xff1a; keys *[字符串]* 设置key 语法&#xff1a;set [key] [value] 设置key 语法&#xff1a; setex 【key】【过期时间&#xff08;单位秒&#xff09;】【value】 key重…

时隔 20 年,这个编程语言再次“称王”

近日&#xff0c;全球知名的编程语言流行度排行榜网站 TIOBE 公布了 1 月编程指数信息。前三的编程语言是Python、C 和C&#xff0c;第四为Java&#xff0c;第五是C#。 TIOBE 的 2022 年度编程语言最终花落 C&#xff0c;也是它时隔 20 年后第二次赢得这一称号。 “年度编程语…

行云创新受邀出席2023中国(深圳)阿联酋(迪拜)经贸合作交流会

1月10日&#xff0c;2023中国&#xff08;深圳&#xff09;-阿联酋&#xff08;迪拜&#xff09;经贸合作交流会成功举办。本次交流会充分展示了深圳和迪拜两地城市营商环境和政策优势&#xff0c;并围绕科技创新、数字经济、港口物流等领域发展经验展开分享&#xff0c;来自两…

Linux网络常用命令(ifconfig/ethtool/nmon+n)

Linux网络常用命令ifconfig可设置网络设备的状态&#xff0c;或是显示目前的设置ethtool 是用于查询及设置网卡参数的命令服务端监控工具&#xff1a;nmonnmon可监控的数据类型ifconfig可设置网络设备的状态&#xff0c;或是显示目前的设置 显示linux系统中当前服务器中的全部…

【并发】并发锁机制-深入理解synchronized(二)

【并发】并发锁机制-深入理解synchronized&#xff08;二&#xff09; synchronized 高级篇&#xff08;底层原理&#xff09; synchronized是JVM内置锁&#xff0c;基于Monitor机制实现。 这个Monitor就是管程的意思&#xff0c;它可以控制线程&#xff0c;让其陷入等待&am…

想去看更大的世界,社科院与杜兰大学金融管理硕士项目给予你前行的勇气

当我们的工作生活趋于稳定&#xff0c;我们那颗不安分的心就按捺不住的跳动&#xff0c;想要去看更美的风景&#xff0c;探索更大的世界。所谓遥不可及的梦想才是你见过更大世界的证明&#xff0c;社科院与杜兰大学金融管理硕士项目给予你前行的勇气。一定要不断提高自己的认知…

浅谈函数栈帧(Stack Frame)

&#x1f499;作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;C 本文目录 什么是栈帧 在调试中观察 总结 什么是栈帧 那我们先来看看什么是栈&#xff1a; 栈(stack)是限定仅在表尾进行插入或者删除的线性表。栈是一种数据结构&#xff0c;它按照后进先出的原则存储…

C进阶:字符函数和内存函数

字符串函数和内存函数字符函数和内存函数字符函数求字符串长度strlen长度不受限制的字符串函数strcpystrcatstrcmp长度受限制的字符串函数strncpystrncatstrncmp字符串查找strstrstrtok错误信息报告strerror字符函数&#xff1a;内存函数memcpymemmovememcmpmemset库函数的模拟…

2023年网络安全比赛--跨站脚本攻击中职组(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 1.访问服务器网站目录1,根据页面信息完成条件,将获取到弹框信息作为flag提交; 2.访问服务器网站目录2,根据页面信息完成条件,将获取到弹框信息作为flag提交; 3.访问服务器网站目录…

javaWeb jsp

概念&#xff1a; Java Server Pages&#xff0c;Java服务端页面。 其中既可以定义 HTML、JS、CSS等静态内容&#xff0c;还可以定义 Java代码的动态内容 JSP HTML Java。最终解析为一个servlet输出给前端。 jsp实践 <dependency> <groupId>javax.servlet…

ASP.NET Core 3.1系列(24)——依赖注入框架之Autofac

1、前言 前面的博客已经介绍过ASP.NET Core中内置IoC容器的使用方法。对于规模较小的项目来说&#xff0c;内置容器完全够用。但在实际开发业务中&#xff0c;一般更推荐开发者使用Autofac作为系统的IoC容器。相较于微软提供的内置容器&#xff0c;Autofac无论是在功能性还是灵…

Python Socket联机自动匹配双人五子棋(含登录注册系统与界面,数据库连接,可作结课作业,可用于学习)

1、前言 首先&#xff0c;关于源码的获取&#xff0c;本人提供了三种方式&#xff1a; 直接从文章里面CtrlC&#xff0c;CtrlV&#xff0c;然后按照我已给的文件结构搞一下即可&#xff1b;通过积分下载上传到CSDN的资源&#xff1b;点开本人的主页&#xff0c;点击“查看详细…