「C++」AVL树的实现(动图)

2024-03-01 03:10
文章标签 c++ 实现 avl 动图

本文主要是介绍「C++」AVL树的实现(动图),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

💻文章目录

  • AVL树
    • 概念
    • AVL的查找
    • AVL树的插入
  • 代码部分
    • AVL树的定义
    • 查找
    • 插入
    • 旋转
  • 📓总结


AVL树

概念

AVL树又名高度平衡的二叉搜索树,由G. M. Adelson-Velsky和E. M. Landis发明,顾名思义,其任意节点的左右子树最大高度差都不超过1,以此来阻止二叉搜索树退化成为单叉树这种情况。

AVL树具有以下的特性:

  • 任意节点的左右子树最大高度差不超过1
  • 所有节点的左节点都比父节点小。
  • 所有节点的右节点都比父节点大。
  • 它的左右子树都是AVL树。
  • 中序遍历是有序的

AVL树与普通二叉搜索树的对比:

在这里插入图片描述

AVL的查找

AVL树的查找与二叉搜索树基本一致,因为其自身的性质,所以只要查找的数据比当前节点小就要到左节点找,反之就是右节点。

请添加图片描述

AVL树的插入

在介绍插入前得先说一下平衡因子,这是为了得知插入新结点后树是否还平衡的方式之一。

平衡因子是在每个结点上安置一个数字,如果是新插入的节点则它的数值为0,如果其在双亲节点的右边,则双亲节点的平衡因子++,反之–,然后继续向上调整,直到父节点的因子为0/2/-2。

在这里插入图片描述

AVL因为要保持其高度平衡的特性,所以每次插入都要检查其是否平衡,如果不平衡(平衡因子的绝对值大于1),则需要通过旋转来让树保持平衡。

AVL树的旋转大致分为两种情况:

  • 极端倾斜(左倾、右倾)
  • 非极端倾斜(局部左倾,局部右倾)

极端倾斜:
极端倾斜的情况比较容易解决,如果是右倾,那么只需要让平衡因子为2的节点做左旋运动,然后更新平衡因子即可,左倾则和右倾相反,做右旋操作。
请添加图片描述

局部倾斜:
局部倾斜分为局部左倾和右倾,而左右倾其中又分为三中情况,为了方便说明,我用parent来表示平衡因子(bf)为2的节点,subR来表示parent->right,subRL来表示subR->left 。

  • subRL为0则表示subRL为新增节点
  • subRL为1则表示新节点在subRL的右子树
  • subRL为-1则表示新节点在subRL的左子树

subRL为0的情况:
在这里插入图片描述
subRL为1的情况:
在这里插入图片描述
subRL为-1的情况:
在这里插入图片描述

代码部分

AVL树的定义

因为我们需要频繁去调整树的平衡,使用普通的二链结构会比较难以控制节点,所以我使用了三叉链的结构,多增加了一个指向父节点的指针。

template<class K, class V>
struct AVLTreeNode
{AVLTreeNode(const std::pair<K, V> kv): _left(nullptr)	, _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}	AVLTreeNode<K, V>* _left;	//左节点AVLTreeNode<K, V>* _right;	//右节点AVLTreeNode<K, V>* _parent;	//父节点std::pair<K, V> _kv;		//使用pair当作数据int _bf;   // 节点的平衡因子
};

查找

二叉搜索树与AVL树的搜索基本无区别

