AVL Tree

前言

 

希望读者

了解二叉搜索树

了解左旋右旋基本操作

https://blog.csdn.net/hebtu666/article/details/84992363

直观感受直接到文章底部,有正确的调整策略动画,自行操作。

二叉搜索树
 

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
具体介绍和实现:https://blog.csdn.net/hebtu666/article/details/81741034

我们知道,对于一般的二叉搜索树(Binary Search Tree),其期望高度(即为一棵平衡树时)为log2n,其各操作的时间复杂度(O(log2n))同时也由此而决定。但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,

此时,其操作的时间复杂度将退化成线性的,即O(n)。我们可以通过随机化建立二叉搜索树来尽量的避免这种情况,但是在进行了多次的操作之后,由于在删除时,我们总是选择将待删除节点的后继代替它本身,这样就会造成总是右边的节点数目减少,以至于树向左偏沉。这同时也会造成树的平衡性受到破坏,提高它的操作的时间复杂度。
 

AVL Tree

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

这种结构是对平衡性要求最严苛的self-Balancing Binary Search Tree。

旋转操作继承自self-Balancing Binary Search Tree

public class AVLTree extends AbstractSelfBalancingBinarySearchTree

旋转

上面网址中已经介绍了二叉搜索树的调整和自平衡二叉搜索树的基本操作(左旋右旋),上篇文章我是这样定义左旋的:

达到了   看似   更平衡的效果。

我们回忆一下:

看起来好像不是很平,对吗?我们转一下:

看起来平了很多。

但!是!

只是看起来而已。

我们知道。ABCD其实都是子树,他们也有自己的深度,如果是这种情况:

我们简化一下:

转之后(A上来,3作为A的右孩子,A的右子树作为新的3的左孩子):

没错,旋转确实让树变平衡了,这是因为,不平衡是由A的左子树造成的,A的左子树深度更深。

我们这样旋转实际上是让

A的左子树相对于B提上去了两层,深度相对于B,-2,

A的右子树相对于B提上去了一层,深度相对于B,-1.

而如果是这样的:

旋转以后:

依旧是不平的。

那我们怎么解决这个问题呢?

先3的左子树旋转:

细节问题:不再讲解

这样,我们的最深处又成了左子树的左子树。然后再按原来旋转就好了。

 

旋转总结

 

那我们来总结一下旋转策略:

单向右旋平衡处理LL:

由于在*a的左子树根结点的左子树上插入结点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行一次右旋转操作;

单向左旋平衡处理RR:

由于在*a的右子树根结点的右子树上插入结点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行一次左旋转操作;

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

由于在*a的左子树根结点的右子树上插入结点,*a的平衡因子由1增至2,致使以*a为根的子树失去平衡,则需进行两次旋转(先左旋后右旋)操作。

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

由于在*a的右子树根结点的左子树上插入结点,*a的平衡因子由-1变为-2,致使以*a为根的子树失去平衡,则需进行两次旋转(先右旋后左旋)操作。

 

深度的记录

 

我们解决了调整问题,但是我们怎么发现树不平衡呢?总不能没插入删除一次都遍历一下求深度吧。

当然要记录一下了。

我们需要知道左子树深度和右子树深度。这样,我们可以添加两个变量,记录左右子树的深度。

但其实不需要,只要记录自己的深度即可。然后左右子树深度就去左右孩子去寻找即可。

这样就引出了一个问题:深度的修改、更新策略是什么呢?

单个节点的深度更新

本棵树的深度=(左子树深度,右子树深度)+1

