AVL树的模拟实现
我们上期提到了二叉搜索树,只是简单的讲了一下原理,那么今天我们就讲一下AVL树。
目录
- AVL树的概念
- AVL树的实现
- AVL树的架构
- insert插入
- 引用pair对象
- 引进parent指针
- 仅插入数据
- 调节平衡因子
- 情况1:插入在父亲的右边,父亲的平衡因子++后为0
- 情况2:插入在父亲的左边,父亲的平衡因子--后为0
- 情况3:插入左或者右,恰巧父亲不是0,是-1/1
- 情况4:当父亲的平衡因子==-2/2,不需要在更新了,证明不平衡了,需要旋转。
- 左边高,右旋
- 右边高,左旋
- 双旋转
- 右左旋转:先右旋然后左旋。
- 左右双旋:先左旋,在右旋。
- 中序遍历
- 判断是否平衡
- AVL树整体代码
AVL树的概念
其实大家应该很奇怪,难道二叉搜索树不能存储数据吗?为什么要有AVL树呢?二叉搜索树有可能会有畸形的情况。像下图,数据比较分散的话,这棵树很正常,如果我们插入的数相对有序就会变成右边那样畸形的树。
这个时候就需要人工干预,这里的AVL数就可以更好的控制这个情况,它有自己的平衡规则:左右子树高度之差(平衡因子)绝对不超过1(-1,0,1)。如果不满足这个规则,那么我们就旋转。
AVL树的实现
因为AVL树也是二叉搜索树的一种,所以他也要满足二叉搜索树的条件,然后在满足他自己的平衡规则。
AVL树的架构
其实数的节点架构和整体架构并没有变,只是多了一个bf(平衡因子),用来调节平衡。
struct AVLTreeNode
{AVLTreeNode(const pair<K, V> kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;pair<K, V> _kv;int _bf;
};
template<class K,class V>
class AVLTree
{public:typedef AVLTreeNode<K,V> Node;private:Node* _root=nullptr;
}
insert插入
引用pair对象
这里与原来不同,这里我们引入了一个pair对象,那么pair是什么?我们用pair来实现KV结构,在库中的map也是用pair也完成KV结构的,所以这里我们就用这pair。pair对象的first是K值,second是V值。
引进parent指针
这里引用父指针是因为我们将来要旋转,要不断向上调节平衡因子,因为当我们插入某个值有可能引起平衡因子失衡。
仅插入数据
其实插入部分和我们之前写的一样,只不过要注意的是,我们的值存的是pair对象,要像拿到K值需要 _kv.first拿到K值。一定要遵循二叉搜索树的规律,左子树比根小,右子树比根大。
if (_root == nullptr){//插入第一个值Node* newNode = new Node(kv);_root = newNode;return true;}Node* newNode = new Node(kv);Node* cur = _root;Node* parent = cur;while (cur){if (cur->_kv.first < newNode->_kv.first){//大于在右边parent = cur;cur = cur->_right;}else if (cur->_kv.first > newNode->_kv.first){//小于在左边parent = cur;cur = cur->_left;}else{//等于,二叉搜索树不允许冗余,所以直接返回false。return false;}}if (parent->_kv.first > newNode->_kv.first){//在左边parent->_left = newNode;}else{//在右边parent->_right = newNode;}newNode->_parent = parent;
调节平衡因子
情况1:插入在父亲的右边,父亲的平衡因子++后为0
红色是我插入的数,插入后,它的parent:12从-1加加后变成了0,我们还需要向上更新吗?答案是不需要向上更新,为什么?0表示左右子树的高度差为0,也就说高度没有变,所以我不需要再向上更新。
解释:平衡因子原来是1/-1都表示这个树缺了一个节点,当我们插入之后正好填上了这个节点,但是高度并不变。看图!我把15插入,右子树这个高度没有变。
情况2:插入在父亲的左边,父亲的平衡因子–后为0
当我插入在左边,那我们就需要给父亲的因子bf–。
情况3:插入左或者右,恰巧父亲不是0,是-1/1
父亲的平衡因子==1/-1,父亲所在子树高度变了。高度变了继续向上更新。
情况4:当父亲的平衡因子==-2/2,不需要在更新了,证明不平衡了,需要旋转。
这个时候就不满足AVL树的规则了,就需要旋转。
左边高,右旋
这个树就是左边高的情况,你会发现parent为-2,cur为-1,这个情况就是左边比右边要高,那么我们需要右旋。
右旋的口诀:把cur的右边给parent的左边,cur的右边链接parent,在让parent的parent链接cur。会发现我们把原来的parent变成了cur的右边。并且现在是平衡的。
其实为什么要把cur的右边给parent呢?是因为cur的右边时当前树最大的值,cur给了parent链接到左边,依然不会破坏二叉搜索树的规则。
void RotateR(Node* parent )
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}parent->_bf = subL->_bf = 0;
}
右边高,左旋
当我们插入红色节点的时候就会导致右边过高,那么我们就需要左旋。
左旋的口诀:让cur的左边给parent的右边链接,然后让父亲变成cur的左边。
解释:为什么要把cur左边的值给parent的右边?cur当前位置是parent的右边,cur的左边也是比parent大的值,给parent的右边依然不影响二叉搜索树的规则。
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if(subRL)subRL->_parent = parent;subR->_left = parent;Node* pphead = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (pphead->_parent == parent){pphead->_right = subR;}else{pphead->_left = subR;}subR->_parent = pphead;}subR->_bf = parent->_bf = 0;
}
双旋转
还有一种情况,它并不是单纯的一边高,用单旋并不能解决问题。当我们仅仅只是单旋会发现他有变成了形态不同,但是问题一样的情况,这个时候就需要双旋。
右左旋转:先右旋然后左旋。
既然我们单旋解决不了问题,我们可以把他变成一边高,然后进行单旋。
如这个图,我们可以对subR进行右旋后,在对parent左旋就可以了。
右左双旋后:
但是有个问题,平衡因子我们应该如何更新?你可能会说这种情况不是正好满足左旋/右旋后平衡因子变成0吗?但是如果其他位置都有节点呢?接下来我们抽象图,给大家演示一下。
当我们插入节点的位置不同,你会发现每一个平衡因子也不一样。如图:我插在了右边。这个时候我通过右左双旋就会得到下图。如最右图的红色平衡因子,应该变成这个情况。所以说插入的位置不同,平衡因子也会不同。
如图:假如我插在了左边。会发现平衡因子又不一样了。所以我们并不能用一个例子来概括所有。
那么我们应该怎么做呢?其实从我们画图,你会发现我们改变的只不过是parent 、subR、sunRL这三个节点的因子,其他的并没有改变。并且,跟subRL的因子有密切关系,所以我们只需要判断subRL的因子是0/1/-1就能修改他们三个因子。
void RotateRL(Node* parent){//右左双旋//平衡因子需要自己调节Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);if (bf == 0){//新插入的parent->_bf =0;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 1){//右边插入的subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if(bf==-1){//在左边插入的parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else{assert(false);}}
左右双旋:先左旋,在右旋。
图下是:左右双旋。
当然了,我们依然需要分析平衡因子,所以我们依然要分析插入在哪个位置。
如图:当插入在右边。
如图:当我们插在左边。
void RotateLR(Node* parent){//左右双旋//平衡因子需要单独调节Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;//先左旋在右旋RotateL(parent->_left);RotateR(parent);//重新计算调节因子if (bf == 0){//当且节点就是新插的subL->_bf = 0;parent->_bf = 0;subLR->_bf = 0;}else if(bf==1){//在当前节点的右边插入的parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if(bf == -1){subL->_bf = 0;parent->_bf = 1;subLR->_bf = 0;}else{assert(false);}}
中序遍历
因为二叉搜索树我们都是中序遍历,因为中序遍历更接近有序。
void _Inorder(Node* root)
{if (root == nullptr){return;}_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second<<endl;_Inorder(root->_right);
}
判断是否平衡
什么时候不平衡?是不是因子大于等于2的时候,所以我们需要算左右树的高度相减,如果超过2,那就是不平衡。
bool _isBalance(Node* root){if (root == nullptr){return true;}int left = _Height(root->_left);int right = _Height(root->_right);if (abs(left - right) >= 2) return false;//因子大于等于2的时候不平衡return _isBalance(root->_left) && _isBalance(root->_right);}int _Height(Node* root){if (root == nullptr)return 0;int left = _Height(root->_left);int right = _Height(root->_right);return max(left, right) + 1;}
AVL树整体代码
以下是整个AVL树所有的代码?
问题:为什么没有像库一样写删除呢?答:我们模拟实现其实是为了了解底层,并不是要超过底层,因为现有的库已经很好了,我们没必要写一个。二叉搜索树很少用删除接口。所以这里没有实现。
#pragma once
#include<iostream>
#include<vector>
#include<string>
#include<assert.h>using namespace std;
namespace KV
{template<class K,class V>struct AVLTreeNode{AVLTreeNode(const pair<K, V> kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;pair<K, V> _kv;int _bf;};template<class K,class V>class AVLTree{public:typedef AVLTreeNode<K,V> Node;bool insert(const pair<K, V> kv){if (_root == nullptr){//插入第一个值Node* newNode = new Node(kv);_root = newNode;return true;}Node* newNode = new Node(kv);Node* cur = _root;Node* parent = cur;while (cur){if (cur->_kv.first < newNode->_kv.first){//大于在右边parent = cur;cur = cur->_right;}else if (cur->_kv.first > newNode->_kv.first){//小于在左边parent = cur;cur = cur->_left;}else{//等于,二叉搜索树不允许冗余,所以直接返回false。return false;}}if (parent->_kv.first > newNode->_kv.first){//在左边parent->_left = newNode;}else{//在右边parent->_right = newNode;}newNode->_parent = parent;cur = newNode;while (parent){if (parent->_left == cur){cur->_parent->_bf--;}else if (parent->_right == cur){cur->_parent->_bf++;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//出现健康问题需要旋转//左边高,右旋转if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//右边高,左旋转else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){//左右双旋RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//右左双旋RotateRL(parent);}else{assert(false);}}else{//按道理说不可能有这种情况,但是保不准会有bugassert(false);break;}}return true;}void Inorder(){_Inorder(_root);}int Height(){return _Height(_root);}bool isBalance(){return _isBalance(_root);}private:int _Height(Node* root){if (root == nullptr)return 0;int left = _Height(root->_left);int right = _Height(root->_right);return max(left, right) + 1;}bool _isBalance(Node* root){if (root == nullptr){return true;}int left = _Height(root->_left);int right = _Height(root->_right);if (abs(left - right) >= 2) return false;return _isBalance(root->_left) && _isBalance(root->_right);}void RotateRL(Node* parent){//右左双旋//平衡因子需要自己调节Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);if (bf == 0){//新插入的parent->_bf =0;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 1){//右边插入的subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if(bf==-1){//在左边插入的parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else{assert(false);}}void RotateLR(Node* parent){//左右双旋//平衡因子需要单独调节Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;//先左旋在右旋RotateL(parent->_left);RotateR(parent);//重新计算调节因子if (bf == 0){//当且节点就是新插的subL->_bf = 0;parent->_bf = 0;subLR->_bf = 0;}else if(bf==1){//在当前节点的右边插入的parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if(bf == -1){subL->_bf = 0;parent->_bf = 1;subLR->_bf = 0;}else{assert(false);}}void RotateR(Node* parent ){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}parent->_bf = subL->_bf = 0;}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if(subRL)subRL->_parent = parent;subR->_left = parent;Node* pphead = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (pphead->_parent == parent){pphead->_right = subR;}else{pphead->_left = subR;}subR->_parent = pphead;}subR->_bf = parent->_bf = 0;}void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second<<" 因子:" <<root->_bf<< endl;_Inorder(root->_right);}Node* _root=nullptr;};
}
相关文章:

AVL树的模拟实现
我们上期提到了二叉搜索树,只是简单的讲了一下原理,那么今天我们就讲一下AVL树。 目录 AVL树的概念AVL树的实现AVL树的架构insert插入引用pair对象引进parent指针仅插入数据调节平衡因子情况1:插入在父亲的右边,父亲的平衡因子后…...

php 一个数组中的元素是否在一个字符串中包含
php 一个数组中的元素是否在一个字符串中包含 要检查一个数组中的元素是否在一个字符串中出现,你可以使用strpos()函数。这个函数返回子字符串首次出现的位置索引,如果没有找到,它会返回false。 $array [apple, banana, cherry]; $string …...

conda修改环境名称后,无法安装包,显示no such file
1问题描述 原本创建环境时设置的名字不太合适,但是因为重新创建环境很麻烦,安装很多包。。所以想直接对包名进行修改,本人采用的方式是直接找到conda环境的文件目录,然后修改文件名,简单粗暴。确实修改成功了…...

linux安装mysql【linux】
linux安装mysql【linux】 前言版权推荐CentOS7.9安装mysql8.0【linux】yum安装rpm安装 最后 前言 2024-5-13 15:52:22 以下内容源自《【linux】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://jss…...

C 语言实例 - 表格形式输出数据
将 1~100 的数据以 10x10 矩阵格式输出。 #include <stdio.h>int main() {int i, j, count;for(i 1; i < 10; i) {for(j i; j <100; j 10 )printf(" %3d", j);printf("\n");}return 0; }运行结果: 1 11 21 31 41 51 61 …...
markdown语法保存
这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…...

数据结构(八)二叉树、哈希查找
文章目录 一、树(一)概念1. 前序遍历:根左右2. 中序遍历:左根右3. 后序遍历:左右根4. 层序遍历:需要借助队列实现 (二)代码实现:二叉树1. 结构体定义2. 创建二叉树1. 注意…...

uniApp 创建Android.keystore证书IOS的证书
Android 证书 1、安装JRE环境 可从Oracle官方下载jre安装包:https://www.oracle.com/technetwork/java/javase/downloads/index.html 打开命令行(cmd),输入以下命令: //切换工作目录到f:路径 D: //将jre命令添加到…...

怎么藏族翻译中文在线翻译?更好地了解藏族文化
怎么藏族翻译中文在线翻译?着全球化的发展,语言交流的重要性日益凸显。藏族,作为中国的一个古老而神秘的民族,其语言对于很多人来说充满了神秘感。然而,在今天的数字化时代,我们有了更多的工具来打破语言壁…...

模拟集成电路(5)----单级放大器(共栅级)
模拟集成电路(5)----单级放大器(共栅级) 有一些场合需要一些小的输入电阻(电流放大器) 大信号分析 − W h e n V i n ≥ V B − V T H ∙ M 1 i s o f f , V o u t V D D − F o r L o w e r V i n I d 1 2 μ n C o x W L ( V…...

学习笔记——数据通信基础——数据通信网络(网络工程师)
网络工程师 网络工程,就是围绕着网络进行的一系列的活动,包括∶网络规划、设计、实施、调试、排错等。网络工程设计的知识领域很宽广,其中路由和交换是计算机网络的基本。 网络工程师∶是在网络工程领域,掌握专业的网络技术&…...

将本地项目上传到 gitee 仓库
1、创建 gitee 仓库 到 gitee 官网,新建仓库 配置新建仓库 完成仓库的创建 项目上传到仓库 上传项目需要安装git git官方下载地址:git下载地址 安装完成,前往本地项目所在文件夹,右击选择 Git Bash Here 刚下载完成需要配置G…...

Django学习
1.pycharm社区版创建django PyCharm社区版如何创建Django项目并运行_pycharm社区版打开django-CSDN博客 2.Django TemplateDoesNotExist: rest_framework 当我们使用djangorestframework框架时,首先下载pip install djangorestframework 参考博文Django Templat…...

说唱程序员
Yo yo yo,这里是代码的战场,程序员的秀场, 键盘敲击声,是我们的节奏响亮。 夜深人静时,我们与Bug正面刚, 调试、优化,每一行代码都得刚强。 我们不懂数理化,只是喜欢瞎搞哈…...

058.最后一个单词的长度
题意 给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 难度 简单 示例 1: 输入:s "Hello World" 输…...

决策树模型-预测用户是否购买某母婴产品
1,场景描述 假设我们是京东的数据分析师,负责分析母婴产品的购买行为。我们想预测用户是否会购买一款新上线的母婴产品。为了进行预测,我们将利用用户的历史购买数据、浏览行为和其他特征,通过决策树模型进行分析,并提…...

工具使用-网络性能测试工具(iperf)-TCP 和 UDP 的吞吐量-包转发率参数的理解
时间戳:2024年5月26日15:18:39 iperf 和 netperf 都是最常用的网络性能测试工具,测试 TCP 和 UDP 的吞吐量。它们都以客户端和服务器通信的方式,测试一段时间内的平均吞吐量。 接下来,我们就以 iperf 为例,看一下 TC…...

什么是JS引擎
JS引擎(JavaScript引擎)是负责在浏览器或Node.js等环境中解析和执行JavaScript代码的软件组件。它是JavaScript运行时的核心,将JavaScript代码转换为机器语言,使其能够在计算机上执行。 不同的浏览器和运行环境使用不同的JS引擎。…...

前端手写文件上传;使用input实现文件拖动上传
使用input实现文件拖动上传 vue2代码: <template><div><div class"drop-area" dragenter"highlight" dragover"highlight" dragleave"unhighlight" drop"handleDrop"click"handleClick&quo…...

Flutter 中的 PhysicalModel 小部件:全面指南
Flutter 中的 PhysicalModel 小部件:全面指南 Flutter 的 PhysicalModel 小部件提供了一种简单而高效的方式来给应用添加物理效果,如阴影和层次感。它本质上是一个矩形的 Container,带有圆角边框和可选的阴影,能够模仿真实世界中…...

Flutter 中的 Center 小部件:全面指南
Flutter 中的 Center 小部件:全面指南 在Flutter的世界里,Center是一个简单而强大的布局小部件,它能够将子组件放置在父组件的中心位置。无论是水平中心、垂直中心,还是两者都居中,Center都能轻松实现。本文将详细介绍…...

windows 执行node报错 800A1391
在项目下执行node -v的时候,抛了这个错误,一开始没发现有啥问题 现在一看,这个报错里的node怎么是个文件... 出现这个问题,是因为项目下,有个同名的文件叫node.js,搞得windows一时不知道是想打开node.js文…...

无人机操作界面来了,起点就很高呀。
无人机操作界面设计需要考虑以下几个方面: 易用性:无人机操作界面应该简单直观,易于操作和理解。操作按钮和控键应该布局合理,易于触摸或点击。重要的操作功能应该易于找到和使用,避免用户迷失或困惑。实时反馈&#…...

Android 11 AudioPolicyService 启动流程
AudioPolicyService在init进程中启动,源码路径:frameworks/av/media/audioserver/audioserver.rc service audioserver /system/bin/audioserverclass coreuser audioserver# media gid needed for /dev/fm (radio) and for /data/misc/media (tee)grou…...

java中static关键字面试五连问
抽象(abstract)方法是否可同时是静态的(static)? 抽象方法本来将来就是要被重写的,而静态方法不能被重写,所以是错误的 是否可以从一个静态(static)方法内部发出对非静态方法的调…...

基于文本来推荐相似酒店
基于文本来推荐相似酒店 查看数据集基本信息 import pandas as pd import numpy as np from nltk.corpus import stopwords from sklearn.metrics.pairwise import linear_kernel from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extrac…...

红队内网攻防渗透:内网渗透之前置知识外网权限提升技术
红队内网攻防渗透 1. 内网权限提升技术1.1 外网权限提升的思路-前置知识1.1.1 外网权限提升知识点:1.1.2 外网权限提升基础内容1.1.2.1 为什么我们要学习权限提升转移技术:1.1.2.2 具体有哪些权限需要我们了解掌握的:1.1.2.3 以上常见权限获取方法简要归类说明:1.1.2.4 以上…...

【漏洞复现】大华智能物联综合管理平台 log4j远程代码执行漏洞
0x01 产品简介 大华ICC智能物联综合管理平台对技术组件进行模块化和松耦合,将解决方案分层分级,提高面向智慧物联的数据接入与生态合作能力。 0x02 漏洞概述 大华ICC智能物联综合管理平台/evo-apigw/evo-brm/1.2.0/user/is-exist 接口处存在 l0g4i远程…...

OrangePi AIpro测评
文章目录 1、外观部分2、系统初探3、AI性能体验4、总结 首先非常感谢csdn以及香橙派能够提供这样一个平台,可以测试OrangePi AIpro这样一块开发板,这块板子给我的感觉还是非常不错的,非常适合用来作为嵌入式学习的板子,性能也达到…...

写代码之前一定要提前想好思路
就和写数学题目一样,在做题目之前要先把思路确立下来。可能是我早年做数学的时候老是着急做题目没怎么分析过题目,把这个习惯不自觉地代入了代码的写入当中。习惯的养成使得我即使明白了自己的问题也依然会不断的犯错,看来只有刻意地提醒自己…...