template <class K, class V>
typename AVLTree<K, V>::Node* AVLTree<K, V>::find(const std::pair<K, V>& data)
{Node* cur = _root;	while(cur){if(cur->_kv.first < data.first){	//到右节点寻找cur = cur->_right;}else if(cur->_kv.first > data.first){	//到左节点寻找cur = cur->_left;}else {	//找到return cur;}}return cur;
}

插入

AVL树的插入无非也就是弄清楚倾斜的时机和位置,就和上方所说的,AVL树的旋转情况只有极端和非极端的,如果没有出现不平衡则向上调整。

template <class K, class V>
bool AVLTree<K, V>::Insert(const std::pair<K, V> data)
{if(!_root){_root = new Node(data);return true;}Node* cur = _root;Node* parent = nullptr;while(cur)	//先搜索{if(cur->_kv.first < data.first){parent = cur;cur = cur->_right;}else if(cur->_kv.first > data.first){parent = cur;cur = cur->_left;}else return false;}cur = new Node(data);		//创建新节点cur->_parent = parent;		//链接if(!parent->_left){parent->_left = cur;}else {parent->_right = cur;}while(parent){if(cur == parent->_left)parent->_bf--;		//这里与上方动图有些许不同,动图与我平衡因子的加减是相反的else	// cur == parent->_rightparent->_bf++;if(parent->_bf == 0)	//为0说明左右节点最大高度差一致break;else if(parent->_bf == 1 || parent->_bf == -1)	//为1则继续向上调整{cur = parent;		parent = parent->_parent;}else if(parent->_bf == 2 || parent->_bf == -2)	//出现了不平衡的清空{if(parent->_bf == 2 && cur->_bf == 1)	//极端右倾{RotateL(parent);					//左倾}else if(parent->_bf == -2 && cur->_bf == -1)	//极端左倾{RotateR(parent);					//右倾}else if(parent->_bf == 2 && cur->_bf == -1)	//局部左倾{RotateRL(parent);					//先右倾,再左倾}else if(parent->_bf == -2 && cur->_bf == 1)	//局部右倾{	RotateLR(parent);					//左倾再右倾}break;}else assert(false);}return true;
}

旋转

AVL树的旋转其难点在于正确的连接节点,与调整平衡因子的数值。

void AVLTree<K, V>::RotateL(Node* parent)
{	//左旋Node* subR = parent->_right;	Node* subRL = subR->_left;Node* parentParent = parent->_parent;parent->_right = subRL;		//交换父节点和其右孩子的位置parent->_parent = subR;subR->_left = parent;		subR->_parent = parentParent;if(subRL)	//subRL有为空的可能性subRL->_parent = parent;if(_root == parent){	_root = subR;}else {	//连接祖父节点if(parent == parentParent->_left){parentParent->_left = subR;}else {parentParent->_right = subR;}}parent->_bf = subR->_bf = 0;	//调整平衡因子
}void AVLTree<K, V>::RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;subL->_right = parent;subL->_parent = parentParent;parent->_left = subLR;parent->_parent = subL;if(subLR)subLR->_parent = parent;if(parent == _root)_root = subL;else{if(parent == parentParent->_left)parentParent->_left = subL;elseparentParent->_right = subL;}subL->_bf = parent->_bf = 0;
} void AVLTree<K, V>::RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;	//先记录平衡因子,以防调整后丢失RotateR(parent->_right);RotateL(parent);//调整因子if(bf == 0)	//说明subRL是新增节点{parent->_bf = subR->_bf = subRL->_bf = 0;}else if(bf == 1)	//新增节点在subRL的右子树{parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if(bf == -1) 	//新增节点在subRL的右子树{subRL->_bf = 0;subR->_bf = 1;parent->_bf = 0;}else {assert(false);}
}void AVLTree<K, V>::RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if(bf == 0){parent->_bf = subL->_bf = subLR->_bf = 0;}else if(bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if(bf == 1){subL->_bf = -1;parent->_bf = 0;subLR->_bf = 0;}else assert(false);
}

📓总结

AVL的时间复杂度:

函数时间复杂度
find O ( l o g 2 n ) O(log_2n) O(log2n)
insert() O ( l o g 2 n ) O(log_2n) O(log2n)

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

这篇关于「C++」AVL树的实现(动图)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/761023

相关文章

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

C++的模板(八):子系统

平常所见的大部分模板代码,模板所传的参数类型,到了模板里面,或实例化为对象,或嵌入模板内部结构中,或在模板内又派生了子类。不管怎样,最终他们在模板内,直接或间接,都实例化成对象了。 但这不是唯一的用法。试想一下。如果在模板内限制调用参数类型的构造函数会发生什么?参数类的对象在模板内无法构造。他们只能从模板的成员函数传入。模板不保存这些对象或者只保存他们的指针。因为构造函数被分离,这些指针在模板外

C++工程编译链接错误汇总VisualStudio

目录 一些小的知识点 make工具 可以使用windows下的事件查看器崩溃的地方 dumpbin工具查看dll是32位还是64位的 _MSC_VER .cc 和.cpp 【VC++目录中的包含目录】 vs 【C/C++常规中的附加包含目录】——头文件所在目录如何怎么添加,添加了以后搜索头文件就会到这些个路径下搜索了 include<> 和 include"" WinMain 和

C/C++的编译和链接过程

目录 从源文件生成可执行文件(书中第2章) 1.Preprocessing预处理——预处理器cpp 2.Compilation编译——编译器cll ps:vs中优化选项设置 3.Assembly汇编——汇编器as ps:vs中汇编输出文件设置 4.Linking链接——链接器ld 符号 模块,库 链接过程——链接器 链接过程 1.简单链接的例子 2.链接过程 3.地址和

C++必修:模版的入门到实践

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C++学习 贝蒂的主页:Betty’s blog 1. 泛型编程 首先让我们来思考一个问题,如何实现一个交换函数? void swap(int& x, int& y){int tmp = x;x = y;y = tmp;} 相信大家很快就能写出上面这段代码,但是如果要求这个交换函数支持字符型

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

C++入门01

1、.h和.cpp 源文件 (.cpp)源文件是C++程序的实际实现代码文件,其中包含了具体的函数和类的定义、实现以及其他相关的代码。主要特点如下:实现代码: 源文件中包含了函数、类的具体实现代码,用于实现程序的功能。编译单元: 源文件通常是一个编译单元,即单独编译的基本单位。每个源文件都会经过编译器的处理,生成对应的目标文件。包含头文件: 源文件可以通过#include指令引入头文件,以使

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主