一、平衡二叉树

  任何一个数据的查找过程都需要从根结点出发,沿某一个路径朝叶子结点前进。因此查找中数据比较次数与树的形态密切相关。 对于二叉树来说,当树中每个结点左右子树高度大致相同时,树高为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. Lesson 2-4(字典)

    2.7 字典 &.字典是许多值的集合,索引可以使用许多不同的数据类型,不只是整数,可以是数.字符串或元组. &.字典的索引被称为“键”,键及其关联的值称为“键-值”对,这种键-值对也称 ...

  2. JS监听浏览器的返回、后退、上一页按钮的事件方法

    在实际的应用中,我们常常需要实现在移动app和浏览器中点击返回.后退.上一页等按钮实现自己的关闭页面.调整到指定页面或执行一些其它操作的需求,那在代码中怎样监听当点击微信.支付宝.百度糯米.百度钱包等 ...

  3. 如何进行PDF页码编排,如何调整PDF页码顺序

    PDF文件的页码顺序如何进行调整?许多小伙伴们都不知道,我们在编辑的时候只知道PDF文件的编辑方法,但是调整页码的顺序我们或许不会,但是如何去进行操作呢?看小编的方法吧!如果我们想要修改PDF文件中的 ...

  4. nginx代理 (带着请求头)

    当你获得云服务器之后, 你有这样一个需求:当你要访问一个url的时候,这个URL只能在人家的云服务器上访问(比如百度),所以你要买百度的BCC,你可能在想在BCC起服务,那样有点麻烦,直接使用ngin ...

  5. iOS开发之获取时间戳方法

    // 得到当前本地时间,13位,整形 + (long long)gs_getCurrentTimeToMilliSecond { double currentTime = [[NSDate date] ...

  6. Java遍历Map对象的四种方式

    关于java中遍历map具体哪四种方式,请看下文详解吧. 方式一 :这是最常见的并且在大多数情况下也是最可取的遍历方式.在键值都需要时使用. Map<Integer, Integer> m ...

  7. python设计模式---结构型之代理模式

    主要想着nginx:) from abc import ABCMeta, abstractmethod # 结构型设计模式---代理模式 class Actor: def __init__(self) ...

  8. LCT入门总结

    原文链接https://www.cnblogs.com/zhouzhendong/p/LCT.html 为什么要写这个总结? 因为之前的总结出问题了…… 下载链接: LCT 入门总结 UPD(2019 ...

  9. Eclipse 那些小技巧(值得收藏)

    1.菜单命令系列 Edit→content Assist→add Alt+/ 代码关联 Windows→Next Editor→add Ctrl+Tab 切换窗口 Run→Debug Toggle L ...

  10. 【转】window.onerror跨域问题

    What the heck is "Script error"? Ben Vinegar/ May 17, 2016 If you’ve done any work with th ...