一、平衡二叉树

  任何一个数据的查找过程都需要从根结点出发,沿某一个路径朝叶子结点前进。因此查找中数据比较次数与树的形态密切相关。 对于二叉树来说,当树中每个结点左右子树高度大致相同时,树高为logN。则平均查找长度与logN成正比,查找的平均时间复杂度在O(logN)数量级上。当先后插入的关键字有序时,BST退化成单支树结构。此时树高n。平均查找长度为(n+1)/2,查找的平均时间复杂度在O(N)数量级上。

  二叉查找树在最差情况下竟然和顺序查找效率相当,这是无法仍受的。事实也证明,当存储数据足够大的时候,树的结构对某些关键字的查找效率影响很大。当然,造成这种情况的主要原因就是BST不够平衡(左右子树高度差太大)。既然如此,那么我们就需要通过一定的算法,将不平衡树改变成平衡树。因此,AVL树就诞生了。AVL(Adelson-Velskii and Landis)树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文《An algorithm for the organization of information》中发表了它 。

  在二叉树中,任何一个节点v的平衡因子都定义为其左、右子树的高度差。空树的高度定义为-1。在二叉查找树T中,若所有节点的平衡因子的绝对值均不超过1,则称T为一棵AVL树。维护平衡只需在插入和删除时维护平衡即可。

    

  新节点记为N,第一个被破坏平衡的祖先记为p(至少是祖父),在同一路径上的p的子节点记为q,q的子节点记为s。按pqs的位置关系分为左左型,左右型,右右型,右左型,两两对称。通过旋转来使树重新平衡,旋转不会破坏BST的性质,也就是中序遍历的顺序,但能改变子树高度。

    

  旋转操作:旋转操作是一种调整树结构而不改变二叉查找树特性的手段。这里要理解树旋转的意义,树的最终目的不是维护节点与节点之间的层级关系,关键是如何用AVL树这种数据结构进行更好的查找和搜索。

    

  先看中间,左左型和右右型,它们只需沿反方向旋转一次即可。左右型和右左型,先调整q和s,转变为上述两种类型。念一下中序遍历顺序,找找感觉。

   

二、代码实现

  AVL树是一棵二叉查找树,与普通二叉查找树不同的是,在插入和删除节点之后需要重新进行平衡,因此继承并重写普通二叉查找树的insert和remove方法,就可以实现一棵AVL树。代码里重点就是插入和删除怎样去维护平衡。

 // 涉及到的类前面博客都有
public class AVLTree<K, V> extends BinarySearchTree<K, V> implements IBinarySearchTree<K, V> {
@Override
public BSTNode<K, V> insert(K key, V value) {
// 先按bst的方式来插入,再调整
BSTNode<K, V> nnode = super.insert(key, value);
// 向上找到第一个不平衡的祖先p
BSTNode<K, V>[] pqs = firstUnbalance(nnode);
if (null != pqs) {// 不平衡
// System.out.println(pqs[0].key);
reBalance(pqs);
}
return nnode;
} /* 分pqs的形状,来调用左旋和右旋 */
private void reBalance(BSTNode<K, V>[] pqs) {
if (pqs == null)
return;
BSTNode p = pqs[0];// 不平衡的那个祖先
BSTNode q = pqs[1];// p的子节点
BSTNode s = pqs[2];// q的子节点 if (q.isRight() && s.isRight()) {// 右右型,以p为中心逆时针旋转
leftRotate(p, q); // 方法在前面的博客
// reBalance(firstUnbalance(q));
} else if (q.isLeft() && s.isLeft()) {// 左左型,以p为中心顺时针旋转
rightRotate(p, q);
// reBalance(firstUnbalance(q));
} else if (q.isLeft() && s.isRight()) {// 左右型
// q.right = s.left;
// if (s.left != null) {
// s.left.parent = q;
// s.left.isLeftChild = false;
// }
// q.parent = s;
// s.left = q;
// q.isLeftChild = true;
//
// s.parent = p;
// p.left = s;
// s.isLeftChild = true;
leftRotate(q, s);// q,s左旋,变为左左型
rightRotate(p, s);
// reBalance(firstUnbalance(s));
} else {// 右左型
// q.left = s.right;
// if (s.right != null) {
// s.right.parent = q;
// s.right.isLeftChild = true;
// }
// q.parent = s;
// s.right = q;
// q.isLeftChild = false;
//
// s.parent = p;
// p.right = s;
// s.isLeftChild = false;
rightRotate(q, s);
leftRotate(p, s);
// reBalance(firstUnbalance(s));
}
} private BSTNode<K, V>[] firstUnbalance(BSTNode<K, V> n) {
if (n == null)
return null;
BSTNode s = n;
BSTNode p = n.parent;
if (p == null)
return null;
BSTNode g = p.parent;
if (g == null)
return null;
if (unBalance(g)) {// 不平衡了
return new BSTNode[] { g, p, s };
} else {
return firstUnbalance(p);
} } @Override
public void remove(K key) {
BSTNode<K, V> node = super.lookupNode(key);
if (node == null)
return; BSTNode<K, V> parent = node.parent;
BSTNode<K, V> left = node.left;
BSTNode<K, V> right = node.right; if (left == null && right == null) {// leaf node
super.removeNode(node);
reBalance(parent); // 重新平衡
} else if (right == null) {// has only left child.左孩子替换自身
// if (node.isLeft()) {
// parent.left = left;
// left.parent = parent;
// } else {
// if (parent == null) {// node is root
// left.parent = null;
// root = left;
// } else {
// parent.right = left;
// left.isLeftChild = false;
// left.parent = parent;
// }
// }
BSTNode<K, V> predecessor = maxNode(left);
BSTNode<K, V> parentOfPredecessor = predecessor.parent;
super.removeNode(predecessor);
node.key = predecessor.key;
node.value = predecessor.value;
reBalance(parentOfPredecessor); } else {// 有右孩子,找到右子树的最小
BSTNode<K, V> successor = minNode(right);
BSTNode<K, V> parentOfSuccessor = successor.parent;
// minNode must be leaf node
super.removeNode(successor);
node.key = successor.key;
reBalance(parentOfSuccessor);
}
} private void reBalance(BSTNode<K, V> node) {
if (node == null)
return; BSTNode<K, V> right = node.right;
BSTNode<K, V> left = node.left;
int hOfRight = getHeight(right);
int hOfleft = getHeight(left); if (hOfRight - hOfleft >= 2) {// 右侧高
leftRotate(node, right);// 左旋
reBalance(right);
} else if (hOfRight - hOfleft <= -2) {
rightRotate(node, left);
reBalance(left);
} else {
reBalance(node.parent);
}
}
}

