[ 数据结构 ] 平衡二叉树(AVL)--------左旋、右旋、双旋

news2024/11/16 11:28:40

0 引出

数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在 回顾:二叉搜索树

image-20230108221827379.png

  1. 左子树全部为空,从形式上看,更像一个单链表.
  2. 插入速度没有影响
  3. 查询速度明显降低(因为需要依次比较), 不能发挥 BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢
  4. 解决方案-平衡二叉树(AVL)

1 平衡二叉树

  1. 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树, 可以保证查询效率较高。
  2. 具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等
  3. 如何保证在创建二叉排序树的过程中,一直都满足平衡二叉树呢?
  4. 在添加节点add方法的末尾判断(也可以说在非叶子节点处):如果左右子树高度差>1,那么考虑左旋/右旋/双旋
//添加节点
    public void add(BNode bnode) {
        if (bnode == null) {
            return;
        }
        if (bnode.value < this.value) {
            if (this.left == null) {
                this.left = bnode;
            } else {
                this.left.add(bnode);
            }
        } else {
            if (this.right == null) {
                this.right = bnode;
            } else {
                this.right.add(bnode);
            }
        }

        //考虑左旋
//        if (rightTreeHeight() - leftTreeHeight() > 1) {
//            leftRotate();
//        }
        //考虑右旋
//        if (leftTreeHeight()-rightTreeHeight() > 1) {
//            rightRotate();
//        }
        //考虑双旋(单独的左旋/右旋可能不能解决问题)
        if (rightTreeHeight() - leftTreeHeight() > 1) {
            if (right != null && right.leftTreeHeight() > right.rightTreeHeight()) {
                right.rightRotate();
            }
            leftRotate();
            return;//省的走下面代码,影响效率(因为涉及递归调用)
        }
        if (leftTreeHeight()-rightTreeHeight() > 1) {
            if (left != null && left.leftTreeHeight() < left.rightTreeHeight()) {
                left.leftRotate();
            }
            rightRotate();
        }
    }
    
    //返回当前节点的高度
    public int getHeight() {
        return Math.max(left == null ? 0 : left.getHeight(), right == null ? 0 : right.getHeight())+1;
    }
    //左子树高度
    public int leftTreeHeight() {
        if (left == null) {
            return 0;
        } else {
            return left.getHeight();
        }
    }
    //右子树高度
    public int rightTreeHeight() {
        if (right == null) {
            return 0;
        } else {
            return right.getHeight();
        }
    }

2 左旋

image-20230108223734856.png

  1. 主要操作就是改变左旋节点4和其右子节点6的指针指向
  2. 首先拷贝4,新的4左指向3,右指向5
  3. 原来的4改为6,左指向新的4,右指向7
  4. 原来的6由于没有被指向,GC回收
  5. 左旋完成
//左旋
    public void leftRotate() {
        BNode newNode = new BNode(value);
        newNode.left = this.left;
        newNode.right = this.right.left;
        this.setValue(right.value);
        this.setLeft(newNode);
        this.setRight(this.right.right);
    }

3 右旋

image-20230109115239759.png

  1. 属于左旋的镜像操作,改变右旋节点10和其左子节点8的指针指向
  2. 首先拷贝10,新的10左指向9,右指向12
  3. 原来的10改为8,左指向7,右指向新的10
  4. 原来的8由于没有被指向,GC回收
  5. 右旋完成
//右旋
    public void rightRotate() {
        BNode newNode = new BNode(value);
        newNode.right = this.right;
        newNode.left = this.left.right;
        this.setValue(left.value);
        this.setRight(newNode);
        this.setLeft(this.left.left);
    }

4 双旋

