AVL树(平衡二叉查找树)
首先要说AVL树,我们就必须先说二叉查找树,先介绍二叉查找树的一些特性,然后我们再来说平衡树的一些特性,结合这些特性,然后来介绍AVL树。
一、二叉查找树
1、二叉树查找树的相关特征定义
二叉树查找树,又叫二叉搜索树,是一种有顺序有规律的树结构。它可以有以下几个特征来定义它:
(1)首先它是一个二叉树,具备二叉树的所有特性,他可以有左右子节点(左右孩子),可以进行插入,删除,遍历等操作;
(2)如果根节点有左子树,则左子树上的所有节点的值均小于根节点上的值,如果根节点有右子树,则有字数上的所有节点的值均大于根节点的值;
(3)它的中序遍历是一个有序数组。
下图即是一个比较典型的二叉查找树:

2、二叉查找树的插入操作
首先,我们记住一点,二叉查找树的插入操作,插入后的节点一定为叶子节点,你可以这样想,一个刚插入的节点,它肯定没有孩子节点,这样它只能是叶子结点。这样,二叉查找树的插入操作就显得很简单,我们只要将插入的值依次和相应的根节点比较:
(1)如果根节点为空时,则创建一个节点,这个节点的值为插入的值,将该节点赋值给根节点;
(2)如果比根节点值小,则进入左子树,获得左子树的根节点;(即这个节点一定是插入左子树的某一位置)
(3)如果比根节点值大,则进入右子树,获得右子树的根节点;(即这个节点一定是插入左子树的某一位置)
例如:利用上图插入值为1的节点,我们的操作如下:
(1)首先插入的值1与根节点值5比较,比5小,则进入左子树,获得左子树的根节点
(2)将插入的值1与当前根节点值3比较,比3小,则进入左子树,获得左子树的根节点
(3)将插入的值1与当前根节点值2比较,比2小,则进入左子树,获得左子树的根节点
(4)判断发下当前根节点为null,创建值为1的节点,并将该节点赋值给当前根节点。
则插入后的二叉查找树为:

3、二叉查找树的删除操作
二叉查找树相对于插入会相对麻烦一点,但是我们大致可以将删除的情况分为以下四种情况:
(1)删除的是叶子节点,即节点没有孩子节点,这个最为简单直接将其删除就可以;
例如:我们要删除值为1的叶子节点:


(2)删除的节点只有有左孩子节点,则将左孩子节点直接代替根节点;
例如:删除节点值为2的节点


(3)删除的节点只有有右孩子节点,则将右孩子节点直接代替根节点;(跟上述(2)基本相同,这里不进行举例)
(4)删除的节点既有左孩子也有右孩子,这种情况相对复杂,我们尽量不去改动此节点上层的节点,我们去左右子树找一个与此节点的值比较相近的节点来代替这个节点即可,然后删除代替节点原本节点(而很显然这个节点一定为叶子节点)。我们采取的方法两种是:
1)在左子树中找到最大的值节点,将改值复制给根节点,然后删除左子树找到的节点;
2)在右子树中找到最小的值节点,将改值复制给根节点,然后删除右子树找到的节点;
例如:我们想要删除值为5的节点(我们采取的是1)的方式)


4、二叉查找树的中序遍历
二叉查找树的中序遍历是非常有意义的,所以这里我只介绍中序遍历,二叉查找树的所有遍历和正常的二叉树遍历没有区别。
中序遍历的执行顺序为:左 根 右,根据二叉查找树的性质,我们可以清楚知道这是一个有序数组。
例如遍历上述的树:

中序遍历的结果为:1-2-3-4-5-6-7-8
二、平衡树
1、平衡树的相关定义
平衡树相对一般树多引入了一个高度的概念,即每个节点记录了以此节点为根节点的子树的高度。然后每个节点的所有子树的高度差必须小于等于平衡值(一般为1)。
节点的高度规定:
(1)若节点为null时,节点的高度为-1;
(2)若节点不为null时,节点的高度为子树的高度的最大值+1;
以二叉平横树为例:


