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--需要 ...
随机推荐
- mac双系统用磁盘工具合并windows分区后,开机还会 出现win分区
如何删除开机硬盘的选择项 打开终端,输入sudo mount -t msdos /dev/disk0s1 /mnt 在Finer中会出现EFI盘,删除其中的Apple文件以外的文件即可(Apple千万 ...
- memory库函数的实现
下面主要对常用的几个memory库函数的实现(memcpy.memmove.memset.memcmp): memcpy函数与memmove函数: 相同点: 两者实现的功能均为从src拷贝count个 ...
- js解析器(重要!)
JavaScript有"预解析"的特性,理解预解析是很重要的,不然在实际开发中可能会遇到很多无法解析的问题,甚至导致程序bug的存在. #js预解析执行过程: 预解析:(全局作用域 ...
- Mahout源码分析:并行化FP-Growth算法
FP-Growth是一种常被用来进行关联分析,挖掘频繁项的算法.与Aprior算法相比,FP-Growth算法采用前缀树的形式来表征数据,减少了扫描事务数据库的次数,通过递归地生成条件FP-tree来 ...
- rapidPHP 1.1.0 介绍
RapidPHP介绍 RapidPHP本着免费开源.快速.高效.简单的面向对象的 轻量级PHP开发框架. 版本: 1.1.0 官网: rapidPHP.gx521.cc 作者: 954418992@q ...
- AspNetCore-MVC实战系列(二)之通过绑定邮箱找回密码
AspNetCore - MVC实战系列目录 . 爱留图网站诞生 . AspNetCore - MVC实战系列(一)之Sqlserver表映射实体模型 . AspNetCore-MVC实战系列(二)之 ...
- OpenMP 入门教程
前两天(其实是几个月以前了)看到了代码中有 #pragma omp parallel for 一段,感觉好像是 OpenMP,以前看到并行化的东西都是直接躲开,既然躲不开了,不妨研究一下: OpenM ...
- 如何运行一个vue工程
在师兄的推荐下入坑vue.js ,发现不知如何运行GitHub上的开源项目,很尴尬.通过查阅网上教程,成功搭建好项目环境,同时对前段工程化有了朦朦胧胧的认知,因此将环境搭建过程分享给大家. 首先, ...
- 简析ASP.NET WebApi的跨域签名
之前的文章写了关于WebApi的跨域问题,当中的方法只是解决了简单请求的跨域问题而非简单请求的跨域问题则没有解决. 要弄清楚 CORS规范将哪些类型的跨域资源请求划分为简单请求的范畴,需要额外了解几个 ...
- 小谈ThinkPHP
ThinkPHP也是一个MVC框架,分视图.控制器和模型,和Yii框架相比相对较好理解,并且是轻量级的框架(相对于Yii来说),在使用Yii框架时候如果将框架放在项目文件中,用编辑器打开文件都比较慢, ...