image-20230109120404116.png

  1. 问题:前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列

    int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL 树.

    int[] arr = {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL 树

  2. 解决如下:

  3. 当符合右旋转的条件时,如果它的左子树的右子树高度大于它的左子树的高度,先对当前这个结点的左节点进行左旋转,再对当前结点进行右旋转的操作即可

  4. 当符合左旋转的条件时,如果它的右子树的左子树高度大于它的右子树的高度,先对当前这个结点的右节点进行右旋转,再对当前结点进行左旋转的操作即可

//添加节点
    public void add(BNode bnode) {
        if (bnode == null) {
            return;
        }
        if (bnode.value < this.value) {
            if (this.left == null) {
                this.left = bnode;
            } else {
                this.left.add(bnode);
            }
        } else {
            if (this.right == null) {
                this.right = bnode;
            } else {
                this.right.add(bnode);
            }
        }

        //考虑左旋
//        if (rightTreeHeight() - leftTreeHeight() > 1) {
//            leftRotate();
//        }
        //考虑右旋
//        if (leftTreeHeight()-rightTreeHeight() > 1) {
//            rightRotate();
//        }
        //考虑双旋(单独的左旋/右旋可能不能解决问题)
        if (rightTreeHeight() - leftTreeHeight() > 1) {
            if (right != null && right.leftTreeHeight() > right.rightTreeHeight()) {
                right.rightRotate();
            }
            leftRotate();
            return;//省的走下面代码,影响效率(因为涉及递归调用)
        }
        if (leftTreeHeight()-rightTreeHeight() > 1) {
            if (left != null && left.leftTreeHeight() < left.rightTreeHeight()) {
                left.leftRotate();
            }
            rightRotate();
        }
    }

5 完整AVL树代码

//平衡二叉树
public class App05_AVLTree {
    public static void main(String[] args) {
        AVLTree tree = new AVLTree();
//        int[] arr = {4,3,6,5,7,8};
//        int[] arr = {10,12, 8, 9, 7, 6};
//        int[] arr = {10, 11, 7, 6, 8, 9};
        int[] arr = {2,1,6,5,7,3};
        for (int i : arr) {
            tree.add(new BNode(i));
        }
        tree.infixOrder();
        System.out.println("根高度:"+tree.getRoot().getHeight());
        System.out.println("左子树高度:"+tree.getRoot().leftTreeHeight());
        System.out.println("右子树高度:"+tree.getRoot().rightTreeHeight());
    }
}

class AVLTree {
    private BNode root;

    public BNode getRoot() {
        return root;
    }

    //中序遍历
    public void infixOrder() {
        if (root != null) {
            root.infixOrder();
        } else {
            System.out.println("树为空!!!");
        }
    }

    //添加节点
    public void add(BNode nd) {
        if (root == null) {
            root = nd;
        } else {
            root.add(nd);
        }
    }

    //找到删除节点及其父节点
    public BNode search(int value) {
        if (root == null) {
            return null;
        } else {
            return root.search(value);
        }
    }
    public BNode searchParent(int value) {
        if (root == null) {
            return null;
        } else {
            if (root.getLeft() == null && root.getRight() == null) {
                return null;
            } else {
                return root.searchParent(value);
            }
        }
    }

    //删除节点
    public void delNode(int value) {
        if (root == null) {
            return;
        } else {
            BNode target = search(value);
            BNode parent = searchParent(value);//为空则只能是删根节点
            //有的删才行
            if (target != null) {
                if (parent != null) {
                    //删叶子节点
                    if (target.getLeft() == null && target.getRight() == null) {
                        if (parent.getLeft()!=null&&parent.getLeft().getValue() == value) {
                            parent.setLeft(null);
                        } else {
                            parent.setRight(null);
                        }

                        //删有双子树的节点
                    } else if (target.getLeft() != null && target.getRight() != null) {
                        //target为左子树
                        if (parent.getLeft().getValue() == value) {
                            int max = delLeftTreeMax(target);
                            target.setValue(max);
                            //target为右子树
                        } else {
                            int min = delRightTreeMin(target);
                            target.setValue(min);
                        }

                        //删有单子树的节点
                    } else {
                        //4种可能,左左,左右,右左,右右
                        if (parent.getLeft()!=null&& parent.getLeft().getValue() == value && target.getLeft() != null) {
                            parent.setLeft(target.getLeft());
                        } else if (parent.getLeft() != null && parent.getLeft().getValue() == value && target.getRight() != null) {
                            parent.setLeft(target.getRight());
                        } else if (parent.getRight().getValue() == value && target.getLeft() != null) {
                            parent.setRight(target.getLeft());
                        } else {
                            parent.setRight(target.getRight());
                        }
                    }
                } else {//没有父节点,说明就是删root,因为删除的节点存在
                    //叶子
                    if (root.getLeft() == null && root.getRight() == null) {
                        root = null;
                        //有双子树
                    } else if (root.getLeft() != null && root.getRight() != null) {
                        //用左子树最大或右子树最小都可以
                        root.setValue(delLeftTreeMax(root.getLeft()));

                        //有单子树
                    } else {
                        if (root.getRight() != null) {
                            root = root.getRight();
                        } else {
                            root = root.getLeft();
                        }
                    }
                }
            } else {
                System.out.println("删除的节点不存在!!!");
            }
        }
    }

    //删左子树,最大值
    public int delLeftTreeMax(BNode nd) {
        BNode temp = nd;
        while (true) {
            if (temp.getRight() == null) {
                break;
            }
            temp = temp.getRight();
        }
        delNode(temp.getValue());
        return temp.getValue();
    }
    //删右子树,最小值
    public int delRightTreeMin(BNode nd) {
        BNode temp = nd;
        while (true) {
            if (temp.getLeft() == null) {
                break;
            }
            temp = temp.getLeft();
        }
        delNode(temp.getValue());
        return temp.getValue();
    }
}

class BNode {
    private int value;
    private BNode left;
    private BNode right;

    public BNode(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public BNode getLeft() {
        return left;
    }

    public void setLeft(BNode left) {
        this.left = left;
    }

    public BNode getRight() {
        return right;
    }

    public void setRight(BNode right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "BNode [value=" + value + "]";
    }

    //中序遍历
    public void infixOrder() {
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this);
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    //添加节点
    public void add(BNode bnode) {
        if (bnode == null) {
            return;
        }
        if (bnode.value < this.value) {
            if (this.left == null) {
                this.left = bnode;
            } else {
                this.left.add(bnode);
            }
        } else {
            if (this.right == null) {
                this.right = bnode;
            } else {
                this.right.add(bnode);
            }
        }

        //考虑左旋
//        if (rightTreeHeight() - leftTreeHeight() > 1) {
//            leftRotate();
//        }
        //考虑右旋
//        if (leftTreeHeight()-rightTreeHeight() > 1) {
//            rightRotate();
//        }
        //考虑双旋(单独的左旋/右旋可能不能解决问题)
        if (rightTreeHeight() - leftTreeHeight() > 1) {
            if (right != null && right.leftTreeHeight() > right.rightTreeHeight()) {
                right.rightRotate();
            }
            leftRotate();
            return;//省的走下面代码,影响效率(因为涉及递归调用)
        }
        if (leftTreeHeight()-rightTreeHeight() > 1) {
            if (left != null && left.leftTreeHeight() < left.rightTreeHeight()) {
                left.leftRotate();
            }
            rightRotate();
        }
    }

    //查找要删除节点
    public BNode search(int value) {
        if (this.value == value) {
            return this;
        } else if (value < this.value) {//这里对于==的处理与add方法保持一致
            if (this.left != null) {
                return this.left.search(value);
            }
        } else {
            if (this.right != null) {
                return this.right.search(value);
            }
        }
        return null;
    }

    //查找要删除的节点的父节点
    public BNode searchParent(int value) {
        if ((this.left != null && this.left.value == value) ||
                this.right != null && this.right.value == value) {
            return this;
        } else {
            if (value < this.value && this.left != null) {
                return this.left.searchParent(value);
            } else if (value >= this.value && this.right != null) {
                return this.right.searchParent(value);
            } else {
                return null;
            }
        }
    }

    //返回当前节点的高度
    public int getHeight() {
        return Math.max(left == null ? 0 : left.getHeight(), right == null ? 0 : right.getHeight())+1;
    }
    //左子树高度
    public int leftTreeHeight() {
        if (left == null) {
            return 0;
        } else {
            return left.getHeight();
        }
    }
    //右子树高度
    public int rightTreeHeight() {
        if (right == null) {
            return 0;
        } else {
            return right.getHeight();
        }
    }

    //左旋
    public void leftRotate() {
        BNode newNode = new BNode(value);
        newNode.left = this.left;
        newNode.right = this.right.left;
        this.setValue(right.value);
        this.setLeft(newNode);
        this.setRight(this.right.right);
    }
    //右旋
    public void rightRotate() {
        BNode newNode = new BNode(value);
        newNode.right = this.right;
        newNode.left = this.left.right;
        this.setValue(left.value);
        this.setRight(newNode);
        this.setLeft(this.left.left);
    }
}

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

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

相关文章

第四十二章 动态规划——数字三角形模型

第四十二章 动态规划——数字三角形模型一、数字三角形模型1、什么是数字三角形模型二、例题1、AcWing 1015. 摘花生&#xff08;1&#xff09;问题&#xff08;2&#xff09;思路状态表示状态转移循环设计初末状态&#xff08;3&#xff09;代码2、AcWing 1018. 最低通行费&am…

第二章:感知机

文章目录2.1感知机模型2.2感知机策略2.3梯度下降法2.4感知机-原始算法形式2.5感知机-原始算法实例2.6感知机-对偶形式2.7感知机-对偶形式实例2.8感知机算法收敛性证明如下&#xff1a;2.1感知机模型 2.2感知机策略 损失函数非负&#xff0c;策略是选择使其最小的模型参数 2.3梯…

【Linux工具】-gcc/g++

gcc/g一&#xff0c;简介二&#xff0c;代码的翻译过程1&#xff0c;预处理2&#xff0c;编译3&#xff0c;汇编4&#xff0c;链接&#xff08;1&#xff09;静态库&#xff08;2&#xff09;动态库&#xff08;3&#xff09;动静态库比较三&#xff0c;常见选项一&#xff0c;…

模板(C++)

模板&#xff08;C&#xff09;泛型编程函数模板概念格式原理实例化匹配原则类模板定义格式实例化泛型编程 如何实现一个通用的交换函数呢&#xff1f; void Swap(int& left, int& right) {int tmp left;left right;right tmp; }void Swap(double& left, doub…

【Spring6源码・IOC】BeanDefinition的加载

哎呀&#xff0c;又是午夜时分&#xff0c;又是一个失眠的夜晚&#xff0c;和去年一样&#xff0c;记得去年今日&#xff0c;也是睡不着觉&#xff0c;就注册了csdn的账号&#xff0c;开始写东西&#xff0c;csdn真是深夜最好的安魂剂。 Spring都发布了6.0&#xff0c;这不赶紧…

2022.12青少年软件编程(Python)等级考试试卷(三级)

2022.12.10青少年软件编程(Python)等级考试试卷(三级) 一、单选题(共25题,共50分) 1.列表L1中全是整数,小明想将其中所有奇数都增加1,偶数不变,于是编写了如下图所示的代码。 请问,图中红线处,代码应该是?(D) A. x || 2 B. x ^ 2 C. x && 2 D. x %…

JS日期与字符串相互转换(时间格式化YYYY-MM-DD,Dayjs的使用)

JS日期与字符串相互转换——JS封装函数&#xff0c;Dayjs转换时间格式相关文章调用场景复现一、JS封装函数1、日期转字符串2、字符串转日期二、 Dayjs转换时间格式1、Dayjs快速安装与使用2、Dayjs格式化日期相关文章调用 文章内容文章链接JS数组对象——根据日期进行排序&…

南邮数据结构考研常用算法

1.链表 在带头结点的链表中&#xff0c;删除所有值为x的结点 void Del_X(Linklist &L,ElemType x){LNode *pL->next, *preL,*q;while (p!null){if (p->datax){qp;pp->next;pre->nextp;free(q);}else{prep;pp->next;}} }使用单链表进行插入排序 ListNode*…

数组常用方法总结 (1) :pop / push / shift / unshift

pop 从尾部删除一项。改变原有数组。返回删除掉的内容。 <template><div class"myBlock"><div class"tableBlock"><div class"title">{{ newObject ? "操作后的数组" : "操作前的数组" }}</d…

从0到1完成一个Vue后台管理项目(十、列表API封装、Table列表渲染、表格数据转换)

往期 从0到1完成一个Vue后台管理项目&#xff08;一、创建项目&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;二、使用element-ui&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;三、使用SCSS/LESS&#xff0c;安装图标库&#xff09; 从0到1完成一个Vu…

docker安装及安装过程中遇到的问题

安装Docker-CE 备注&#xff1a;Docker 安装&#xff0c;请参考 Docker 官⽅⽂档: Install Docker Engine on Ubuntu | Docker Documentation 也可参照如下命令进行快速安装。 Ubuntu 卸载旧版本&#xff08;视需要&#xff09; $ sudo apt-get remove docker docker-engin…

Java并发(4)- synchronized与CAS

引言 上一篇文章中我们说过&#xff0c;volatile通过lock指令保证了可见性、有序性以及“部分”原子性。但在大部分并发问题中&#xff0c;都需要保证操作的原子性&#xff0c;volatile并不具有该功能&#xff0c;这时就需要通过其他手段来达到线程安全的目的&#xff0c;在Ja…

因子图--isam相关内容总结

编辑切换为居中添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;编辑切换为居中添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;平方根法--通过平方根的方法&#xff0c;发现矩阵新增加的变量都会出现在最后一行。Givens旋转方法求解Ax…

Linux环境下,java调用C/C++动态库

目录 一、环境准备 1、安装gcc/g 2、下载jdk库并配置运行环境 二、配合Java程序创建C程序的动态库 1、生成要求清单 2、交给C 去实现 (1) 接口函数实现 (2) 创建动态库 (3) 检查动态库是否正常链接 3、测试&#xff1a;Java程序调用C动态库 一、环境准备 既然是同时…

百万级数据以Excel形式导出

(1).主要考虑到两个方面&#xff0c;第一个方面是内存溢出问题&#xff0c;所以选用阿里的EasyExcel因为它对POI进行了封装功能强大&#xff1b;第二个方面是由于excel版本导致Sheet存储容量不一样&#xff0c;cexcel2003(.xls)每个Sheet只能存6万多条数据而cexcel2007(xlsx)能…

【自学Python】Python布尔型(bool)

Python布尔型(bool) Python布尔型(bool)教程 Python 布尔类型也叫 bool 类型&#xff0c;Python 布尔类型取值为 True 和 False。Python bool 类型的 True 表示条件为真&#xff0c; False 表示条件为假。 Python布尔型(bool) Python 中的布尔类型可以当做 整数 来对待&…

LeetCode 287. 寻找重复数难度中等2004

&#x1f308;&#x1f308;&#x1f604;&#x1f604;欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 287. 寻找重复数难度中等2004&#xff0c;做好准备了么&#xff0c;那么开始吧。&#x1f332;&#x1f332;&#x1f434;&#x1f434;一、题目名称LeetCo…

怎么在Gitee(码云)上传一个项目(一分钟)

目录怎么在Gitee&#xff08;码云&#xff09;上传一个项目1、工具1.1、Git1.2、新建仓库2、上传流程3、回答上传项目流程中的几个疑问&#xff1f;怎么在Gitee&#xff08;码云&#xff09;上传一个项目 1、工具 1.1、Git 在Git官网或者利用镜像下载符合自己电脑操作系统版…

小众企业在选购低代码平台时需要注意什么

编者按&#xff1a;企业个性化定制需求如何实现&#xff1f;本文介绍了小众企业在选择低代码平台需要注意的点&#xff0c;帮助企业选出更合适得的软件平台。关键词&#xff1a;源码交付&#xff0c;数据整合&#xff0c;前后端分离&#xff0c;私有化部署&#xff0c;安全技术…

IB生物笔记:细胞学说

国际学校生物老师从0开始解读IB生物&#xff0c;感兴趣的同学记得收藏哦~IB生物分为SL(standard level)和HL(higher level)SL有6个topic∶细胞生物&#xff0c;分子生物&#xff0c;遗传学&#xff0c;生态学&#xff0c;物种进化以及多样性和人体生理。HL除了上述6个topic外还…