平衡二叉树(AVL)介绍及其实现
一、平衡二叉树
任何一个数据的查找过程都需要从根结点出发,沿某一个路径朝叶子结点前进。因此查找中数据比较次数与树的形态密切相关。 对于二叉树来说,当树中每个结点左右子树高度大致相同时,树高为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)介绍及其实现的更多相关文章
- 数据结构与算法--从平衡二叉树(AVL)到红黑树
数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树.算法的性能取决于树的形状,而树的形状取决于插入键的顺序.在最好的情况下,n个结点的树是完全平衡的,如下图"最好情况&q ...
- Java 树结构实际应用 四(平衡二叉树/AVL树)
平衡二叉树(AVL 树) 1 看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在. 左边 BST 存在的问题分析: ...
- 二叉查找树(BST)、平衡二叉树(AVL树)(只有插入说明)
二叉查找树(BST).平衡二叉树(AVL树)(只有插入说明) 二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点, ...
- 平衡二叉树AVL - 插入节点后旋转方法分析
平衡二叉树 AVL( 发明者为Adel'son-Vel'skii 和 Landis)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1. 首先我们知道,当插入一个节点,从此插入点到树根 ...
- 二叉查找树、平衡二叉树(AVL)、B+树、联合索引
1. [定义] 二叉排序树(二拆查找树)中,左子树都比节点小,右子树都比节点大,递归定义. [性能] 二叉排序树的性能取决于二叉树的层数 最好的情况是 O(logn),存在于完全二叉排序树情况下,其访 ...
- 平衡二叉树AVL删除
平衡二叉树的插入过程:http://www.cnblogs.com/hujunzheng/p/4665451.html 对于二叉平衡树的删除采用的是二叉排序树删除的思路: 假设被删结点是*p,其双亲是 ...
- 数据结构快速回顾——平衡二叉树 AVL (转)
平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发明了这棵 ...
- K:平衡二叉树(AVL)
相关介绍: 二叉查找树的查找效率与二叉树的形状有关,对于按给定序列建立的二叉排序树,若其左.右子树均匀分布,则查找过程类似于有序表的二分查找,时间复杂度变为O(log2n).当若给定序列原来有序,则 ...
- 平衡二叉树,AVL树之代码篇
看完了第一篇博客,相信大家对于平衡二叉树的插入调整以及删除调整已经有了一定的了解,下面,我们开始介绍代码部分. 首先,再次提一下使用的结构定义 typedef char KeyType; //关键字 ...
随机推荐
- 移动端web app开发学习笔记
移动web和pc端web以及web app 移动web开发跟web前端开发差别很小,使用的技术都是html+css+js.手机网页可以理解成pc网页的缩小版加一些触摸特性.在浏览器中进行的网页开发,最 ...
- poj1988 Cube Stacking 带权并查集
题目链接:http://poj.org/problem?id=1988 题意:有n个方块,编号为1-n,现在存在两种操作: M i j 将编号为i的方块所在的那一堆方块移到编号为j的方块所在的那 ...
- es6 let 和 const
function test(){ let a = 1 for (let i =0;i<3;i++){ console.log(i) //1,2 } console.log(i) ...
- Spring boot实现原生websocket
网上的大部分教程是基于sockjs,这篇文章内容则是基于原生协议. 后台Spring boot 配置 @Configuration @EnableWebSocket public class WebS ...
- haproxy快速安装
haproxy是一款提供负载均衡的代理服务器,它可基于modetcp 实现伪四层调度,还可以基于modehttp实现七层调度,类似于nginx,因为他没有web服务所以不像nginx那样进行控制. 通 ...
- Docker 学习2 Docker基础用法
一.docker架构 1.client端 2.server端,docker daemo守护进程,监听在套接字之上.docker支持三种类型套接字. a.ip vs套接字:即IP + 端口套接字 b.i ...
- 远程服务器数据交互技术:rsync,scp,mysqldump
远程服务器间数据文件交互,可用技术:rsync,scp 速度:rsync是非加密传输,比scp快 安全:scp为加密传输 备份体量:rsync只更新差异部分,可以做增量和全量备份.scp为全量 传输方 ...
- DBUtils - Python数据库连接池
flask没有ORM操作这一功能, 但是想要操作数据库怎么办呢, 使用第三方包DBUtils 查询需要SQL原生语句! DBUtils 第一步还是要下载 使用pycharm直接搜索DBUtils fr ...
- 20190312_浅谈go&java差异(一)
多线程 java java中对于大量的比较耗时的任务多采用多线程对方式对任务进行处理,同时由于进程和线程 本身是通过宿主机OS进行管理的,当在cpu核数较少或线程分配不当 会导致多线程的效果不佳的事常 ...
- 图论之Dijkstra算法
Dijkstra算法是图论中经典的最短路径算法之一,主要用于解决单源最短路径问题. 单源最短路径问题,即求某个源节点到其他各个节点的最短路径. Dijkstra算法采用了贪心算法的思想,如图求1号节点 ...