[ 数据结构 ] 平衡二叉树(AVL)--------左旋、右旋、双旋
0 引出
数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在 回顾:二叉搜索树
- 左子树全部为空,从形式上看,更像一个单链表.
- 插入速度没有影响
- 查询速度明显降低(因为需要依次比较), 不能发挥 BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢
- 解决方案-平衡二叉树(AVL)
1 平衡二叉树
- 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树, 可以保证查询效率较高。
- 具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等
- 如何保证在创建二叉排序树的过程中,一直都满足平衡二叉树呢?
- 在添加节点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 左旋
- 主要操作就是改变左旋节点4和其右子节点6的指针指向
- 首先拷贝4,新的4左指向3,右指向5
- 原来的4改为6,左指向新的4,右指向7
- 原来的6由于没有被指向,GC回收
- 左旋完成
//左旋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 右旋
- 属于左旋的镜像操作,改变右旋节点10和其左子节点8的指针指向
- 首先拷贝10,新的10左指向9,右指向12
- 原来的10改为8,左指向7,右指向新的10
- 原来的8由于没有被指向,GC回收
- 右旋完成
//右旋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 双旋
问题:前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列
int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL 树.
int[] arr = {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL 树
解决如下:
当符合右旋转的条件时,如果它的左子树的右子树高度大于它的左子树的高度,先对当前这个结点的左节点进行左旋转,再对当前结点进行右旋转的操作即可
当符合左旋转的条件时,如果它的右子树的左子树高度大于它的右子树的高度,先对当前这个结点的右节点进行右旋转,再对当前结点进行左旋转的操作即可
//添加节点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;}@Overridepublic 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);}
}
[ 数据结构 ] 平衡二叉树(AVL)--------左旋、右旋、双旋相关推荐
- 搞懂平衡二叉树的左旋右旋双旋(Java实现)
刚看到韩顺平老师的数据结构与算法对于平衡二叉树的讲解(最后会附上地址),有如下理解,希望能帮助大家!哪里需要改正的欢迎指正! 平衡二叉树:一种二叉排序树(BST Binary Sort Tree)的升 ...
- 平衡二叉树的左旋右旋详解 看不懂你打我
平衡二叉树的左旋右旋 看不懂你打我 左旋右旋的操作 为什么要左旋右旋 左旋右旋能保持排序二叉排序树的性质吗 下次写平衡二叉树的LL.RR.LR.RL. 左旋右旋的操作 1.左旋:对X节点左旋,即以X的 ...
- avl树左旋右旋的理解
一直没搞懂非平衡二叉树变平衡二叉树时左旋右旋,今天下定决心搞懂,然后在众多博客中终于找到了这样一篇,非常形象,记录如下: AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大 ...
- HashMap 数据结构之红黑树, 红黑树在什么时候左旋 右旋 如何旋转
树结构是数据结构中最经典最常用的结构之一,也是面试中常问的面试题,最近学习了一下红黑树的知识,记录整理一下 文章目录 一.红黑树的特征 二.变色左旋和右旋 1.变色规则 2.左旋 3.右旋 总结 前言 ...
- 二叉树旋转--左旋|右旋
二叉树旋转 二叉树的旋转主要是应用在AVL树中,当添加一个节点时候导致左右两个子树的高度差不在是-1 , 1 , 0而变成了2 或者-2.此时就需要用到左旋/右旋了.当然左右旋或者有左旋也是基于左旋和 ...
- nyoj202 红黑树 (二叉树的左旋右旋)
题目202 题目信息 运行结果 本题排行 讨论区 红黑树 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 什么是红黑树呢?顾名思义,跟枣树类似,红黑树是一种叶子是黑色果子 ...
- 左旋右旋问题一次搞定!!!
左旋右旋问题的解决 编程思想 1.在左右旋函数中实现该函数功能首先要想好如何存放移位后的字符元素 2.左旋时将字符串首元素赋给临时变量tmp而后将字符串元素依次前移一位 3.将tmp的值再赋给字符串的 ...
- 字符串左旋右旋——三步旋转法和移相法
题目:实现一个函数,可以左旋字符串中的k个字符. AABCD左旋一个字符得到ABCDA AABCD左旋两个字符得到BCDAA 方法一:三步旋转法 左旋程序思路:首先根据画图得知左旋后的结果,然后在分析 ...
- 详解红黑树之左旋右旋
为什么要左旋右旋? 为了使得左右子树的高度差在一定范围内,需要通过旋转调整,这样就可以保持平稳的搜索效率 左旋: 步骤 1.设原来E的父节点是father,那么左旋之后需要改变的是: 2.S和fath ...
最新文章
- PBR游戏3D模型合集包 PBR Game 3D-Models Bundle February 2022
- springboot学习笔记(三)
- perl学习之:localtime
- 一段H264数据的分析
- 如何从 0 到 1,搭建一个完整的 Kubernetes 集群?
- 三星GalaxyNote20系列全新渲染图曝光:屏下摄像头来了?
- bootstrap-引用-命名来源
- 你距离哈佛学霸到底有多远?实力证明,真正的学霸精神不是智商,而是。。。
- Android 的窗口管理系统 (View, Canvas, WindowManager)
- python高级数据筛选的方法_使用python对多个txt文件中的数据进行筛选的方法
- 稚晖君的HoloCubic 透明棱镜小电视
- mac pe 制作教程
- 证券运维外包第3个月工作总结
- svn提交报错,Error running context: 远程主机强迫关闭了一个现有的连接
- IC设计行业都有哪些不错的公司(外企篇)
- 服务器和普通电脑有什么区别?
- 记录如何在KVM上手动部署山石vfw
- 【java逻辑运算】java逻辑运算符的使用与计算
- python自动化测试 namp端口扫描
- 销售用到的心理学技巧有哪些