2、关于平横树的平衡操作
对于平衡树来说,往往是在插入和删除操作之后,导致原本的平横树失衡,这里我们不进行插入和删除操作的讲解了,而且这里我也不准备对平衡操作进行讲解,因为平衡的话我们必须要有平衡的限制条件,不然我们对于一个情况来说,我们平衡操作不唯一,价值性其实不高。我准备在平衡二叉查找树中讲解平衡操作,这样有二叉查找树的相关要求限制平衡操作,而且这种平衡会显得很意义。
三、AVL(平衡二叉查找树)
写了这么久,终于引出了AVL树了,不容易啊。
1、AVL(平衡二叉查找树)的相关特性和定义
我们可以通过二叉查找树和平衡树来定义AVL树:
(1)首先它有二叉查找树的所有特性,如果根节点有左子树,则左子树上的所有节点的值均小于根节点的值,如果根节点有右子树,则有字数上的所有节点的值均大于根节点的值。
(2)每个节点都会有一个高度值,左子树的高度值和右子树的高度值差值应该小于规定的平衡值(一般为1)。
总结在一起为:AVL树是一个带有平衡条件约束的二叉查找树。
2、AVL树的平衡操作(旋转)
首先,作为一个树形结构,它一定有插入和删除操作,之前不是说了平衡树的平衡操作是在插入删除操作之后进行的么,为什么不先将AVL树的插入和删除?AVL树的插入和删除操作和二叉查找树几乎一模一样,唯一区别就是插入操作删除操作之后会有一个平衡操作。所以在这里我们只需要讲清楚如何进行平衡操作结合前面讲解的二叉查找树的插入删除操作就可以明白AVL树的插入删除操作了。另外AVL树的遍历操作和二叉查找树一模一样。
AVL树的平衡旋转操作总共有四种情况:每次都是k1处失衡
补充:
(1)与左孩子节点单旋转(以根为基准)



(2)与右孩子节点单旋转(以根为基准)



在这里我们对单旋转进行一下总结,虽然图中给的是三个明确的节点,但事实上只有两个节点(红色圈起来的节点)进行相应的旋转,发生了相应的状态的改变,另一个节点k3是没有状态改变的。
(3)双旋转,先根的左孩子和其右孩子旋转,然后根最后与左孩子节点单旋转





(4)双旋转,先根的右孩子和其左孩子旋转,然后根最后与右孩子节点单旋转





