平衡二叉树AVL左旋,右旋,双旋——java
概念分析
- 平衡二叉树也叫平衡二叉搜索树,又被称为AVL树,它能保证查询效率较高
- 他具有以下特点:它是一颗空树或者它的左右两颗子树的高度差的绝对值不大于一,并且左右子树都是平衡二叉树,其也满足二叉排序树的特点
小练习
看过上述的概念之后,我们应该可以分辨哪些是平衡二叉树。
为什么出现平衡二叉树
上一章讲的是二叉排序树,其效率一般是大于链表的,那么如果我们添加的数列是{1,2,3,4,5,6}呢?
我们会发现一个问题,我们构建的BST成为了一个链表形状,但是效率却不如链表,因为进行遍历查找还需要判断是否有左子结点。
建立节点类和AVL类
结点类
class Node{int value;Node left;Node right;public Node(int value){this.value = value;}/*** 查找待删除的节点* @param value 待删除节点的值* @return 返回被删除的节点 不存在该节点则返回null*/public Node search(int value){if(this.value == value){return this;} else if (value < this.value){ // 二叉排序树的左子结点都小于其父结点的值if (this.left == null){return null;}else {return this.left.search(value);}}else { // 如果不小于其父结点的值 那么可以判断在右子结点if (this.right == null){return null;}else {return this.right.search(value);}}}/*** 查找待删除节点的父结点* @param value 待删除节点的值* @return 待删除节点的父结点*/public Node 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;}}}/*** BST添加节点* @param node 待添加的节点*/public void add(Node node){if(node == null){return;}// 添加的节点小于此节点的值 我们将其添加到左子结点处if (node.value < this.value){if (this.left == null){this.left = node;}else {// 如果其左子结点不为空,那么我们将要插入的节点与其左子结点进行比较this.left.add(node);}}else { // 相等的或者大于的都添加到右子结点if (this.right == null){this.right = node;}else {this.right.add(node);}}// 左旋转发生在添加之后,如果右子树高度比左子树的高度超过一if (this.rightHeight() - 1 > this.leftHeight()){if (this.right != null && this.right.leftHeight() > this.right.rightHeight()){this.right.rightRotate();}this.leftRotate();}// 左子树高度比右子树的高度超过一if (this.leftHeight() - 1 > this.rightHeight()){if (this.left != null && this.left.rightHeight() > this.left.leftHeight()){this.left.leftRotate();}this.rightRotate();}}/*** BST中序遍历*/public void inorderTraversal(){if (this.left != null){this.left.inorderTraversal();}System.out.print(this.value + "\t");if (this.right != null){this.right.inorderTraversal();}}/*** 以当前节点为根节点的树的高度* @return 高度*/public int height(){return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;}/*** 以当前结点为根节点的右子树的高度* @return 右子树的高度*/public int rightHeight(){if (this.right == null){return 0;}else {return this.right.height();}}/*** 以当前节点为根节点的左子树的高度* @return 左子树的高度*/public int leftHeight(){if (this.left == null){return 0;}else {return this.left.height();}}// 左旋转public void leftRotate(){// 1. 新建一个节点 值等于当前结点Node newNode = new Node(this.value);// 2. 新结点的左子结点指向当前结点的左子结点newNode.left = this.left;// 2. 新结点的右子结点指向当前结点的右子结点的左子结点newNode.right = this.right.left;// 4. 当前结点的值等于当前结点的右子结点this.value = this.right.value;// 5. 当前结点的有右子树等于右子结点的右子树this.right = this.right.right;// 6. 当前结点的左子树设置为新建的结点this.left = newNode;}// 右旋转public void rightRotate(){// 1. 新建一个结点 值等于当前结点Node newNode = new Node(this.value);// 2. 新结点的右子结点指向当前结点的右子结点newNode.right = this.right;// 3. 新结点的左子结点等于当前结点的左子结点的右子结点newNode.left = this.left.right;// 4. 当前结点的值等于当前结点的左子结点this.value = this.left.value;// 5. 当前结点的左子树等于当前结点的左子树的左子树this.left = this.left.left;// 6. 当前结点的右子树等于新建的节点this.right = newNode;}@Overridepublic String toString() {return "Node{" +"value=" + value +'}';}
}
AVL类
class AVLTree{Node root;/*** 找到左子树中最大的值* @param node 左子树的根节点* @return 返回左子树中最大的值*/public int deleteLeftMax(Node node){Node temp = node;while (temp.right != null){temp = temp.right;}// 找到左侧最大 将其删除temp = delete(temp.value);return temp.value;}/*** 找到右子树中最小的值* @param node 右子树的根节点* @return 返回右子树中最小的值*/public int deleteRightMin(Node node){Node temp = node;while (temp.left != null){temp = temp.left;}// 找到右侧最小 将其删除temp = delete(temp.value);return temp.value;}public Node delete(int value){// 如果当前BST为空 直接结束流程 返回空if (root == null){return null;} else {// 1. 先去找到待删除的节点Node targetNode = search(value);// 没有找到待删除节点if (targetNode == null){return null;}// 如果可以找到待删除节点 并且BST只有一个rootif (root.left == null && root.right == null){Node temp = root;root = null;return temp;}// 如果可以找到待删除节点 并且BST不只有一个root,那么待删除节点必然有父结点Node parent = searchParent(value);// 判断待删除节点是否是叶子节点if(targetNode.left == null && targetNode.right == null){if (parent.left != null &&parent.left.value == value){Node temp = parent.left;parent.left = null;return temp;} else if (parent.right != null && parent.right.value == value){Node temp = parent.right;parent.right = null;return temp;}else {return null;}}else if (targetNode.left != null && targetNode.right != null){// 这种情况是待删除节点有两个子结点int target;
// target = deleteRightMin(targetNode.right);target = deleteLeftMax(targetNode.left);targetNode.value = target;}else {// 待删除节点只有一个子结点// 待删除节点的子结点是左子结点if(targetNode.left != null){if (parent != null){if (parent.left.value == value){parent.left = targetNode.left;}else {parent.right = targetNode.left;}}else {root = targetNode.left;}}else {if (parent != null){if (parent.left.value == value){parent.left = targetNode.right;}else {parent.right = targetNode.right;}}else {root = targetNode.right;}}return targetNode;}return null;}}public Node search(int value){if (root == null){return null;}else {return root.search(value);}}public Node searchParent(int value){if (root == null){return null;}else {return root.searchParent(value);}}/*** BST添加节点* @param node 待被添加的节点*/public void add(Node node){if (root == null) {root = node;}else {root.add(node);}}/*** BST实现中序遍历*/public void inorderTraversal(){if (root == null){System.out.println("BST为空");}else {root.inorderTraversal();}}}
一个小问题
我们怎么得到当前结点为根节点的子树的高度呢?
递归解决,取左边的最高值和右边最高值,取到最大值以后加上结点本身即为高度
/*** 以当前节点为根节点的树的高度* @return 高度*/public int height(){return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;}
取左子树的高度:
/*** 以当前节点为根节点的右子树的高度* @return 右子树的高度*/public int leftHeight(){if (this.left == null){return 0;}else {return this.left.height();}}
取右子树的高度:
/*** 以当前结点为根节点的右子树的高度* @return 右子树的高度*/public int rightHeight(){if (this.right == null){return 0;}else {return this.right.height();}}
左旋转
根据数列 {4, 3, 6, 5, 7, 8}创建一颗二叉排序树,我们发现树是这个样子的
这个是不满足二叉排序树的,我们要将其构建为二叉排序树,就需要进行左旋转。
步骤如下:
1. 我们新建一个结点,其值等于根节点
2. 将新建的结点的左子结点指向根节点的左子结点
3. 将新建节点的右子结点指向根节点的右子结点的左子结点
4. 将根节点的值置为根节点的右子结点的值
5. 将根节点的右子结点置为根节点的右子结点的右子结点
6. 将根节点的左子结点的值置为新建的节点
图解:
我们新建一个结点,值为4,其左子结点指向3,右子结点是7,之后将根节点的值置为6,根节点的右子结点置为根节点的右子结点的右子结点,那么就是我上图中右边画的这个二叉树,此时满足平衡二叉树。
代码实现:
public void leftRotate(){// 1. 新建一个节点 值等于当前结点Node newNode = new Node(this.value);// 2. 新结点的左子结点指向当前结点的左子结点newNode.left = this.left;// 2. 新结点的右子结点指向当前结点的右子结点的左子结点newNode.right = this.right.left;// 4. 当前结点的值等于当前结点的右子结点this.value = this.right.value;// 5. 当前结点的有右子树等于右子结点的右子树this.right = this.right.right;// 6. 当前结点的左子树设置为新建的结点this.left = newNode;
}
右旋转
根据数列{10, 12, 8, 9, 7, 6}创建一颗二叉排序树,我们发现树是这个样子的
此时左子树的高度大于了右子树的高度,且大于的值超过了1,我们需要对其进行右旋
步骤如下:
1. 新建一个结点,其值等于根节点
2. 新建结点的右子节点等于根节点右子节点
3. 新建结点的左子结点等于根结点的左子结点的右子结点
4. 根节点的值等于根节点的左子结点的值
5. 根节点的左子结点等于根结点的左子结点的左子结点
6. 根节点的右子结点等于新建的结点
图解:
代码如下
public void rightRotate(){// 1. 新建一个结点 值等于当前结点Node newNode = new Node(this.value);// 2. 新结点的右子结点指向当前结点的右子结点newNode.right = this.right;// 3. 新结点的左子结点等于当前结点的左子结点的右子结点newNode.left = this.left.right;// 4. 当前结点的值等于当前结点的左子结点this.value = this.left.value;// 5. 当前结点的左子树等于当前结点的左子树的左子树this.left = this.left.left;// 6. 当前结点的右子树等于新建的节点this.right = newNode;
}
双旋转
可是有的时候单旋转并不能解决问题,如下:
int[] arr = {10, 11, 7, 6, 8, 9};
使用此数列进行右旋转,会产生什么情况呢?
这就尴尬了,怎么右旋转完了感觉还得左旋转一下呢?
问题分析:
因为根节点的左子结点的右子树的高度大于其左子结点的左子树的高度,我们进行右旋转时,需要将根节点的左子树的右子结点设置为新结点的左子结点(看不懂的往上翻),根节点的左子结点设置为根节点的左子结点的左子结点,那问题来了,现在根结点的左子节点的左子树的高度已然小于根节点的右子结点的右子树的高度了,我们再将其旋转,只不过是将其左子树高变成右子树高而已。
解决办法:
我们可以先将根节点的左子结点进行一次左旋转,之后对根节点进行右旋转
如果是右子树出现这种情况,那我们让其右旋,之后左旋即可
情况限制:
必须是满足根节点的左右子树高度差的绝对值大于一,并且较高的那颗子树如果是左子树,这棵子树的根节点的右子树的高度大于其左子树的高度,或者较高的那颗子树是右子树,那么就是右子树的左子树的高度大于右子树的右子树的高度,这时候我们需要双旋转!!!
代码分析:
// 插入在add方法的最后面,每次插入我们都进行一次判断是否需要旋转// 左旋转发生在添加之后,如果右子树高度比左子树的高度超过一if (this.rightHeight() - 1 > this.leftHeight()){if (this.right != null && this.right.leftHeight() > this.right.rightHeight()){this.right.rightRotate();}this.leftRotate();}// 左子树高度比右子树的高度超过一if (this.leftHeight() - 1 > this.rightHeight()){if (this.left != null && this.left.rightHeight() > this.left.leftHeight()){this.left.leftRotate();}this.rightRotate();}
小结
平衡二叉树是为了改进二叉排序树而设计的,重点是左旋转和右旋转,双旋转是指在满足左旋转或右旋转的基础上,再进行一次判断,然后将其调整为单次旋转可以调整为平衡二叉树的程度。
星光不符赶路人。
平衡二叉树AVL左旋,右旋,双旋——java相关推荐
- 搞懂平衡二叉树的左旋右旋双旋(Java实现)
刚看到韩顺平老师的数据结构与算法对于平衡二叉树的讲解(最后会附上地址),有如下理解,希望能帮助大家!哪里需要改正的欢迎指正! 平衡二叉树:一种二叉排序树(BST Binary Sort Tree)的升 ...
- 平衡二叉树的左旋右旋详解 看不懂你打我
平衡二叉树的左旋右旋 看不懂你打我 左旋右旋的操作 为什么要左旋右旋 左旋右旋能保持排序二叉排序树的性质吗 下次写平衡二叉树的LL.RR.LR.RL. 左旋右旋的操作 1.左旋:对X节点左旋,即以X的 ...
- [ 数据结构 ] 平衡二叉树(AVL)--------左旋、右旋、双旋
0 引出 数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在 回顾:二叉搜索树 左子树全部为空,从形式上看,更像一个单链表. 插入速度没有影响 查询速度明显降低(因为需 ...
- avl树左旋右旋的理解
一直没搞懂非平衡二叉树变平衡二叉树时左旋右旋,今天下定决心搞懂,然后在众多博客中终于找到了这样一篇,非常形象,记录如下: AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大 ...
- 二叉树旋转--左旋|右旋
二叉树旋转 二叉树的旋转主要是应用在AVL树中,当添加一个节点时候导致左右两个子树的高度差不在是-1 , 1 , 0而变成了2 或者-2.此时就需要用到左旋/右旋了.当然左右旋或者有左旋也是基于左旋和 ...
- 左旋右旋问题一次搞定!!!
左旋右旋问题的解决 编程思想 1.在左右旋函数中实现该函数功能首先要想好如何存放移位后的字符元素 2.左旋时将字符串首元素赋给临时变量tmp而后将字符串元素依次前移一位 3.将tmp的值再赋给字符串的 ...
- HashMap 数据结构之红黑树, 红黑树在什么时候左旋 右旋 如何旋转
树结构是数据结构中最经典最常用的结构之一,也是面试中常问的面试题,最近学习了一下红黑树的知识,记录整理一下 文章目录 一.红黑树的特征 二.变色左旋和右旋 1.变色规则 2.左旋 3.右旋 总结 前言 ...
- 字符串左旋右旋——三步旋转法和移相法
题目:实现一个函数,可以左旋字符串中的k个字符. AABCD左旋一个字符得到ABCDA AABCD左旋两个字符得到BCDAA 方法一:三步旋转法 左旋程序思路:首先根据画图得知左旋后的结果,然后在分析 ...
- nyoj202 红黑树 (二叉树的左旋右旋)
题目202 题目信息 运行结果 本题排行 讨论区 红黑树 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 什么是红黑树呢?顾名思义,跟枣树类似,红黑树是一种叶子是黑色果子 ...
- 详解红黑树之左旋右旋
为什么要左旋右旋? 为了使得左右子树的高度差在一定范围内,需要通过旋转调整,这样就可以保持平稳的搜索效率 左旋: 步骤 1.设原来E的父节点是father,那么左旋之后需要改变的是: 2.S和fath ...
最新文章
- linux发邮件安装什么意思,linux – 如何找出安装/发送电子邮件的邮件程序?
- 快速解决 Android SDK Manager 无法下载或者下载速度慢
- 学习	shell脚本之前的基础知识
- 【Pytorch神经网络理论篇】 06 神经元+神经网络模型+全连接网络模型
- 算法笔记_036:预排序(Java)
- oracle imp导入库到指定表空间
- android fragment面试,Android fragment之间传递数据的方式?
- 大数据技术平台主要分为哪几类
- 【渝粤教育】电大中专计算机职业素养 (7)作业 题库
- PAIP.通过公共网络传递秘密信息.txt (包括语音和文字)
- 《Linux私房菜》——一、linux基础整理
- 《人月神话》-第19章-20年后的《人月神话》
- (六)Ps剪切蒙版/图框
- Perfmon监控Windows进程性能
- 【20230407】NVIDIA显卡算力、Jetson比较
- ubuntu18.04 安装 roboware-studio
- Android 反编译APK详解
- matlab二阶锥,二次规划和锥规划
- MATLAB Simulink
- MathWorks 中国
- 一代传奇SIFT算法专利到期
- 精品课 - Python 基础