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,带有圆角边框和可选的阴影,能够模仿真实世界中…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...