总结:之前说单旋转只涉及到两个节点的状态发生变化,而双旋转则是三个节点状态都发生改变。其实双旋转的过程也是两个单旋转的过程,而且这两个单旋转也就是上面所说的两个单旋转的情况,比如说:情况(4),是k2和k3节点先进行了(2)旋转,转化为类似(1)情况,然后在k1和k3进行(1)旋转。
3、AVL树的相关操作的代码实现(Java)
package Tree;
/*
* avl树是一种平衡二叉查找树,了解了avl树的相关操作,将会有利于对排序和树的知识的理解
*/
/*
* 创建avl树节点,这个树的节点有:
* 1、左右子树的引用
* 2、树节点存储的值
* 3、该节点树高度差
*/
class AVLTreeNode{
public AVLTreeNode avlLeft;//左孩子
public AVLTreeNode avlRight;//右孩子
public int data;//节点存储的值
public int height;//节点树高度值
public AVLTreeNode(int data,AVLTreeNode avlLeft,AVLTreeNode avlRight){
this.avlLeft=avlLeft;
this.data=data;
this.avlRight=avlRight;
}
} public class AVLTree {
private static final int ALLOWED_IMBALANCE=1;//规定了AVL树允许左右子树高度差值的最大值
//用于计算当前节点的树高度差
public int height(AVLTreeNode t){
int mark=t==null? -1:t.height;
return mark;
} /*
* 插入操作(传入参数)
* m 插入的新的节点值
* t AVL树的根结点
*/
public AVLTreeNode insert(int m,AVLTreeNode t){
if(t==null){
return t=new AVLTreeNode(m, null, null);
}
if(t.data>m){
t.avlLeft=insert(m,t.avlLeft);
}else if(t.data<m){
t.avlRight=insert(m, t.avlRight);
}else{
;//防止出现添加相同数字的现象
}
return balance(t);
} /*
* AVL树的删除操作
* m 要删除节点的值
* t AVL树的根节点
*/
public AVLTreeNode delete(int m,AVLTreeNode t){
if(t==null){
return t;
}
if(t.data>m){
t.avlLeft=delete(m, t.avlLeft);
}else if(t.data<m){
t.avlRight=delete(m,t.avlRight);
}else if(t.avlLeft!=null&&t.avlRight!=null){//如果左右子树非空,该如何进行删除操作
//这个时候找到左子树最大的元素或右子树最小的元素来填充这个位置,在这里我选择右子树最小的
t.data=findMin(t.avlRight).data;
//同时我们获得右子树删除右子树上多余的值(原先的最小值)
t.avlRight=delete(t.data,t.avlRight);
}else{//当右子树为空或左子树为空,该如何进行删除操作,简单,左子树为空,删除操作直接将右子树的节点替代根就完事
t=(t.avlLeft!=null)?t.avlLeft:t.avlRight;
}
return balance(t);
} /*
* 用来查找二叉查找树种最小的元素
*/
public AVLTreeNode findMin(AVLTreeNode t){
//根据二叉查找树的特点,树的最小节点一定是在最左边的子树上,我们只需要不停的寻找它的左子树即可
while(t.avlLeft!=null){
t=t.avlLeft;
}
return t;
} /*
* 用于平衡二叉查找树的(AVL树核心方法)
*/
public AVLTreeNode balance(AVLTreeNode t){
if(t==null)
return t;
if(height(t.avlLeft)-height(t.avlRight)>ALLOWED_IMBALANCE){
if(height(t.avlLeft.avlLeft)>height(t.avlLeft.avlRight)){//第一种情况
t=rotateWithLeftChild(t);
}else{//第三种情况
t=doubleWithLeftChild(t);
}
}else if(height(t.avlRight)-height(t.avlLeft)>ALLOWED_IMBALANCE){
if(height(t.avlRight.avlLeft)<height(t.avlRight.avlRight)){//第二种情况
t=rotateWithRightChild(t);
}else{//第四种情况
t=doubleWithRightChild(t);
}
}else{
;//第三种就是已经平衡,不进行任何操作
}
t.height=Math.max(height(t.avlLeft), height(t.avlRight))+1;
return t;
} /*
* 平衡操作需要的旋转操作
*/
//与左孩子节点单旋转(以根为基准)
public AVLTreeNode rotateWithLeftChild(AVLTreeNode k1){
AVLTreeNode k2=k1.avlLeft;
k1.avlLeft=k2.avlRight;
k2.avlRight=k1;
k1.height=Math.max(height(k1.avlLeft), height(k1.avlRight))+1;//计算以这个节点为根的树的高度
k2.height=Math.max(height(k2.avlLeft), k1.height)+1;
return k2;
} //与右孩子节点单旋转(以根为基准)
public AVLTreeNode rotateWithRightChild(AVLTreeNode k1){
AVLTreeNode k2=k1.avlRight;
k1.avlRight=k2.avlLeft;
k2.avlLeft=k1;
k1.height=Math.max(height(k1.avlLeft),height(k1.avlRight))+1;
k2.height=Math.max(height(k2.avlRight), k1.height)+1;
return k2;
} //双旋转,先根的左孩子和其右孩子旋转,然后根最后与左孩子节点单旋转
public AVLTreeNode doubleWithLeftChild(AVLTreeNode k1){
//首先的思想是将双旋转转换为之前习惯的单旋转的情况,
k1.avlLeft=rotateWithRightChild(k1.avlLeft);
//然后通过调用一次与左孩子节点单旋转
AVLTreeNode k2=rotateWithLeftChild(k1);
return k2;
} //双旋转,先根的左孩子和其右孩子旋转,然后根最后与左孩子节点单旋转
public AVLTreeNode doubleWithRightChild(AVLTreeNode k1){
//首先的思想是将双旋转转换为之前习惯的单旋转的情况,
k1.avlRight=rotateWithLeftChild(k1.avlRight);
//然后通过调用一次与左孩子节点单旋转
AVLTreeNode k2=rotateWithRightChild(k1);
return k2;
} /*
* 中序遍历的方法(递归)
*/
public void midTravleTree(AVLTreeNode h){
if(h!=null){
midTravleTree(h.avlLeft);
System.out.print(h.data+" ");
midTravleTree(h.avlRight);
}
} /*
* 测试
*/ //public void static
public static void main(String[] args) {
AVLTree avlTree=new AVLTree();
AVLTreeNode t=null;
t=avlTree.insert(1, t);
System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
t=avlTree.insert(2, t);
System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
t=avlTree.insert(3, t);
System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
t=avlTree.insert(4, t);
System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
t=avlTree.insert(5, t);
System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
t=avlTree.insert(6, t);
System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
t=avlTree.insert(7, t);
System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
t=avlTree.insert(8, t);
System.out.println("t.height:"+t.height);//每次插完后后进行查一次树的高度
//中序遍历一下avl树
avlTree.midTravleTree(t);
System.out.println();//换行
t=avlTree.delete(4, t);
System.out.println("t.height:"+t.height);//每次删除后后进行查一次树的高度
t=avlTree.delete(6, t);
System.out.println("t.height:"+t.height);//每次删除后后进行查一次树的高度
//中序遍历一下avl树
avlTree.midTravleTree(t);
}
}
运行结果:
t.height:0
t.height:1
t.height:1
t.height:2
t.height:2
t.height:2
t.height:2
t.height:3
1 2 3 4 5 6 7 8
t.height:2
t.height:2
1 2 3 5 7 8
四、说一些自己感想
AVL树的出现,一方面为排序提供了方便,另一方面也提高了树结构的查询效率,查询的时间复杂度为O(logn)。
AVL树(平衡二叉查找树)的更多相关文章
- AVL树平衡旋转详解
AVL树平衡旋转详解 概述 AVL树又叫做平衡二叉树.前言部分我也有说到,AVL树的前提是二叉排序树(BST或叫做二叉查找树).由于在生成BST树的过程中可能会出现线型树结构,比如插入的顺序是:1, ...
- AVL树(查找、插入、删除)——C语言
AVL树 平衡二叉查找树(Self-balancing binary search tree)又被称为AVL树(AVL树是根据它的发明者G. M. Adelson-Velskii和E. M. Land ...
- AVL树
AVL树 在二叉查找树(BST)中,频繁的插入操作可能会让树的性能发生退化,因此,需要加入一些平衡操作,使树的高度达到理想的O(logn),这就是AVL树出现的背景.注意,AVL树的起名来源于两个发明 ...
- 二叉树与AVL树
二叉树 什么是二叉树? 父节点至多只有两个子树的树形结构成为二叉树.如下图所示,图1不是二叉树,图2是一棵二叉树. 图1 普通的树 ...
- 恐怖的AVL树
学习参考:http://www.cnblogs.com/Camilo/p/3917041.html 今天闲来无事打算学习AVL树,并以AVL树的插入作为切入点. 不知不觉,我就在电脑前编了4个小时…… ...
- 5分钟了解二叉树之AVL树
转载请注明出处:https://www.cnblogs.com/morningli/p/16033733.html AVL树是带有平衡条件的二叉查找树,其每个节点的左子树和右子树的高度最多相差1.为了 ...
- 纸上谈兵:AVL树
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 二叉搜索树的深度与搜索效率 我们在树, 二叉树, 二叉搜索树中提到,一个有n个节点 ...
- 纸上谈兵: AVL树[转]
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 二叉搜索树的深度与搜索效率 我们在树, 二叉树, 二叉搜索树中提到,一个有n个节点 ...
- Linux内核之于红黑树and AVL树
为什么Linux早先使用AVL树而后来倾向于红黑树? 实际上这是由红黑树的有用主义特质导致的结果,本短文依旧是形而上的观点.红黑树能够直接由2-3树导出.我们能够不再提红黑树,而仅仅提2- ...
- 自已动手作图搞清楚AVL树
@ 目录 一.背景 二.平衡二分搜索树---AVL树 2.1 AVL树的基本概念 结点 高度 平衡因子 2.2 AVL树的验证 三.旋转操作 3.1 L L--需要通过右旋操作 3.2 R R--需要 ...
随机推荐
- Spring Boot 整合 Redis 实现缓存操作
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 产品没有价值,开发团队再优秀也无济于事 – <启示录> 』 本文提纲 ...
- MySQL最常用数值函数
数值函数: 用来处理很多数值方面的运算,使用数值函数,可以免去很多繁杂的判断求值的过程,能够大大提高用户的工作效率. 1.ABS(x):返回 x 的绝对值 mysql> select abs(- ...
- IIC模块TestBench的书写方法
今天在看黑金AX309FPGA开发板自带教程中的EEPROM那一章,考虑如何写其中iic_com模块的TestBench,难点在于1. 该模块存在一个inout型的端口信号:2. 时序较为复杂,不可能 ...
- Hibernate考试试题(部分题库)含答案
Hibernate考试试题 (题库) 1. 在Hibernate中,下列说法正确的有( ABC ).[选三项] A.Hibernate是一个开放源代码的对象关系映射框架 B.Hibernate对JD ...
- 简单几步让网站支持https,windows iis配置方式
1.https证书的分类 SSL证书没有所谓的"品质"和"等级"之分,只有三种不同的类型.SSL证书需要向国际公认的证书证书认证机构(简称CA,Certific ...
- sass入门学习篇(一)
先简单的介绍一下sass,如果你了解less,sass就没什么太大问题 Sass 是对 CSS 的扩展,让 CSS 语言更强大.优雅. 它允许你使用变量.嵌套规则. mixins.导入等众多功能, 并 ...
- 杜教筛 && bzoj3944 Sum
Description Input 一共T+1行 第1行为数据组数T(T<=10) 第2~T+1行每行一个非负整数N,代表一组询问 Output 一共T行,每行两个用空格分隔的数ans1,ans ...
- #include<> 和#include“”的区别
1.< >引用的是编译器的类库路径里面的头文件2." "引用的是程序目录的相对路径中的头文件,在程序目录的相对路径中找不到该头文件时会继续在类库路径里搜寻该头文件 ...
- selenium IDE的3种下载安装方式
第一种方式: 打开firefox浏览器-----点击右上角-----附加组件----插件----搜索框输入“selenium”-----搜索的结果中下拉到页面尾部,点击“查看全部的37项结果”---进 ...
- PHP的学习记录
这是我的第一次写博客,是一个PHP的初学者,刚刚开始之前是一点儿的都不懂,现在开始通过买些书籍回来学习,废话少说,开始记录笔记吧. 函数:函数的英文名为function,也就是功能的意思,在自定义函数 ...