平衡二叉树(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; //关键字 ...
随机推荐
- CentOS7各个版本镜像下载地址
# CentOS7.6 下载地址 # CentOS-7-x86_64-DVD-1810.iso CentOS 7.6 DVD 版 4G http://mirrors.163.com/centos/7. ...
- 学习笔记_J2EE_SpringMVC_03_注解配置_@RequestMapping用法
@RequestMappingde的用法 摘要: 主要介绍注解@RequestMapping的用法 一.@RequestMapping 简介 在Spring MVC 中使用 @RequestMappi ...
- mysql 和 sqlserver sql差异比较
mysql:select * from table_name limit 100,200;--取出从100到200的数据 获取时间:mysql:now() mysql tinyint(0,1) → b ...
- redis 集群 遇坑1
redis 集群 遇坑1 redis集群需要开2个端口 一个是客户端连接端口 一个是 集群总线端口 集群总线端口 是 客户端端口 + 10000 如 客户端端口是 6380 则集群总线端口 为 163 ...
- .net 第一次请求比较慢
为了提高访问速度,也便有了预编译. 关于ASP.NET网站:每个页面都编译成一个.dll文件 用Assembly.GetExecutingAssembly().Location 查看 而ASP.NET ...
- Java对象引用和对象赋值
关于对象与引用之间的一些基本概念. 初学Java时,在很长一段时间里,总觉得基本概念很模糊.后来才知道,在许多Java书中,把对象和对象的引用混为一谈.可是,如果我分不清对象与对象引用,那实在没法很好 ...
- 记录新项目中遇到的技术及自己忘记的技术点【DES加密解密,MD5加密,字符串压缩、解压,字符串截取等操作】
一.DES加密.解密 #region DES加密解密 /// <summary> /// 进行DES加密 /// </summary> /// <param name=& ...
- 几个VB常见又内涵的错误
第一位内涵的就是:没有对象 找到对象,却发现是别人的对象 不能加载也不能卸载...这到底是什么对象 哈哈哈~
- PHP生成图表pChart
pChart是一个开源的图表生成库,主要涉及3个Class:pChart.class, pData.class, pCache.class,可生成20多种简单或复杂的图表,支持PNG,JPG,GIF通 ...
- linux系统资源监控
top命令 1.平均负载(load average): 正在耗费CPU进程与正在等待io的进程之和,三个值分别是一分钟,五分钟,十五分钟的平均负载,负载值只要小于CPU颗粒数属于正常情况 任务进程(T ...