(百度百科)在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 "An algorithm for the organization of information" 中发表了它。

平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体,也是第一个引入平衡概念的二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。

基本操作

单旋转:左旋转、右旋转

双旋转:左旋转与右旋转结合

 

操作判断

1. 按照二叉搜索树的方式增加节点,新增节点称为一个叶节点。

2. 从新增节点开始,回溯到第一个失衡节点。(如果回溯到根节点,还没有失衡节点,就说明该树已经符合AVL性质)。

3. 找到断的边,并确定断弦的方向(左OR右)。

4. 以断边下端为根节点,确定两个子树中的哪一个深度大(左子树还是右子树)。

5. 如果第2和第3步中的方向一致(都为左或者都为右),需要单旋转以失衡节点为根节点的子树。否则,双旋转以失衡节点为根节点的子树。若是左左(LL),则需要一次右旋转;若是右右(RR),则需要一次左旋转;若是左右(LR),则需要先左旋转再右旋转;若是右左(RL),则需要先右旋转再左旋转。

单向右旋平衡处理案例分析(LL)

从上图可以看出,从叶子结点往上回溯,(5)是第一个失衡结点,失衡方向是左边,而(5)的左子树根结点(3)的左子树深度比右子树大,所以该情况属于LL,只需要以(5)为转轴进行一次右旋转。

