首先要说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树(平衡二叉查找树)的更多相关文章

  1. AVL树平衡旋转详解

    AVL树平衡旋转详解 概述 AVL树又叫做平衡二叉树.前言部分我也有说到,AVL树的前提是二叉排序树(BST或叫做二叉查找树).由于在生成BST树的过程中可能会出现线型树结构,比如插入的顺序是:1, ...

  2. AVL树(查找、插入、删除)——C语言

    AVL树 平衡二叉查找树(Self-balancing binary search tree)又被称为AVL树(AVL树是根据它的发明者G. M. Adelson-Velskii和E. M. Land ...

  3. AVL树

    AVL树 在二叉查找树(BST)中,频繁的插入操作可能会让树的性能发生退化,因此,需要加入一些平衡操作,使树的高度达到理想的O(logn),这就是AVL树出现的背景.注意,AVL树的起名来源于两个发明 ...

  4. 二叉树与AVL树

    二叉树 什么是二叉树? 父节点至多只有两个子树的树形结构成为二叉树.如下图所示,图1不是二叉树,图2是一棵二叉树. 图1 普通的树                                    ...

  5. 恐怖的AVL树

    学习参考:http://www.cnblogs.com/Camilo/p/3917041.html 今天闲来无事打算学习AVL树,并以AVL树的插入作为切入点. 不知不觉,我就在电脑前编了4个小时…… ...

  6. 5分钟了解二叉树之AVL树

    转载请注明出处:https://www.cnblogs.com/morningli/p/16033733.html AVL树是带有平衡条件的二叉查找树,其每个节点的左子树和右子树的高度最多相差1.为了 ...

  7. 纸上谈兵:AVL树

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 二叉搜索树的深度与搜索效率 我们在树, 二叉树, 二叉搜索树中提到,一个有n个节点 ...

  8. 纸上谈兵: AVL树[转]

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 二叉搜索树的深度与搜索效率 我们在树, 二叉树, 二叉搜索树中提到,一个有n个节点 ...

  9. Linux内核之于红黑树and AVL树

    为什么Linux早先使用AVL树而后来倾向于红黑树?       实际上这是由红黑树的有用主义特质导致的结果,本短文依旧是形而上的观点.红黑树能够直接由2-3树导出.我们能够不再提红黑树,而仅仅提2- ...

  10. 自已动手作图搞清楚AVL树

    @ 目录 一.背景 二.平衡二分搜索树---AVL树 2.1 AVL树的基本概念 结点 高度 平衡因子 2.2 AVL树的验证 三.旋转操作 3.1 L L--需要通过右旋操作 3.2 R R--需要 ...

随机推荐

  1. HTTP协议介绍

    一.什么是HTTP协议呢? 维基百科 写道 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是互联网上应用最为广泛的一种网络协议.HTTP是一个客户端终端 ...

  2. java多线程基本概述(五)——线程通信

    线程之间的通信可以通过共享内存变量的方式进行相互通信,也可以使用api提供的wait(),notify()实现线程之间的通信.wait()方法是Object类的方法,改方法用来将当前的线程置入&quo ...

  3. Extjs6(二)——用extjs6.0写一个系统登录及注销

    本文基于ext-6.0.0 一.写login页 1.在view文件夹中创建login文件夹,在login中创建文件login.js和loginController.js(login.js放在class ...

  4. 捕获mssqlservice 修改表后的数据,统一存储到特定的表中,之后通过代码同步两个库的数据

    根据之前的一些想法,如果有A,B 两个数据库, 如果把A 用户通过界面产生的更新或者插入修改,操作的数据同步更新到B 库中,如果允许延时2分钟以内 想法一: 通过创建触发器 把变更的数据和对应的表名称 ...

  5. androidStudio通过svn进行版本控制

    andoridStudio配置使用svn(以windows为例) 1.先安装svn客户端程序,TortoiseSVN,注意安装过程中要勾选command line client tools(默认是不安 ...

  6. 【原创】bootstrap框架的学习 第七课 -[bootstrap表格]

    Bootstrap 表格 标签 描述 <table> 为表格添加基础样式. <thead> 表格标题行的容器元素(<tr>),用来标识表格列. <tbody& ...

  7. C#中 dynamic 关键字

       所有表达式都能隐式的转换成dynamic,因为所有的表达式最终都能生成从Object派生出的类型. ; int b = a; //隐式转换错误 int b2 = (int)a; ; int b3 ...

  8. web安全—浏览器的进制

    浏览器的进制 字符的ascii码值可以转化为进制形式.可以用来绕过XSS filter. HTML属性值中的进制使用 .十进制使用a 表示,&#作为前缀,;作为后缀,后缀也可以没有. 如果要使 ...

  9. 学习MVC之租房网站(八)- 前台注册和登录

    在上一篇<学习MVC之租房网站(七)-房源管理和配图上传>完成了在后台新增.编辑房源信息以及上传房源配图的功能.到此后台开发便告一段落了,开始实现前台的功能,也是从用户的登录.注册开始. ...

  10. Trie树详解

    1. 概述 Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树.Trie一词来自retrieve,发音为/tri ...