所以写出节点node的深度更新方法:

    private static final void updateHeight(AVLNode node) {
//不存在孩子,为-1,最后+1,深度为0int leftHeight = (node.left == null) ? -1 : ((AVLNode) node.left).height;int rightHeight = (node.right == null) ? -1 : ((AVLNode) node.right).height;node.height = 1 + Math.max(leftHeight, rightHeight);}

 

写出旋转代码

配合上面的方法和文章头部给出文章Abstract Self-Balancing Binary Search Tree的旋转,我们可以AVL树的四种旋转:

    private Node avlRotateLeft(Node node) {Node temp = super.rotateLeft(node);updateHeight((AVLNode)temp.left);updateHeight((AVLNode)temp);return temp;}private Node avlRotateRight(Node node) {Node temp = super.rotateRight(node);updateHeight((AVLNode)temp.right);updateHeight((AVLNode)temp);return temp;}protected Node doubleRotateRightLeft(Node node) {node.right = avlRotateRight(node.right);return avlRotateLeft(node);}protected Node doubleRotateLeftRight(Node node) {node.left = avlRotateLeft(node.left);return avlRotateRight(node);}

请自行模拟哪些节点的深度记录需要修改。

 

总写调整方法

 

我们写出了旋转的操作和相应的深度更新。

现在我们把这些方法分情况总写。

    private void rebalance(AVLNode node) {while (node != null) {Node parent = node.parent;int leftHeight = (node.left == null) ? -1 : ((AVLNode) node.left).height;int rightHeight = (node.right == null) ? -1 : ((AVLNode) node.right).height;int nodeBalance = rightHeight - leftHeight;if (nodeBalance == 2) {if (((AVLNode)node.right.right).height+1 == rightHeight) {node = (AVLNode)avlRotateLeft(node);break;} else {node = (AVLNode)doubleRotateRightLeft(node);break;}} else if (nodeBalance == -2) {if (((AVLNode)node.left.left).height+1 == leftHeight) {node = (AVLNode)avlRotateRight(node);break;} else {node = (AVLNode)doubleRotateLeftRight(node);break;}} else {updateHeight(node);//平衡就一直往上更新高度}node = (AVLNode)parent;}}

插入完工

 

我们的插入就完工了。

    public Node insert(int element) {Node newNode = super.insert(element);//插入rebalance((AVLNode)newNode);//调整return newNode;}

 

删除

也是一样的思路,自底向上,先一路修改高度后,进行rebalance调整。

    public Node delete(int element) {Node deleteNode = super.search(element);if (deleteNode != null) {Node successorNode = super.delete(deleteNode);//结合上面网址二叉搜索树实现的情况介绍if (successorNode != null) {// if replaced from getMinimum(deleteNode.right) // then come back there and update heightsAVLNode minimum = successorNode.right != null ? (AVLNode)getMinimum(successorNode.right) : (AVLNode)successorNode;recomputeHeight(minimum);rebalance((AVLNode)minimum);} else {recomputeHeight((AVLNode)deleteNode.parent);//先修改rebalance((AVLNode)deleteNode.parent);//再调整}return successorNode;}return null;}/*** Recomputes height information from the node and up for all of parents. It needs to be done after delete.*/private void recomputeHeight(AVLNode node) {while (node != null) {node.height = maxHeight((AVLNode)node.left, (AVLNode)node.right) + 1;node = (AVLNode)node.parent;}}/*** Returns higher height of 2 nodes. */private int maxHeight(AVLNode node1, AVLNode node2) {if (node1 != null && node2 != null) {return node1.height > node2.height ? node1.height : node2.height;} else if (node1 == null) {return node2 != null ? node2.height : -1;} else if (node2 == null) {return node1 != null ? node1.height : -1;}return -1;}

请手动模拟哪里的高度需要改,哪里不需要改。

 

直观表现程序

 

如果看的比较晕,或者直接从头跳下来的同学,这个程序是正确的模拟了,维护AVL树的策略和一些我没写的基本操作。大家可以自己操作,直观感受一下。

https://www.cs.usfca.edu/~galles/visualization/AVLtree.html?utm_source=qq&utm_medium=social&utm_oi=826801573962338304

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445462.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++基础学习(07)--(类)

文章目录目录类与对象1.类成员函数2.类访问修饰符3.构造函数与析构函数4.拷贝构造函数5. 友元函数6.内联函数7.this指针8.指向类的指针9.类的静态成员目录 类与对象 #include <iostream>using namespace std;class Box {public:double length; // 长度double breadth;…

c++基础学习(08)--(继承、重载、多态、虚函数)

文章目录目录1.继承2.重载3.多态 && 虚函数目录 1.继承 #include <iostream>using namespace std;// 基类 class Shape {public:void setWidth(int w){width w;}void setHeight(int h){height h;}protected:int width;int height; };// 派生类 class Rectang…

图的应用

1. 图的应用总览 在数据结构中图的应用很广泛&#xff0c;本文主要从以下四个方面介绍&#xff1a; ①最小生成树&#xff1a;给定一个无向网络&#xff0c;在该网的所有生成树中&#xff0c;使得各边权数之和最小的那棵生成树称为该网的最小生成树&#xff0c;也叫最小代价…

c++基础学习(09)--(数据抽象、数据封装、接口)

文章目录目录1.数据抽象2.数据封装3.抽象接口类目录 1.数据抽象 数据抽象&#xff1a;就是把它当做黑箱子使用&#xff0c;内部实现与外部接口分开 C类实现数据抽象&#xff0c;如sort()函数&#xff0c;ostream的cout对象 #include <iostream> using namespace std…

c++基础学习(10)--(文件、流、异常处理、动态内存、命名空间)

文章目录目录1.文件和流2.异常处理3.动态内存4.命名空间目录 1.文件和流 注意 文件打开方式中的in和out都是相对于内存&#xff08;计算机&#xff09;而言的&#xff0c;计算机读取文件&#xff0c;是将数据从磁盘中的文件读入到内存中&#xff0c;所以用的是in #include &…

数据结构和算法(02)---字符串(c++)

文章目录目录一.c风格的字符串与操作函数1.c风格字符串2.c风格字符串处理函数二.c中的字符串与操作函数1.c中的string类2.string类的基本操作3.string类的操作汇总目录 数据结构&#xff1a; 逻辑结构&#xff1a;数组&#xff0c;栈&#xff0c;队列&#xff0c;字符串&#x…

如何学习数据结构和算法——大佬文章汇总

第一篇 第二篇、 作者&#xff1a;左程云 我分别说一下国内和国外的行情。 国内的话&#xff0c;一般来讲&#xff0c;工资高的公司在面试时算法和数据结构题目的比重较大&#xff0c;工资一般的公司比重较小。当然同样公司的不同岗位&#xff0c;要求也会不同&#xff0c;…

c++基础学习(13)--(STL、标准库)

文章目录目录1. STL教程2.标准库3.有用的资源目录 1. STL教程 #include <iostream> #include <vector> using namespace std;int main() {// 创建一个向量存储 intvector<int> vec; int i;// 显示 vec 的原始大小cout << "vector size " &…

哈夫曼实现文件压缩解压缩(c语言)

写一个对文件进行压缩和解压缩的程序&#xff0c;功能如下&#xff1a; ① 可以对纯英文文档实现压缩和解压&#xff1b; ② 较好的界面程序运行的说明。 介绍哈夫曼&#xff1a; 效率最高的判别树即为哈夫曼树 在计算机数据处理中&#xff0c;霍夫曼编码使用变长编码表对源…

c++基础学习(11)--(模板、预处理器、信号处理)

文章目录目录1.模板2.预处理器3.信号处理目录 1.模板 模板是泛型编程的基础&#xff0c;泛型编程&#xff1a;以一种独立于任何特定类型的方式 #include <iostream> #include <string>using namespace std;template <typename T> inline T const& Max…

c++基础学习(12)--(多线程、Web编程)

文章目录目录1.多线程2.web编程目录 1.多线程 #include <iostream> // 必须的头文件 #include <pthread.h>using namespace std;#define NUM_THREADS 5// 线程的运行函数 void* say_hello(void* args) {cout << "Hello Runoob&#xff01;" <&…

《Head First设计模式》第九章(1)迭代器模式

迭代器模式 因为这一章涉及到两个模式&#xff0c;内容有点多&#xff0c;还有一个组合模式留到下一篇写吧。 有许多种方法可以把对象堆起来成为一个集合&#xff08;collection&#xff09;。你可以把它们放进数组、堆栈、列表或者是散列表&#xff08;Hashtable&#xff09…

索尼XB950N1 震撼人心的重低音

虽然题目是震撼人心的重低音&#xff0c;但是低音可以通过app调节&#xff0c;所以我们可以用这个耳机听各种类型的歌曲。 索尼XB950N1与XB950B1非常相似&#xff0c;但索尼XB950N1提供了主动降噪&#xff0c;续航稍长一些。从蓝牙3.0升级到了蓝牙4.1&#xff0c;改善了传输范…

数据结构和算法(04)---数组,动态内存,vector(c++)

文章目录目录数组1.数组的申明2.数组的初始化3.二维数组4.指向数组的指针5.传递数组给函数动态内存1.new &#xff0c;delete运算符2.数组的动态内存分配vector1.vector基本操作2.vector使用3.vector动态二维数组 初始化和赋值目录 数据结构&#xff1a; 逻辑结构&#xff1a;数…

数据结构和算法(05)---链表(c++)

文章目录目录链表的基本概念1.数组和链表链表的使用1.链表的简单使用2.链表的进阶使用3.链表的高阶使用4.链表的其他操作链表容器list1.list介绍2. list使用3. list与vector之间的区别4.list例子代码目录 数据结构&#xff1a; 逻辑结构&#xff1a;数组&#xff0c;栈&#xf…

论文阅读 状态压缩

状态压缩 Abstract 信息学发展势头迅猛&#xff0c;信息学奥赛的题目来源遍及各行各业&#xff0c;经常有一些在实际应用中很有价值的问题被引入信息学并得到有效解决。然而有一些问题却被认为很可能不存在有效的(多项式级的)算法&#xff0c;本文以对几个例题的剖析&#xf…

数据结构和算法(06)---二叉树(c++)

文章目录目录二叉树1.二叉树的基本概念2.二叉树的应用和时间复杂度3.二叉树的插入4.二叉树的查找5. 二叉树的遍历6.二叉树的删除二叉树的基本操作1.二叉树的基础操作2.代码实现创建二叉树和三种遍历二叉树的方法目录 数据结构&#xff1a; 逻辑结构&#xff1a;数组&#xff0c…

如何转载CSDN博客

前言 对于喜欢逛CSDN的人来说&#xff0c;看别人的博客确实能够对自己有不小的提高&#xff0c;有时候看到特别好的博客想转载下载&#xff0c;但是不能一个字一个字的敲了&#xff0c;这时候我们就想快速转载别人的博客&#xff0c;把别人的博客移到自己的空间里面&#xff0c…

CSDN写博客(字体颜色、大小)

markdown里面的标记语言可以使用标签对来实现对文本文字颜色大小信息的控制。下面给出几个实例&#xff1a; 黑体字示例 微软雅黑示例 华文彩云示例 color#00ffff size可以根据实际大小进行设置&#xff0c;一般不超过7。 红色字体CSDN 红色字体CSDN 使用十六进制颜色值 …

bose qc30 安静的城市是什么样子

使用感受 网友1&#xff08;20岁&#xff09;&#xff1a; 当你带着这个耳机听音乐的时候&#xff0c;有一种感觉&#xff0c;感觉这个世界都是你歌曲里的MV&#xff0c;这个枯燥乏味的世界都被赋予了你心中的那份情感&#xff0c;这种感觉&#xff0c;真的很棒 网友2&#…