单向左旋平衡处理案例分析(RR

从上图可以看出,从叶子结点往上回溯,(10)是第一个失衡结点,失衡方向是右边,而(10)的左子树根结点(13)的右子树深度比左子树大,所以该情况属于RR,只需要以(10)为转轴进行一次左旋转。

双向旋转(先左后右)平衡处理案例分析(LR

先以(5)为轴向左旋转

后以(8)为轴向右旋转

从上图可以看出,从叶子结点往上回溯,(8)是第一个失衡结点,失衡方向是左边,而(5)的左子树根结点(7)的右子树深度比左子树大,所以该情况属于LR,因此需要先以(5)为转轴进行一次左旋转,后以(8)为转轴进行一次右旋转。

双向旋转(先右后左)平衡处理(RL

先以(13)为轴向右旋转

后以(8)为轴向左旋转

从上图可以看出,从叶子结点往上回溯,(8)是第一个失衡结点,失衡方向是右边,而(13)的右子树根结点(10)的左子树深度比右子树大,所以该情况属于RL,因此需要先以(13)为转轴进行一次右旋转,后以(8)为转轴进行一次左旋转。

插入操作

向AVL树中插入元素可能会导致树失去平衡。但是,我们只需要调整插入点至根节点的路径上不满足平衡状态的各节点中深度最大的那个即可。假设该最深节点为X。导致X失去平衡可能有四种情况:

(1)插入点位于X的左子结点的左子树-左左。

(2)插入点位于X的左子结点的右子树-左右。

(3)插入点位于X的右子结点的左子树-右左。

(4)插入点位于X的右子结点的左子树-右右。

情况1和4是对称的,成为外侧插入,可以通过单旋转解决。而情况2和3也是对称的,成为内侧插入。通过双旋转解决。

删除操作

(1)判断当前根结点是否等于要删除的节点,否则进入步骤(2),是则进入(1.1)。

(1.1)如果是叶子结点直接删除,然后进入步骤(3),否则进入(1.2)。

(1.2)如果该结点只有左子树,则直接删除结点把左子树的根结点往上提,再步骤(3),否则进入步骤(1.3)。

(1.3)如果该结点只有右子树,则直接删除结点把右子树的根结点往上提,再步骤(3),否则进入步骤(1.4)。

(1.4)如果左右子树都不为空,则从左子树获取前驱(后驱同理),替换当前删除结点,再从前驱结点的原父结点往上遍历检查高度和平衡,也就是步骤(3)。

(2)判断当前根结点是否大于删除节点,是则进入当前根结点的右子树继续递归删除,否则进入当前根结点的左子树继续递归删除,最后重新进入步骤(1),直到遍历完整棵树。

(3)从移除结点的父结点开始往上遍历,检查高度是否变化和失衡,若高度发生变化,则重新赋值最新高度,若失衡,则按照以上的旋转方式检查进行旋转。

AVL树的实现练习(Java

 public class AVLTree {

     private TreeNode root=null;

     /**
* 获取树的高度
* @param subTree
* @return
*/
private int height(TreeNode subTree){
if(subTree == null){
return ;
}else{
return subTree.height;
}
} /**
* 中序遍历
* @param subTree
*/
public void inOrder(TreeNode subTree){
if(subTree!=null){
inOrder(subTree.leftChild);
visted(subTree);
inOrder(subTree.rightChild);
}
} public void visted(TreeNode subTree){
System.out.print(subTree.data+"("+subTree.height+")"+",");
} /**
* 右旋转(LL)
* @param subTree 转轴节点
*/
public void r_Rotate(TreeNode sn){ TreeNode pn = sn.parent;
TreeNode ln = sn.leftChild; sn.leftChild = ln.rightChild;
if(ln.rightChild !=null){
ln.rightChild.parent = sn;
}
ln.rightChild = sn; sn.parent = ln;
ln.parent = pn;
if(pn != null && pn.rightChild==sn){
pn.rightChild = ln;
}else if(pn != null && pn.leftChild==sn){
pn.leftChild = ln;
} if(pn == null){
this.root = ln;
} sn.height = Math.max(height(sn.leftChild), height(sn.rightChild))+;
ln.height = Math.max(height(ln.leftChild), height(ln.rightChild))+;
} /**
* 左旋转(RR)
* @param subTree 转轴节点
*/
public void l_Rotate(TreeNode sn){ TreeNode pn = sn.parent;
TreeNode rn = sn.rightChild; sn.rightChild = rn.leftChild;
if(rn.leftChild !=null){
rn.leftChild.parent = sn;
}
rn.leftChild = sn; sn.parent = rn;
rn.parent = pn;
if(pn != null && pn.rightChild==sn){
pn.rightChild = rn;
}else if(pn != null && pn.leftChild==sn){
pn.leftChild = rn;
} if(pn == null){
this.root = rn;
} sn.height = Math.max(height(sn.leftChild), height(sn.rightChild))+;
rn.height = Math.max(height(rn.leftChild), height(rn.rightChild))+;
} /**
* 插入
* @param subTree
* @param iv
*/
public void insertNote(TreeNode subTree, int iv){ if(subTree == null){
/*空树,根结点赋值*/
subTree = new TreeNode(iv);
this.root = subTree; }else if(subTree.data > iv){
/*插入左子树*/
if(subTree.leftChild == null){
TreeNode newNode = new TreeNode(iv);
subTree.leftChild = newNode;
newNode.parent = subTree;
}else{
insertNote(subTree.leftChild, iv);
/*判断是否需要旋转*/
if(compareHeight(subTree.leftChild,subTree.rightChild) == ){
if(compareHeight(subTree.leftChild.leftChild,subTree.leftChild.rightChild)>=){
r_Rotate(subTree);
}else{
l_Rotate(subTree.leftChild);
r_Rotate(subTree);
}
}
} }else if(subTree.data < iv){
/*插入右子树*/
if(subTree.rightChild == null){
TreeNode newNode = new TreeNode(iv);
subTree.rightChild = newNode;
newNode.parent = subTree;
}else{
insertNote(subTree.rightChild, iv);
/*判断是否需要旋转*/
if(compareHeight(subTree.rightChild, subTree.leftChild) == ){
if(compareHeight(subTree.rightChild.rightChild, subTree.rightChild.leftChild)>=){
l_Rotate(subTree);
}else{
r_Rotate(subTree.rightChild);
l_Rotate(subTree);
}
}
}
} /*重新赋值当前结点高度*/
subTree.height = Math.max(height(subTree.leftChild), height(subTree.rightChild))+;
} /**
* 删除结点
* @param subTree
* @param dv
*/
public void deleteNote(TreeNode subTree, int dv){
if(subTree == null){
System.out.println("node is not exist."); }else if(subTree.data == dv){ /*叶结点删除*/
if (subTree.leftChild == null && subTree.rightChild == null) {
if(subTree.parent != null){
/*非单结点树*/
if(subTree.parent.leftChild == subTree){
subTree.parent.leftChild = null;
}else{
subTree.parent.rightChild = null;
}
TreeNode cn = subTree.parent;
subTree.parent = null;
/*递归往上检查父类高度是否变化*/
deleteCheck(cn);
}else{
/*单结点树*/
this.root = null;
} /*删除结点只存在左子树*/
}else if(subTree.leftChild != null && subTree.rightChild == null){
TreeNode ln = subTree.leftChild;
ln.parent = subTree.parent;
subTree.leftChild = null;
if(subTree.parent != null){
if(subTree.parent.leftChild == subTree){
subTree.parent.leftChild = ln;
}else{
subTree.parent.rightChild = ln;
}
subTree.parent = null;
/*递归往上检查父类高度是否变化*/
deleteCheck(ln.parent);
}else{
this.root = ln;
} /*删除结点只存在右子树*/
}else if(subTree.leftChild == null && subTree.rightChild != null){
TreeNode rn = subTree.rightChild;
rn.parent = subTree.parent;
subTree.rightChild = null;
if(subTree.parent != null){
if(subTree.parent.leftChild == subTree){
subTree.parent.leftChild = rn;
}else{
subTree.parent.rightChild = rn;
}
subTree.parent = null;
/*递归往上检查父类高度是否变化*/
deleteCheck(rn.parent);
}else{
this.root = rn;
} /*删除结点左右子树非空*/
}else{
TreeNode cn; //删除
/*寻找直接前驱*/
TreeNode tn = subTree.leftChild; if(tn.rightChild == null){
if(subTree.parent == null){
this.root = tn;
}else if(subTree.parent.leftChild == subTree){
subTree.parent.leftChild = tn;
}else if(subTree.parent.rightChild == subTree){
subTree.parent.rightChild = tn;
}
tn.parent = subTree.parent;
tn.rightChild = subTree.rightChild;
subTree.rightChild.parent = tn;
cn = tn;
}else{
while(tn.rightChild != null){
tn = tn.rightChild;
}
/*释放前驱结点*/
cn = tn.parent;
if(tn.leftChild != null){
tn.parent.rightChild = tn.leftChild;
tn.leftChild.parent = tn.parent;
tn.parent = null;
tn.leftChild = null;
}else{
tn.parent.rightChild = null;
tn.parent = null;
} /*tn是删除节点的左子树最大值(即前驱),替换删除节点*/
if(subTree.parent == null){
this.root = tn;
}else if(subTree.parent.leftChild == subTree){
subTree.parent.leftChild = tn;
}else if(subTree.parent.rightChild == subTree){
subTree.parent.rightChild = tn;
}
tn.parent = subTree.parent;
tn.leftChild = subTree.leftChild;
subTree.leftChild.parent = tn;
tn.rightChild = subTree.rightChild;
subTree.rightChild.parent = tn;
tn.height = subTree.height; }
subTree.parent = null;
subTree.leftChild = null;
subTree.rightChild = null;
subTree = null; /*递归往上检查父类高度是否变化*/
deleteCheck(cn);
} }else if(subTree.data > dv){
/*从左子树继续递归删除*/
deleteNote(subTree.leftChild, dv); }else if(subTree.data < dv){
/*从右子树继续递归删除*/
deleteNote(subTree.rightChild, dv);
}
} /**
* 删除往上递归检查父节点树高度是否发生变化和是否失衡
* @param subTree
*/
public void deleteCheck(TreeNode subTree){ if(subTree != null){
TreeNode pn = subTree.parent;
int hl = height(subTree.leftChild);
int hr = height(subTree.rightChild);
int height = hl>=hr?hl+:hr+;
int cp = compareHeight(subTree.leftChild,subTree.rightChild); if(subTree.height != height){
subTree.height = height;
} /*判断是否LL或LR*/
if(cp == ){
if(compareHeight(subTree.leftChild.leftChild,subTree.leftChild.rightChild)>=){
r_Rotate(subTree);
}else{
l_Rotate(subTree.leftChild);
r_Rotate(subTree);
}
} /*判断是否RR或RL*/
if(cp == -){
if(compareHeight(subTree.rightChild.rightChild, subTree.rightChild.leftChild)>=){
l_Rotate(subTree);
}else{
r_Rotate(subTree.rightChild);
l_Rotate(subTree);
}
} /*继续往上检查父类*/
deleteCheck(pn);
}
} /**
* 比较子树高度差
* @param subTree1
* @param subTree2
* @return
*/
public int compareHeight(TreeNode subTree1, TreeNode subTree2){
int h1 = height(subTree1);
int h2 = height(subTree2);
return h1 - h2;
} /**
* 二叉树的节点数据结构
*/
private class TreeNode{ private int data;
private TreeNode parent = null;
private TreeNode leftChild=null;
private TreeNode rightChild=null;
private int height = ; public TreeNode(int data){
this.data=data;
}
} public static void main(String[] args) { AVLTree avl = new AVLTree(); avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, );
avl.insertNote(avl.root, ); System.out.print("create t over:");
avl.inOrder(avl.root);
System.out.println(""); System.out.print("delete 1 over:");
avl.deleteNote(avl.root, );
avl.inOrder(avl.root);
System.out.println(""); System.out.print("delete 7 over:");
avl.deleteNote(avl.root, );
avl.inOrder(avl.root);
System.out.println(""); System.out.print("delete 6 over:");
avl.deleteNote(avl.root, );
avl.inOrder(avl.root);
System.out.println(""); System.out.print("delete 3 over:");
avl.deleteNote(avl.root, );
avl.inOrder(avl.root);
System.out.println(""); }
}

运行结果:

create t over:1(1),3(3),4(1),6(2),7(1),8(4),10(2),13(1),14(3),19(2),20(1),

delete 1 over:3(2),4(1),6(3),7(1),8(4),10(2),13(1),14(3),19(2),20(1),

delete 7 over:3(1),4(2),6(1),8(4),10(2),13(1),14(3),19(2),20(1),

delete 6 over:3(1),4(2),8(4),10(2),13(1),14(3),19(2),20(1),

delete 3 over:4(1),8(3),10(2),13(1),14(4),19(2),20(1),

【数据结构】平衡二叉树—AVL树的更多相关文章

  1. 大话数据结构—平衡二叉树(AVL树)

    平衡二叉树(Self-Balancing Binary Search Tree/Height-Balanced Binary Search Tree),是一种二叉排序树,当中每个节点的左子树和右子树的 ...

  2. 二叉查找树(BST)、平衡二叉树(AVL树)(只有插入说明)

    二叉查找树(BST).平衡二叉树(AVL树)(只有插入说明) 二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点, ...

  3. Java 树结构实际应用 四(平衡二叉树/AVL树)

    平衡二叉树(AVL 树) 1 看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.  左边 BST 存在的问题分析: ...

  4. D&F学数据结构系列——AVL树(平衡二叉树)

    AVL树(带有平衡条件的二叉查找树) 定义:一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树. 为什么要使用AVL树(即为什么要给二叉查找树增加平衡条件),已经在我之前的博文中说到过 ...

  5. 平衡二叉树,AVL树之图解篇

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

  6. 二叉查找树(BST)、平衡二叉树(AVL树)

    二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点,都满足其左子树上所有结点的数据域均小于或等于根结点的数据域,右 ...

  7. linux 内核数据结构之 avl树.

    转载: http://blog.csdn.net/programmingring/article/details/37969745 https://zh.wikipedia.org/wiki/AVL% ...

  8. 图解:平衡二叉树,AVL树

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

  9. 数据结构之AVL树

    AVL树是高度平衡的而二叉树.它的特点是:AVL树中任何节点的两个子树的高度最大差别为1. 旋转 如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡.这种失去平衡的可以概括为4种姿态:LL ...

随机推荐

  1. 获取youku视频下载链接(wireshark抓包分析)

    随便说两句 前两天写了一个python脚本,试图以分析网页源码的方式得到优酷视频的下载地址,结果只得到视频的纯播放地址,下载纯播放地址得到的文件也无法正常播放视频. 这里共享一下播放地址得到的方法(想 ...

  2. Hadoop单机模式安装-(2)安装Ubuntu虚拟机

    网络上关于如何单机模式安装Hadoop的文章很多,按照其步骤走下来多数都失败,按照其操作弯路走过了不少但终究还是把问题都解决了,所以顺便自己详细记录下完整的安装过程. 此篇主要介绍在虚拟机设置完毕后, ...

  3. JavaScript call

    <script> function dog() { this.sound = "wangwang~"; this.shout = function () { alert ...

  4. 把office文档转换为html过程中的一些坑

    之前和我们项目的团队一起酝酿了一个项目,公司的业务文档技术文档比较多,但都比较分散,虽然通过FTP或其他方式聚合起来了,但感觉还是不够方便. 另外公司每次都来新员工,新员工都需要一些培训,比较耗时,比 ...

  5. VisualSVN Server的配置和使用方法(转)

    1.为什么要用VisualSVN Server,而不用Subversion? 回答: 因为如果直接使用Subversion,那么在Windows 系统上,要想让它随系统启动,就要封装SVN Serve ...

  6. HTML5的 input:file上传类型控制

    一.input:file属性 属性值有以下几个比较常用: accept:表示可以选择的文件MIME类型,多个MIME类型用英文逗号分开,常用的MIME类型见下表. multiple:是否可以选择多个文 ...

  7. Linux学习笔记(14)-进程通信|共享内存

    在Linux中,共享内存是允许两个不相关的进程访问同一个逻辑内存的进程间通信方法,是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式. 不同进程之间共享的内存通常安排为同一段物理内存.进程可 ...

  8. Shell_1 简介

    1 Shell 变量 只读变量 使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变. #!/bin/bash -x varName="AAA" echo ...

  9. node.js之开发环境搭建

    一.安装linux系统 (已安装linux可跳此步骤) 虚拟机推荐选择:VirtualBox 或者 Vmware (专业版永久激活码:5A02H-AU243-TZJ49-GTC7K-3C61N) 我这 ...

  10. topcoder SRM 628 DIV2 BishopMove

    题目比较简单. 注意看测试用例2,给的提示 Please note that this is the largest possible return value: whenever there is ...