平衡二叉树(AVL)介绍及其实现的更多相关文章

  1. 数据结构与算法--从平衡二叉树(AVL)到红黑树

    数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树.算法的性能取决于树的形状,而树的形状取决于插入键的顺序.在最好的情况下,n个结点的树是完全平衡的,如下图"最好情况&q ...

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

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

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

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

  4. 平衡二叉树AVL - 插入节点后旋转方法分析

    平衡二叉树 AVL( 发明者为Adel'son-Vel'skii 和 Landis)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1. 首先我们知道,当插入一个节点,从此插入点到树根 ...

  5. 二叉查找树、平衡二叉树(AVL)、B+树、联合索引

    1. [定义] 二叉排序树(二拆查找树)中,左子树都比节点小,右子树都比节点大,递归定义. [性能] 二叉排序树的性能取决于二叉树的层数 最好的情况是 O(logn),存在于完全二叉排序树情况下,其访 ...

  6. 平衡二叉树AVL删除

    平衡二叉树的插入过程:http://www.cnblogs.com/hujunzheng/p/4665451.html 对于二叉平衡树的删除采用的是二叉排序树删除的思路: 假设被删结点是*p,其双亲是 ...

  7. 数据结构快速回顾——平衡二叉树 AVL (转)

    平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发明了这棵 ...

  8. K:平衡二叉树(AVL)

    相关介绍:  二叉查找树的查找效率与二叉树的形状有关,对于按给定序列建立的二叉排序树,若其左.右子树均匀分布,则查找过程类似于有序表的二分查找,时间复杂度变为O(log2n).当若给定序列原来有序,则 ...

  9. 平衡二叉树,AVL树之代码篇

    看完了第一篇博客,相信大家对于平衡二叉树的插入调整以及删除调整已经有了一定的了解,下面,我们开始介绍代码部分. 首先,再次提一下使用的结构定义 typedef char KeyType; //关键字 ...

随机推荐

  1. win10下caffe安装与mnist测试实验注意点

    caffe安装 安装内容:win10教育版+anaconda2+python(无gpu版本) 安装教程:主要依照三年一梦教程:https://www.cnblogs.com/king-lps/p/65 ...

  2. Django models文件模型变更注意事项(表结构的修改)

    表结构的修改 1.表结构修改后,原来表中已存在的数据,就会出现结构混乱,makemigrations更新表的时候就会出错 比如第一次建模型,漏了一个字段,后来补上了.(经常遇到模型字段修改) 重新ma ...

  3. 04-HTTP协议和静态Web服务器

    一.HTTP协议(HyperText Transfer Protocol)     超文本传输协议,超文本是超级文本的缩写,是指超越文本限制或者超链接,比如:图片.音乐.视频.超链接等等都属于超文本. ...

  4. <!特别的一天>

    <!DOCTYPE html> <html> <head> <meta charset="gb2312/"> <title&g ...

  5. 《物联网框架ServerSuperIO教程》- 23.动态数据接口增加缓存,提高数据输出到OPCServer和(实时)数据库的效率

     22.1   概述及要解决的问题 设备驱动有DeviceDynamic接口,可以继承并增加新的实时数据属性,每次通讯完成后更新这些属性数据.原来是通过DeviceDynamic接口实体类反射的方式获 ...

  6. 第二项目AIaisell(易销宝)

    一.什么是报表 向上级报告情况的表格.简单的说:报表就是用表格.图表等格式来动态显示数据,可以用公式表示为:“报表 = 多样的格式 + 动态的数据” 表格:详细数据 图表: 直观 二.表格数据展示 2 ...

  7. linux安装vsftp服务

    如果管理一个网站,需要经常上传下载一些文件,通过scp传输吗?当然不是,太麻烦了,而且首先你需要本机是linux的系统,这时我们需要一个工具,叫ftp. ftp是文件传输协议,通过它可以很方便上传下载 ...

  8. sql预编译&动态语句静态语句

    https://www.cnblogs.com/micrari/p/7112781.html https://www.cnblogs.com/MarsDing/p/9871703.html https ...

  9. 四、OpenStack—glance组件介绍与安装

    一.glance介绍 Glance是Openstack项目中负责镜像管理的模块,其功能包括虚拟机镜像的查找.注册和检索等. Glance提供Restful API可以查询虚拟机镜像的metadata及 ...

  10. PBRT笔记(13)——光线传播1:表面反射

    采样反射函数 BxDF::Sample_f()方法根据与相应的散射函数相似的分布来选择方向.在8.2节中,该方法用于寻找来自完美镜面的反射和透射光线;在这里讲介绍实现其他类型的采样技术. BxDF:: ...