实现平衡二叉树(AVL树)的旋转
给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.
左边 BST 存在的问题分析:
1) 左子树全部为空,从形式上看,更像一个单链表.
2) 插入速度没有影响
3) 查询速度明显降低(因为需要依次比较), 不能发挥 BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢.
遇到以上情况,这时候就需要用我们今天的主角平衡二叉树来解决.
平衡二叉树的基本介绍:
1) 平衡二叉树也叫平衡 二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树, 可以保证查询效率较高。
2) 具有以下特点:它是一 一 棵空树或 它的左右两个子树的高度差的绝对值不超过 1,并且 左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等
例如:下图中的第一颗树和第二颗树都是平衡二叉树,而最后一棵树不是
将一颗普通的二叉排序树变成平衡二叉树有三种方法:
第一种方法:如果该树的左子树高度-右子树高度>1就需要使用右旋转来平衡该树.
第二种方法:如果该树的右子树高度-左子树高度>1就需要使用左旋转来平衡该树.
第三种方法:如果该树满足第一种或者第二种方法的情况,但是单独使用一次左(右)旋转也不能将其变成平衡二叉树的时候,就需要使用双旋转。
双旋转也分为两种情况:
第一种:先左旋转再右旋转
第二种:先右旋转再左旋转
以下来介绍左(右)旋转的具体实现步骤
左旋转的实现步骤:
1、创建一个新节点,将当前树的根节点的值给它2、让newNode的左子节点指向当前树的root的左子树3、让newNode的右子节点指向当前树的root的右子树的左子树4、让当前树的root的权值等于当前树的root的右子树的权值5、让当前树的root的右子树指向当前树的root的右子树的右子树6、让当前树的root的左子树指向newNode
代码如下:
/*** 实现avl树的左旋转(右子树高度-1大于左子树高度)* 思路:*/public void leftRotate(){//1、创建一个新节点,将当前树的根节点的值给它Node newNode = new Node(this.value);//2、让newNode的左子节点指向当前树的root的左子树newNode.left = this.left;//3、让newNode的右子节点指向当前树的root的右子树的左子树newNode.right = this.right.left;//4、让当前树的root的权值等于当前树的root的右子树的权值this.value = this.right.value;//5、让当前树的root的右子树指向当前树的root的右子树的右子树this.right = this.right.right;//6、让当前树的root的左子树指向newNodethis.left = newNode;}
右旋转的实现步骤:
1、创建一个新节点,将当前树的根节点的值给它2、让newNode的左子节点指向当前树的root的右子树3、让newNode的右子节点指向当前树的root的左子树的右子树4、让当前树的root的权值等于当前树的root的左子树的权值5、让当前树的root的左子树指向当前树的root的左子树的左子树6、让当前树的root的右子树指向newNode
代码如下:
/*** 实现avl树的右旋转(左子树高度-1大于右子树高度)* 思路:*/public void rightRotate(){//1、创建一个新节点,将当前树的根节点的值给它Node newNode = new Node(this.value);//2、让newNode的左子节点指向当前树的root的右子树newNode.right = this.right;//3、让newNode的右子节点指向当前树的root的左子树的右子树newNode.left = this.left.right;//4、让当前树的root的权值等于当前树的root的左子树的权值this.value = this.left.value;//5、让当前树的root的左子树指向当前树的root的左子树的左子树this.left = this.left.left;//6、让当前树的root的右子树指向newNodethis.right = newNode;}
介绍双旋转的应用场景:
有些数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列
int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL 树.
解决思路分析:
第一种情况(先左再右):
1. 当树符合右旋转的条件时
2. 如果它的左子树的右子树高度大于它的左子树的高度
3. 先对当前这个结点的左节点进行左旋转
4. 然后再对当前结点进行右旋转的操作即可
第二种情况(先右再左):
1. 当树符合左旋转的条件时
2. 如果它的右子树的左子树高度大于它的右子树的高度
3. 先对当前这个结点的右节点进行右旋转
4. 然后再对当前结点进行左旋转的操作即可
以上介绍的两种情况都是属于双旋转的情况。
因为AVL树也要满足二叉排序树的要求,所以在添加节点的时候也要满足BST的要求,但同时还要满足平衡二叉树本身的要求,所以在插入节点的时候就需要进行判断是否需要进行旋转来平衡该树。所以我们还需要能求出以当前节点为根节点的树的高度和它的左子树或者右子树的高度。
获取树的高度的方法:
//求出左子树的高度public int leftHeight() {if (this.left == null) {return 0;}return this.left.height();}//求出右子树的高度public int rightHeight() {if (this.right == null) {return 0;}return this.right.height();}//求树的高度public int height() {return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;}
以下是AVL树添加节点的代码:
//递归的形式添加结点,注意需要满足二叉排序树的要求public void add(Node node) {if (node == null) {return;}//判断传入的节点的权值,和当前节点的关系if (node.value == this.value) {if (this.left == null) {this.left = node;return;}if (this.right == null) {this.right = node;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);}}// 当添加完一个结点后,如果: ( 右子树的高度- 左子树的高度) > 1 , 左旋转if (this.rightHeight() - this.leftHeight() > 1){//如果当前节点的右子树的左子树的高度大于当前节点的右子树的右子树高度,则需要对当前节点的右子节点先进行右旋转if (this.right.leftHeight() > this.right.rightHeight()){//先对当前节点的右子节点进行右旋转this.right.rightRotate();//再对当前节点进行左旋转this.leftRotate();}else {//直接进行左旋转this.leftRotate();}}// 当添加完一个节点后,如果: (左子树高度 - 右子树高度) > 1 , 右旋转if (this.leftHeight() - this.rightHeight() > 1){//如果当前节点的左子树的右子树的高度大于当前节点的左子树的左子树高度,则需要对当前节点的左子节点先进行左旋转if (this.left.rightHeight() > this.left.leftHeight()){//对当前节点的左子树进行左旋转this.left.leftRotate();//再对当前节点进行右旋转this.rightRotate();}else {//直接进行右旋转this.rightRotate();}}}
以下给出完整的代码:
Node:
public class Node {public Integer value;public Node left;public Node right;public Node(Integer value) {this.value = value;}@Overridepublic String toString() {return "Node{" +"value=" + value +'}';}/*** 实现avl树的左旋转(右子树高度-1大于左子树高度)* 思路:*/public void leftRotate(){//1、创建一个新节点,将当前树的根节点的值给它Node newNode = new Node(this.value);//2、让newNode的左子节点指向当前树的root的左子树newNode.left = this.left;//3、让newNode的右子节点指向当前树的root的右子树的左子树newNode.right = this.right.left;//4、让当前树的root的权值等于当前树的root的右子树的权值this.value = this.right.value;//5、让当前树的root的右子树指向当前树的root的右子树的右子树this.right = this.right.right;//6、让当前树的root的左子树指向newNodethis.left = newNode;}/*** 实现avl树的右旋转(左子树高度-1大于右子树高度)* 思路:*/public void rightRotate(){//1、创建一个新节点,将当前树的根节点的值给它Node newNode = new Node(this.value);//2、让newNode的左子节点指向当前树的root的右子树newNode.right = this.right;//3、让newNode的右子节点指向当前树的root的左子树的右子树newNode.left = this.left.right;//4、让当前树的root的权值等于当前树的root的左子树的权值this.value = this.left.value;//5、让当前树的root的左子树指向当前树的root的左子树的左子树this.left = this.left.left;//6、让当前树的root的右子树指向newNodethis.right = newNode;}//求出左子树的高度public int leftHeight() {if (this.left == null) {return 0;}return this.left.height();}//求出右子树的高度public int rightHeight() {if (this.right == null) {return 0;}return this.right.height();}//求树的高度public int height() {return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;}//递归的形式添加结点,注意需要满足二叉排序树的要求public void add(Node node) {if (node == null) {return;}//判断传入的节点的权值,和当前节点的关系if (node.value == this.value) {if (this.left == null) {this.left = node;return;}if (this.right == null) {this.right = node;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);}}// 当添加完一个结点后,如果: ( 右子树的高度- 左子树的高度) > 1 , 左旋转if (this.rightHeight() - this.leftHeight() > 1){//如果当前节点的右子树的左子树的高度大于当前节点的右子树的右子树高度,则需要对当前节点的右子节点先进行右旋转if (this.right.leftHeight() > this.right.rightHeight()){//先对当前节点的右子节点进行右旋转this.right.rightRotate();//再对当前节点进行左旋转this.leftRotate();}else {//直接进行左旋转this.leftRotate();}}// 当添加完一个节点后,如果: (左子树高度 - 右子树高度) > 1 , 右旋转if (this.leftHeight() - this.rightHeight() > 1){//如果当前节点的左子树的右子树的高度大于当前节点的左子树的左子树高度,则需要对当前节点的左子节点先进行左旋转if (this.left.rightHeight() > this.left.leftHeight()){//对当前节点的左子树进行左旋转this.left.leftRotate();//再对当前节点进行右旋转this.rightRotate();}else {//直接进行右旋转this.rightRotate();}}}//根据value寻找节点public Node search(int value) {if (this.value == value) {return this;} else if (value < this.value) {if (this.left == null) {return null;}return this.left.search(value);} else {if (this.right == null) {return null;}return this.right.search(value);}}//查找要删除节点的父节点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;}}}//中序遍历public void infixOrder() {if (this.left != null) {this.left.infixOrder();}System.out.print(this.value + " ");if (this.right != null) {this.right.infixOrder();}}
}
AVLTree:
public class AVLTree {private Node root;public Node getRoot() {return root;}// 添加结点的方法public void add(Node node) {if (root == null) {root = node;// 如果 root 为空则直接让 root 指向 node} else {root.add(node);}}// 中序遍历public void infixOrder() {if (root != null) {root.infixOrder();} else {System.out.println("二叉排序树为空,不能遍历");}}/*** 实现avl树的左旋转(右子树高度-1大于左子树高度)*/public void leftRotate(){this.root.leftRotate();}/*** 实现avl树的右旋转(左子树高度-1大于右子树高度)*/public void rightRotate(){this.root.rightRotate();}// 查找要删除的结点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);}}}
AVLTreeDemo:
package com.atguigu.avl;public class AVLTreeDemo {public static void main(String[] args) {//需要左旋转avl树int[] arr = {4,3,6,5,7,8};//需要双旋转的avl树//int[] arrRight = { 10, 11, 7, 6, 8, 9 };//创建AVLTree对象AVLTree avlTree = new AVLTree();//添加结点for(int i=0; i < arr.length; i++) {//在添加节点的时候已经进行了自平衡处理avlTree.add(new Node(arr[i]));}System.out.println("中序遍历:"); // 3 4 5 6 7 8avlTree.infixOrder();System.out.println();System.out.println("树的高度=" + avlTree.getRoot().height()); //3System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 2System.out.println("当前的根结点=" + avlTree.getRoot());//6//再加入几个节点进行验证自平衡二叉树avlTree.add(new Node(10));avlTree.add(new Node(11));System.out.println("中序遍历:"); // 3 4 5 6 7 8 10 11avlTree.infixOrder();System.out.println();System.out.println("树的高度=" + avlTree.getRoot().height()); //4System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 3System.out.println("当前的根结点=" + avlTree.getRoot());//6}
}
实现平衡二叉树(AVL树)的旋转相关推荐
- AVL树平衡旋转详解
AVL树平衡旋转详解 概述 AVL树又叫做平衡二叉树.前言部分我也有说到,AVL树的前提是二叉排序树(BST或叫做二叉查找树).由于在生成BST树的过程中可能会出现线型树结构,比如插入的顺序是:1, ...
- Python数据结构11:树的实现,树的应用,前中后序遍历,二叉查找树BST,平衡二叉树AVL树,哈夫曼树和哈夫曼编码
1.概念 树一种基本的"非线性"数据结构. 相关术语: 节点Node:组成树的基本部分.每个节点具有名称,或"键值",节点还可以保存额外数据项,数据项根据不同的 ...
- Java数据结构——平衡二叉树(AVL树)
AVL树的引入 搜索二叉树有着极高的搜索效率,但是搜索二叉树会出现以下极端情况: 这样的二叉树搜索效率甚至比链表还低.在搜索二叉树基础上出现的平衡二叉树(AVL树)就解决了这样的问题.当平衡二叉树(A ...
- c++《AVL树的概念》《AVL树的插入》《AVL树的旋转》《AVL树的验证》《AVL树的删除》《AVL树的性能》
4.1 AVL树 4.1.1 AVL树的概念 二叉搜索树虽可以缩短查找的效率,**但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当 于在顺序表中搜索元素,效率低下.**因此,两位俄罗斯的 ...
- 平衡二叉树之AVL树的旋转
平衡二叉树是基于二叉排序树(或者也叫二叉搜索树)实现的一种自平衡的二叉树,实现方法有比如:AVL树.红黑树等等 二叉搜索树定义(维基百科): 二叉查找树(英语:Binary Search Tree), ...
- 平衡二叉树,AVL树之图解篇
学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...
- 一种基于平衡二叉树(AVL树)插入、查找和删除的简易图书管理系统
目录 1. 需求分析 2. 项目核心设计 2.1 结点插入 2.2 结点删除 3 测试结果 4 总结分析 4.1 调试过程中的问题是如何解决的,以及对设计与实现的回顾讨论和分析 4.2 算法的时间和空 ...
- 平衡二叉树---AVL树的实现
AVL树是最先发明的自平衡二叉查找算法,是平衡二叉树的一种.在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它又被成为高度平衡树.查找.插入和删除在平均和最坏情况下都是O(log n).增加和 ...
- 平衡二叉树(AVL树)和红黑树区别
1.二叉搜索树,平衡二叉树,红黑树的算法效率 操作 二叉查找树 平衡二叉树 红黑树 查找 O(n) O(logn) O(log2 n) 插入 O(n) O(logn) O(log2 n) 删除 O(n ...
最新文章
- Python中模块(Module)和包(Package)的区别
- Linux 列出文件列表命令ls
- OpenCV为轮廓创建边界旋转框和椭圆
- 法语语言考试C1,法语考试大比拼:专八与Dalf C1,哪个更难?
- SSIS的文件系统任务实例(zz)
- vscode编辑器tab换行缩进问题
- Spring Session实战
- 在linux中,我为什么不能安装VMware Tools?
- JAVA进阶教学之(单链表数据结构)
- 常用工具备忘(更新中)
- JavaScript学习(七十五)—图解浅拷贝和深拷贝
- 非常漂亮滴皮肤skin++ 终极破解之法
- visio流程图怎么合并线_visio图形怎么合并? visio合并图形的方法
- 微信小程序之扫普通链接二维码打开小程序实现动态传递参数及踩坑总结
- CAD突然没有对话框了?只能命令行输入内容??(FILEDIA=0?CMDECHO=0?)
- Excel中用REPT函数制作图表
- java零基础学习第九天
- mysql命令行导入csv_MySQL命令行导入CSV文件
- sqlmap 使用方法
- DMOZ重新接受登录申请(转)