Java数据结构——AVL树
AVL树(平衡二叉树)定义
AVL树本质上是一颗二叉查找树,但是它又具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,并且拥有自平衡机制。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为平衡二叉树。下面是平衡二叉树和非平衡二叉树对比的例图:
平衡因子(bf):结点的左子树的深度减去右子树的深度,那么显然-1<=bf<=1;
AVL树的作用
我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。
例如:我们按顺序将一组数据1,2,3,4,5,6分别插入到一颗空二叉查找树和AVL树中,插入的结果如下图:
由上图可知,同样的结点,由于插入方式不同导致树的高度也有所不同。特别是在带插入结点个数很多且正序的情况下,会导致二叉树的高度是O(N),而AVL树就不会出现这种情况,树的高度始终是O(lgN).高度越小,对树的一些基本操作的时间复杂度就会越小。这也就是我们引入AVL树的原因。
AVL树的基本操作
AVL树不仅是一颗二叉查找树,它还有其他的性质。如果我们按照一般的二叉查找树的插入方式可能会破坏AVL树的平衡性。同理,在删除的时候也有可能会破坏树的平衡性,所以我们要做一些特殊的处理,包括:单旋转和双旋转!
单旋转---右旋:
由上图可知:在插入之前树是一颗AVL树,而插入之后结点T的左右子树高度差的绝对值不再 < 1,此时AVL树的平衡性被破坏,我们要对其进行旋转。由上图可知我们是在结点T的左结点的左子树上做了插入元素的操作,我们称这种情况为左左情况,我们应该进行右旋转(只需旋转一次,故是单旋转)。具体旋转步骤是:
T向右旋转成为L的右结点,同时,Y放到T的左孩子上。这样即可得到一颗新的AVL树,旋转过程图如下:
单旋转---左旋:
由上图可知:在插入之前树是一颗AVL树,而插入之后结点T的左右子树高度差的绝对值不再 < 1,此时AVL树的平衡性被破坏,我们要对其进行旋转。由上图可知我们是在结点T的右结点的右子树上做了插入元素的操作,我们称这种情况为右右情况,我们应该进行左旋转(只需旋转一次,故事单旋转)。具体旋转步骤是:
T向右旋转成为R的左结点,同时,Y放到T的左孩子上。这样即可得到一颗新的AVL树,旋转过程图如下:
以上就是插入操作时的单旋转情况!我们要注意的是:谁是T谁是L,谁是R还有谁是X,Y,Z!T始终是开始不平衡的左右子树的根节点。显然L是T的左结点,R是T的右节点。X、Y、Y是子树当然也可以为NULL.NULL归NULL,但不能破坏插入时我上面所说的左左情况或者右右情况。
双旋转的---左右(先左后右)旋:
由上图可知,我们在T结点的左结点的右子树上插入一个元素时,会使得根为T的树的左右子树高度差的绝对值不再 < 1,如果只是进行简单的右旋,得到的树仍然是不平衡的。我们应该按照如下图所示进行二次旋转:
双旋转的---右左(先右后左)旋:
由上图可知,我们在T结点的右结点的左子树上插入一个元素时,会使得根为T的树的左右子树高度差的绝对值不再 < 1,如果只是进行简单的左旋,得到的树仍然是不平衡的。我们应该按照如下图所示进行二次旋转:
简单实现
TreeNode.java
public class TreeNode {
private int data;
private TreeNode leftChild;
private TreeNode rightChild;
private int height; public int getData() {
return data;
} public void setData(int data) {
this.data = data;
} public TreeNode getLeftChild() {
return leftChild;
} public void setLeftChild(TreeNode leftChild) {
this.leftChild = leftChild;
} public TreeNode getRightChild() {
return rightChild;
} public void setRightChild(TreeNode rightChild) {
this.rightChild = rightChild;
} public TreeNode(int data) {
super();
this.data = data;
//初始化话高度为1
height = 1;
}
}
AVLTree.java:
import java.util.ArrayList; public class AVLTree {
private static TreeNode root;
private static boolean flag=true;
// 获得高度
private int getHeight(TreeNode node) {
if (node == null) {
return 0;
} else {
return node.height;
}
}
// 获得节点的平衡因子
private int getBalanceFactor(TreeNode node) {
if (node == null) {
return 0;
} else {
return getHeight(node.leftChild) - getHeight(node.rightChild);
}
}
// 判断该二叉树是否是一颗二叉搜索树
public boolean isBST() {
ArrayList<Integer> datas = new ArrayList<>();
inOrder(root, datas);
for (int i = 1; i < datas.size(); i++)
if (datas.get(i - 1) > datas.get(i))
return false;
return true;
}
// 中序遍历添加进集合
public void inOrder(TreeNode node, ArrayList<Integer> datas) {
if (node != null) {
inOrder(node.getLeftChild(), datas);
datas.add(node.data);
inOrder(node.getRightChild(), datas);
} else {
return;
}
} public void inOrder(TreeNode node) {
if (node != null) {
inOrder(node.getLeftChild());
System.out.print(node.data + " ");
inOrder(node.getRightChild());
}
}
// 判断该二叉树是否是一颗平衡二叉树
public boolean isBalanced() {
return isBalanced(root);
} private boolean isBalanced(TreeNode node) {
if (node == null) {
return true;
} else {
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1) {
return false;
}
return isBalanced(node.leftChild) && isBalanced(node.rightChild);
}
} // 对节点进行向右旋转操作,返回旋转后的根节点x
// y x
// / \ / \
// x T4 向右旋转(y) z y
// / \ ----------> / \ / \
// z T3 T1 T2 T3 T4
// / \
//T1 T2
private TreeNode rightRotate(TreeNode y) {
TreeNode x = y.leftChild;
TreeNode T3 = x.rightChild;
// 向右旋转过程
x.rightChild = y;
y.leftChild = T3;
// 更新height
y.height = Math.max(getHeight(y.leftChild), getHeight(y.rightChild)) + 1;
x.height = Math.max(getHeight(x.leftChild), getHeight(x.rightChild)) + 1;
//依次添加进avl树时可能会有60,50,40的情况,此时如果不改变root的值,root指向60,旋转后不再是根节点,即当传进来的节点指向root时,就需要改变root节点
if (y == root) {
root = x;
}
return x;
}
// 对节点进行向左旋转操作,返回旋转后的根节点x
// y x
// / \ / \
// T1 x 向左旋转(y) y z
// / \ -------> / \ / \
// T2 z T1 T2 T3 T4
// / \
// T3 T4
private TreeNode leftRotate(TreeNode y) {
TreeNode x = y.rightChild;
TreeNode T2 = x.leftChild;
// 向左旋转过程
x.leftChild = y;
y.rightChild = T2;
// 更新height
y.height = Math.max(getHeight(y.leftChild), getHeight(y.rightChild)) + 1;
x.height = Math.max(getHeight(x.leftChild), getHeight(x.rightChild)) + 1;
//依次添加进avl树时可能会有40,50,60的情况,此时如果不改变root的值,root指向40,旋转后不再是根节点,需要改变,即当传进来的节点指向root时,就需要改变root节点
if (y== root) {
root = x;
}
return x;
}
// 添加节点
public TreeNode addNode(TreeNode node, int data) {
if (root == null) {
TreeNode treeNode = new TreeNode(data);
root = treeNode;
return root;
}
if (node == null) {
TreeNode treeNode = new TreeNode(data);
return treeNode;
} else {
if (data < node.data) {
node.leftChild = addNode(node.leftChild, data);
} else if (data > node.data) {
node.rightChild = addNode(node.rightChild, data);
} else {
node.data = data;
}
// 更新height
node.height = 1 + Math.max(getHeight(node.leftChild), getHeight(node.rightChild));
// 计算平衡因子
int balanceFactor = getBalanceFactor(node);
// LL
if (balanceFactor > 1 && getBalanceFactor(node.leftChild) >= 0) {
return rightRotate(node);
}
// RR
if (balanceFactor < -1 && getBalanceFactor(node.rightChild) <= 0) {
return leftRotate(node);
}
// LR
if (balanceFactor > 1 && getBalanceFactor(node.leftChild) < 0) {
node.leftChild = leftRotate(node.leftChild);
return rightRotate(node);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(node.rightChild) > 0) {
node.rightChild = rightRotate(node.rightChild);
return leftRotate(node);
}
return node;
}
}
// 删除节点
public TreeNode deleteNode(TreeNode node, int data) {
if (node == null) {
System.out.println("find not");
flag=false;
return null;
}
TreeNode retNode;
if (data < node.data) {
node.leftChild = deleteNode(node.leftChild, data);
retNode = node;
} else if (data > node.data) {
node.rightChild = deleteNode(node.rightChild, data);
retNode = node;
} else {
// 左子树为空的时候
if (node.leftChild == null) {
TreeNode rightNode = node.rightChild;
node.rightChild = null;
retNode = rightNode;
}
// 右子树为空的时候
else if (node.rightChild == null) {
TreeNode leftNode = node.leftChild;
node.leftChild = null;
retNode = leftNode;
} else {
// 左右子树都不为空的时候
// 找到待删除节点的后继节点
TreeNode successor = processer(node.rightChild);
//如果删除的恰好是根节点
if (node == root) {
root = successor;
}
successor.rightChild = deleteNode(node.rightChild, successor.data);
successor.leftChild = node.leftChild;
node.leftChild = node.rightChild = null;
retNode = successor;
}
}
if (retNode == null) {
return null;
} else {
// 更新height
retNode.height = 1 + Math.max(getHeight(retNode.leftChild), getHeight(retNode.rightChild));
// 计算平衡因子
int balanceFactor = getBalanceFactor(retNode);
// LL
if (balanceFactor > 1 && getBalanceFactor(retNode.leftChild) >= 0) {
return rightRotate(retNode);
}
// RR
if (balanceFactor < -1 && getBalanceFactor(retNode.rightChild) <= 0) {
return leftRotate(retNode);
}
// LR
if (balanceFactor > 1 && getBalanceFactor(retNode.leftChild) < 0) {
retNode.leftChild = leftRotate(retNode.leftChild);
return rightRotate(retNode);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(retNode.rightChild) > 0) {
retNode.rightChild = rightRotate(retNode.rightChild);
return leftRotate(retNode);
}
return retNode;
}
}
// 寻找后继节点
private TreeNode processer(TreeNode node) {
if (node.leftChild == null) {
return node;
} else {
return processer(node.leftChild);
}
}
// 修改节点
public boolean updateNode(int oldData, int newData) {
TreeNode del = deleteNode(root, oldData);
if(flag==false) {
return false;
}else {
addNode(root, newData);
return true;
}
}
// 查找节点
public TreeNode findNode(int data) {
TreeNode current = root;
while (current.data != data) {
if (data < current.data) {
current = current.leftChild;
} else {
current = current.rightChild;
}
if (current == null) {
return null;
}
}
return current;
} public static void main(String[] args) {
AVLTree tree = new AVLTree();
int[] arr = new int[] { 60, 50, 40, 30, 20, 10 };
//依次添加进avl树
for (int i : arr) {
tree.addNode(root, i);
}
//中序遍历
tree.inOrder(root);
System.out.println();
//是否是BST
System.out.println("is BST:" + tree.isBST());
//是否平衡
System.out.println("is Balanced:" + tree.isBalanced());
//添加节点45
tree.addNode(root, 45);
//是否还是BST
System.out.println("is BST:" + tree.isBST());
//是否还是平衡的
System.out.println("is Balanced:" + tree.isBalanced());
//查找节点50
System.out.println(tree.findNode(50));
//删除节点后
tree.deleteNode(root, 40);
//是否还是BST
System.out.println("is BST:" + tree.isBST());
//是否还是平衡的
System.out.println("is Balanced:" + tree.isBalanced());
tree.updateNode(45, 51);
//是否还是BST
System.out.println("is BST:" + tree.isBST());
//是否还是平衡的
System.out.println("is Balanced:" + tree.isBalanced());
}
}
Java数据结构——AVL树的更多相关文章
- JAVA数据结构--AVL树的实现
AVL树的定义 在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下的时间复杂度都是.增 ...
- Java数据结构之树和二叉树(2)
从这里始将要继续进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来 ...
- Java数据结构之树和二叉树
从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let's go~~ Java中的数据结构模型可以分为一下几部分: 1.线性结构 2.树形结构 3.图形或者网状结构 接下来的 ...
- 第三十二篇 玩转数据结构——AVL树(AVL Tree)
1.. 平衡二叉树 平衡二叉树要求,对于任意一个节点,左子树和右子树的高度差不能超过1. 平衡二叉树的高度和节点数量之间的关系也是O(logn) 为二叉树标注节点高度并计算平衡因子 AVL ...
- 数据结构-AVL树的旋转
http://blog.csdn.net/GabrieL1026/article/details/6311339 平衡二叉树在进行插入操作的时候可能出现不平衡的情况,AVL树即是一种自平衡的二叉树,它 ...
- 简单数据结构———AVL树
C - 万恶的二叉树 Crawling in process... Crawling failed Time Limit:1000MS Memory Limit:32768KB 64b ...
- java数据结构之树
树定义和基本术语定义树(Tree)是n(n≥0)个结点的有限集T,并且当n>0时满足下列条件: (1)有且仅有一个特定的称为根(Root)的结点: (2)当n>1时,其余结 ...
- 数据结构--Avl树的创建,插入的递归版本和非递归版本,删除等操作
AVL树本质上还是一棵二叉搜索树,它的特点是: 1.本身首先是一棵二叉搜索树. 2.带有平衡条件:每个结点的左右子树的高度之差的绝对值最多为1(空树的高度为-1). 也就是说,AVL树,本质上 ...
- 再回首数据结构—AVL树(一)
前面所讲的二叉搜索树有个比较严重致命的问题就是极端情况下当数据以排序好的顺序创建搜索树此时二叉搜索树将退化为链表结构因此性能也大幅度下降,因此为了解决此问题我们下面要介绍的与二叉搜索树非常类似的结构就 ...
随机推荐
- python基础全部知识点整理,超级全(20万字+)
目录 Python编程语言简介 https://www.cnblogs.com/hany-postq473111315/p/12256134.html Python环境搭建及中文编码 https:// ...
- pandas_重采样多索引标准差协方差
# 重采样 多索引 标准差 协方差 import pandas as pd import numpy as np import copy # 设置列对齐 pd.set_option("dis ...
- P3239 [HNOI2015]亚瑟王 期望 dp
LINK:亚瑟王 Saber!Excalibur! 比较难的期望dp. 可以发现如果暴力枚举所有的局面复杂度很高 . 转换的思路则是 期望的线性性. 求出每张牌的期望累加即可. 考虑每张牌的期望=这张 ...
- CF R630 div2 1332 E Height All the Same
LINK:Height All the Same 比赛的时候 被这道题给打自闭了 还有1个多小时的时候开始想 想了30min 无果 放弃治疗. 心态炸了 F不想看了 应该要把题目全看一遍的 下次不能这 ...
- 基于IDEA 代码提交Git
基于IDEA 代码提交Git 步骤 1 创建一个项目 2 点击 VCS --> Import init Version Control --> Create Git Repository ...
- ios_UITextField右侧小圆叉
inputTF.clearButtonMode = UITextFieldViewModeWhileEditing; 只有在输入了字符时才出现哦
- 001_centos7下比特币源码编译安装
今天我们介绍比特币的源码安装过程,是利用编译安装的 首先安装依赖 yum install -y boost-devel qt-devel protobuf-devel qrencode-devel l ...
- eureka和feign的使用
1 eureka和feign的简介(copy来的) eureka:Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务.Spring Cloud将它集成在其子项目spring- ...
- mongodb学习这篇你就成功入门了,springboot2.0整合mongodb
本文演示以window10系统. 一:环境搭建: 1:mongodb下载和安装: http://www.mongodb.org/官网下载压缩包或者exe傻瓜式安装都行. 2:mongodb配置和搭建服 ...
- DevOps让金融业数字化转型更敏捷 | 分享实录
以下为博云近期在活动中分享的关于<如何通过 DevOps 让数字化转型变得更加敏捷>的主题演讲实录. 01 金融科技进入VUCA时代 大家好,今天分享的题目是<如何通过 DevOps ...