C++从入门到起飞之——AVL树 全方位剖析!

C++从入门到起飞之——AVL树 全方位剖析!

🌈个人主页:秋风起,再归来~
🔥系列专栏:C++从入门到起飞          
🔖克心守己,律己则安

目录

1. AVL的概念

2. AVL树的实现

 2.1 AVL树的结构

 2.2 AVL树的插⼊

>AVL树插⼊⼀个值的⼤概过程

>平衡因⼦更新

>插⼊结点及更新平衡因⼦的代码实现

2.3 旋转

2.3.1 旋转的原则

2.3.2 右单旋

 2.3.3 右单旋代码实现

2.3.4 左单旋

2.3.5 左单旋代码实现 

2.3.6 左右双旋

2.3.7左右双旋代码实现

2.3.8 右左双旋

2.3.9 右左双旋代码实现

2.4 AVL树的查找

2.5 AVL树平衡检测

3. 源码

4、完结散花


1. AVL的概念

• AVL树是最先发明的⾃平衡⼆叉查找树,AVL是⼀颗空树,或者具备下列性质的⼆叉搜索树:它的 左右⼦树都是AV树,且左右⼦树的⾼度差的绝对值不超过1。AVL树是⼀颗⾼度平衡搜索⼆叉树, 通过控制⾼度差去控制平衡。

• AVL树得名于它的发明者G.M.Adelson-Velsky和E.M.Landis是两个前苏联的科学家,他们在1962 年的论⽂《An algorithm or the organization of information》中发表了它。

• AVL树实现这⾥我们引⼊⼀个平衡因⼦(balance factor)的概念,每个结点都有⼀个平衡因⼦,任何 结点的平衡因⼦等于右⼦树的⾼度减去左⼦树的⾼度,也就是说任何结点的平衡因⼦等于0/1/-1, AVL树并不是必须要平衡因⼦,但是有了平衡因⼦可以更⽅便我们去进⾏观察和控制树是否平衡, 就像⼀个⻛向标⼀样。

• 思考⼀下为什么AVL树是⾼度平衡搜索⼆叉树,要求⾼度差不超过1,⽽不是⾼度差是0呢?0不是更 好的平衡吗?画画图分析我们发现,不是不想这样设计,⽽是有些情况是做不到⾼度差是0的。⽐ 如⼀棵树是2个结点,4个结点等情况下,⾼度差最好就是1,⽆法作为⾼度差是0

• AVL树整体结点数量和分布和完全⼆叉树类似,⾼度可以控制在 ,那么增删查改的效率也可 以控制在,相⽐⼆叉搜索树有了本质的提升。

2. AVL树的实现

 2.1 AVL树的结构

节点的结构:

template<class K,class V> struct AVLTreeNode { pair<K, V> _kv; AVLTreeNode* _parent; AVLTreeNode* _right; AVLTreeNode* _left; int _bf;//平衡因子 AVLTreeNode(const pair<K,V>& kv) :_kv(kv) , _parent(nullptr) , _right(nullptr) ,_left(nullptr) ,_bf(0) {} };

树的结构:

template<class K, class V> class AVLTree { public: typedef AVLTreeNode<K,V> Node; //...... private: Node* _root=nullptr; };

 2.2 AVL树的插⼊

>AVL树插⼊⼀个值的⼤概过程

1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊。

2. 新增结点以后,只会影响祖先结点的⾼度,也就是可能会影响部分祖先结点的平衡因⼦,所以更新 从新增结点->根结点路径上的平衡因⼦,实际中最坏情况下要更新到根,有些情况更新到中间就可 以停⽌了,具体情况我们下⾯再详细分析。

3. 更新平衡因⼦过程中没有出现问题,则插⼊结束

4. 更新平衡因⼦过程中出现不平衡,对不平衡⼦树旋转,旋转后本质调平衡的同时,本质降低了⼦树 的⾼度,不会再影响上⼀层,所以插⼊结束

>平衡因⼦更新

更新原则:

• 平衡因⼦=右⼦树⾼度-左⼦树⾼度

• 只有⼦树⾼度变化才会影响当前结点平衡因⼦。

• 插⼊结点,会增加⾼度,所以新增结点在parent的右⼦树,parent的平衡因⼦++,新增结点在 parent的左⼦树,parent平衡因⼦--

• parent所在⼦树的⾼度是否变化决定了是否会继续往上更新

更新停⽌条件:

• 更新后parent的平衡因⼦等于0,更新中parent的平衡因⼦变化为-1->0或者1->0,说明更新前 parent⼦树⼀边⾼⼀边低,新增的结点插⼊在低的那边,插⼊后parent所在的⼦树⾼度不变,不会 影响parent的⽗亲结点的平衡因⼦,更新结束。

• 更新后parent的平衡因⼦等于1或-1,更新前更新中parent的平衡因⼦变化为0->1或者0->-1,说 明更新前parent⼦树两边⼀样⾼,新增的插⼊结点后,parent所在的⼦树⼀边⾼⼀边低,parent所 在的⼦树符合平衡要求,但是⾼度增加了1,会影响arent的⽗亲结点的平衡因⼦,所以要继续向上 更新。

• 更新后parent的平衡因⼦等于2或-2,更新前更新中parent的平衡因⼦变化为1->2或者-1->-2,说 明更新前parent⼦树⼀边⾼⼀边低,新增的插⼊结点在⾼的那边,parent所在的⼦树⾼的那边更⾼ 了,破坏了平衡,parent所在的⼦树不符合平衡要求,需要旋转处理,旋转的⽬标有两个:

1、把 parent⼦树旋转平衡。

2、降低parent⼦树的⾼度,恢复到插⼊结点以前的⾼度。所以旋转后也不 需要继续往上更新,插⼊结束。

>插⼊结点及更新平衡因⼦的代码实现

 bool insert(const pair<K, V>& kv) { //如果树为空,直接在根插入 if (_root == nullptr) { _root = new Node(kv); return true; } //树不为空,先按照搜索树规则找到插入位置 Node* parent = nullptr; Node* cur = _root; while (cur) { //插入的key小就往左走 if (kv.first < cur->_kv.first) { parent = cur; cur = cur->_left; } //大就往右走 else if (kv.first > cur->_kv.first) { parent = cur; cur = cur->_right; } else//不支持键值冗余 { return false; } } //找到在parent插入的位置了 cur = new Node(kv); if (kv.first < parent->_kv.first) parent->_left = cur; else parent->_right = cur; //不要忘记链接新增节点的parent cur->_parent = parent; //开始更新平衡因子 while (parent) { if (parent->_left == cur) parent->_bf--; else parent->_bf++; //_bf从1或-1到0,不会影响祖先节点 if (parent->_bf == 0) { break; } //_bf从0到1或-1,会影响祖先节点,继续向上更新 else if (parent->_bf == 1 || parent->_bf == -1) { cur = parent; parent = parent->_parent; } //平衡破坏,旋转恢复平衡 else if (parent->_bf == 2 || parent->_bf == -2) { //旋转逻辑 //........ break;//旋转完后,该节点的平衡因子为0,无需向上更新 } else//非预想平衡因子,直接断死 { assert(false); } } return true; }

2.3 旋转

2.3.1 旋转的原则

1. 保持搜索树的规则

2. 让旋转的树从不满⾜变平衡,其次降低旋转树的⾼度 旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。

说明:下⾯的图中,有些结点我们给的是具体值,如10和5等结点,这⾥是为了⽅便讲解,实际中是什 么值都可以,只要⼤⼩关系符合搜索树的规则即可。

2.3.2 右单旋

具象图

抽象图 

 2.3.3 右单旋代码实现

//右单旋 void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; Node* pParent = parent->_parent; parent->_left = subLR; if(subLR)//如果不为空 subLR->_parent = parent; subL->_right = parent; parent->_parent = subL; if (pParent == nullptr) { _root = subL; subL->_parent = nullptr; } else { if (pParent->_left == parent) { pParent->_left = subL; } else { pParent->_right = subL; } subL->_parent = pParent; } parent->_bf = subL->_bf = 0; }

2.3.4 左单旋

具象图

抽象图  

2.3.5 左单旋代码实现 

//左单旋 void RotateL(Node* parent) { Node* pParent = parent->_parent; Node* subR = parent->_right; Node* subRL = subR->_left; subR->_left = parent; parent->_parent = subR; parent->_right = subRL; if (subRL) subRL->_parent = parent; if (pParent == nullptr) { _root = subR; subR->_parent = nullptr; } else { if (pParent->_left == parent) { pParent->_left = subR; } else { pParent->_right = subR; } subR->_parent = pParent; } parent->_bf = subR->_bf = 0; }

2.3.6 左右双旋

具象图

 抽象图  

2.3.7左右双旋代码实现

//左右双旋 void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(parent->_left); RotateR(parent); if (bf == -1) { subL->_bf = 0; parent->_bf = 1; subLR->_bf = 0; } else if(bf == 1) { subL->_bf = -1; parent->_bf = 0; subLR->_bf = 0; } else if (bf == 0) { subL->_bf = 0; parent->_bf = 0; subLR->_bf = 0; } else { assert(false); } }

2.3.8 右左双旋

2.3.9 右左双旋代码实现

//右左双旋 void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(parent->_right); RotateL(parent); if (bf == -1) { subR->_bf = 1; parent->_bf = 0; subRL->_bf = 0; } else if (bf == 1) { subR->_bf = 0; parent->_bf = -1; subRL->_bf = 0; } else if (bf == 0) { subR->_bf = 0; parent->_bf = 0; subRL->_bf = 0; } else { assert(false); } }

2.4 AVL树的查找

按⼆叉搜索树逻辑实现即可,搜索效率为O(logN)

//查找 Node* find(const K& key) { Node* cur = _root; while (cur) { if (cur->_kv.first == key) { return cur; } else if (key < cur->_kv.first) { cur = cur->_left; } else { cur = cur->_right; } } return nullptr; }

2.5 AVL树平衡检测

我们实现的AVL树是否合格,我们通过检查左右⼦树⾼度差的的程序进⾏反向验证,同时检查⼀下结点 的平衡因⼦更新是否出现了问题。

//中序遍历 Node* _Inorder(Node* root) { if (root == nullptr) return nullptr; _Inorder(root->_left); cout << "{" << root->_kv.first << "," << root->_kv.second << "}" << endl; _Inorder(root->_right); return root; }
//计算树的高度 int _Height(Node* root) { if (root == nullptr) return 0; int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; }
//计算节点的数量 int _Size(Node* root) { if (root == nullptr) return 0; int CountL = _Size(root->_left); int CountR = _Size(root->_right); return CountL + CountR + 1; }
//判断是否是AVL树 bool _IsBalanceTree(Node* root) { //空树也是AVL树 if (root == nullptr) return true; int LHeight = _Height(root->_left); int RHeight = _Height(root->_right); int ret = RHeight - LHeight; if (abs(ret) >= 2) { cout << root->_kv.first << "高度差异常" << endl << "高度差为:" << ret << endl; return false; } if (ret != root->_bf) { cout << root->_kv.first << "平衡因子异常" << endl; return false; } return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right); }
#include"AVLTree.h" #include<vector> void TestRotate() { AVLTree<int, int> t; // 常规的测试⽤例 //int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 }; // 特殊的带有双旋场景的测试⽤例 int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 }; for (auto e : a) { t.insert({ e,e }); } t.Inorder(); cout << t.IsBalanceTree() << endl; } void TestTreeBalance() { const int N = 1000; srand((unsigned int)time(nullptr)); AVLTree<int, int> t; vector<int> v; for (int i = 0; i < N; i++) { v.push_back(rand() + i); } for (auto e : v) { t.insert({ e,e }); } cout << t.Height() << endl;; cout << t.Size() << endl; cout << t.IsBalanceTree() << endl; } int main() { //TestRotate(); TestTreeBalance(); return 0; }

3. 源码

#pragma once #include<assert.h> #include<iostream> using namespace std; template<class K,class V> struct AVLTreeNode { pair<K, V> _kv; AVLTreeNode* _parent; AVLTreeNode* _right; AVLTreeNode* _left; int _bf;//平衡因子 AVLTreeNode(const pair<K,V>& kv) :_kv(kv) , _parent(nullptr) , _right(nullptr) ,_left(nullptr) ,_bf(0) {} }; template<class K, class V> class AVLTree { public: typedef AVLTreeNode<K,V> Node; //插入 bool insert(const pair<K, V>& kv) { //如果树为空,直接在根插入 if (_root == nullptr) { _root = new Node(kv); return true; } //树不为空,先按照搜索树规则找到插入位置 Node* parent = nullptr; Node* cur = _root; while (cur) { //插入的key小就往左走 if (kv.first < cur->_kv.first) { parent = cur; cur = cur->_left; } //大就往右走 else if (kv.first > cur->_kv.first) { parent = cur; cur = cur->_right; } else//不支持键值冗余 { return false; } } //找到在parent插入的位置了 cur = new Node(kv); if (kv.first < parent->_kv.first) parent->_left = cur; else parent->_right = cur; //不要忘记链接新增节点的parent cur->_parent = parent; //开始更新平衡因子 while (parent) { if (parent->_left == cur) parent->_bf--; else parent->_bf++; //_bf从1或-1到0,不会影响祖先节点 if (parent->_bf == 0) { break; } //_bf从0到1或-1,会影响祖先节点,继续向上更新 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 && parent->_left->_bf == -1) { RotateR(parent); } //纯粹右边高进行左单旋 else if (parent->_bf == 2 && parent->_right->_bf == 1) { RotateL(parent); } //不纯粹左边高进行左右双旋 else if (parent->_bf == -2 && parent->_left->_bf == 1) { RotateLR(parent); } //不纯粹右边高进行右左双旋 else if (parent->_bf == 2 && parent->_right->_bf == -1) { RotateRL(parent); } break;//旋转完后,该节点的平衡因子为0,无需向上更新 } else//非预想平衡因子,直接断死 { assert(false); } } return true; } //查找 Node* find(const K& key) { Node* cur = _root; while (cur) { if (cur->_kv.first == key) { return cur; } else if (key < cur->_kv.first) { cur = cur->_left; } else { cur = cur->_right; } } return nullptr; } //中序遍历 void Inorder() { _Inorder(_root); } //计算树的高度 int Height() { return _Height(_root); } //计算树的节点个数 int Size() { return _Size(_root); } //判断是否是AVL树 bool IsBalanceTree() { return _IsBalanceTree(_root); } private: //右单旋 void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; Node* pParent = parent->_parent; parent->_left = subLR; if(subLR)//如果不为空 subLR->_parent = parent; subL->_right = parent; parent->_parent = subL; if (pParent == nullptr) { _root = subL; subL->_parent = nullptr; } else { if (pParent->_left == parent) { pParent->_left = subL; } else { pParent->_right = subL; } subL->_parent = pParent; } parent->_bf = subL->_bf = 0; } //左单旋 void RotateL(Node* parent) { Node* pParent = parent->_parent; Node* subR = parent->_right; Node* subRL = subR->_left; subR->_left = parent; parent->_parent = subR; parent->_right = subRL; if (subRL) subRL->_parent = parent; if (pParent == nullptr) { _root = subR; subR->_parent = nullptr; } else { if (pParent->_left == parent) { pParent->_left = subR; } else { pParent->_right = subR; } subR->_parent = pParent; } parent->_bf = subR->_bf = 0; } //左右双旋 void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(parent->_left); RotateR(parent); if (bf == -1) { subL->_bf = 0; parent->_bf = 1; subLR->_bf = 0; } else if(bf == 1) { subL->_bf = -1; parent->_bf = 0; subLR->_bf = 0; } else if (bf == 0) { subL->_bf = 0; parent->_bf = 0; subLR->_bf = 0; } else { assert(false); } } //右左双旋 void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(parent->_right); RotateL(parent); if (bf == -1) { subR->_bf = 1; parent->_bf = 0; subRL->_bf = 0; } else if (bf == 1) { subR->_bf = 0; parent->_bf = -1; subRL->_bf = 0; } else if (bf == 0) { subR->_bf = 0; parent->_bf = 0; subRL->_bf = 0; } else { assert(false); } } //中序遍历 Node* _Inorder(Node* root) { if (root == nullptr) return nullptr; _Inorder(root->_left); cout << "{" << root->_kv.first << "," << root->_kv.second << "}" << endl; _Inorder(root->_right); return root; } //计算树的高度 int _Height(Node* root) { if (root == nullptr) return 0; int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; } //计算节点的数量 int _Size(Node* root) { if (root == nullptr) return 0; int CountL = _Size(root->_left); int CountR = _Size(root->_right); return CountL + CountR + 1; } //判断是否是AVL树 bool _IsBalanceTree(Node* root) { //空树也是AVL树 if (root == nullptr) return true; int LHeight = _Height(root->_left); int RHeight = _Height(root->_right); int ret = RHeight - LHeight; if (abs(ret) >= 2) { cout << root->_kv.first << "高度差异常" << endl << "高度差为:" << ret << endl; return false; } if (ret != root->_bf) { cout << root->_kv.first << "平衡因子异常" << endl; return false; } return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right); } private: Node* _root=nullptr; };

4、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​

​​

Read more

Spring Boot 消息队列与异步通信

Spring Boot 消息队列与异步通信

Spring Boot 消息队列与异步通信 21.1 学习目标与重点提示 学习目标:掌握Spring Boot消息队列与异步通信的核心概念与使用方法,包括消息队列的定义与特点、Spring Boot与ActiveMQ的集成、Spring Boot与RabbitMQ的集成、Spring Boot与Kafka的集成、Spring Boot异步通信的基本方法、Spring Boot的实际应用场景,学会在实际开发中处理消息队列与异步通信问题。 重点:消息队列的定义与特点、Spring Boot与ActiveMQ的集成、Spring Boot与RabbitMQ的集成、Spring Boot与Kafka的集成、Spring Boot异步通信的基本方法、Spring Boot的实际应用场景。 21.2 消息队列概述 消息队列是Java开发中的重要组件。 21.2.1 消息队列的定义 定义:消息队列是一种异步通信机制,用于在应用程序之间传递消息。 作用: * 实现应用程序之间的异步通信。 * 实现应用程序之间的解耦。 * 提高应用程序的性能。 常见的消息队列: * Activ

By Ne0inhk
【案例实战】鸿蒙分布式智能办公应用的架构设计与性能优化

【案例实战】鸿蒙分布式智能办公应用的架构设计与性能优化

目录 一、项目背景与挑战 项目概述 1.1 面临的技术挑战 二、分布式架构设计 2.1 整体架构概览 2.2 组件化设计 2.3 分布式通信机制 三、性能优化实战 3.1 UI渲染优化 3.1.1 虚拟列表实现 3.1.2 懒加载和预加载策略 3.2 内存管理优化 3.2.1 内存泄漏检测与修复 3.2.2 对象池与资源复用 3.3 启动性能优化 四、鸿蒙开放能力接入 4.1 云开发能力集成

By Ne0inhk
Flutter 组件 flutterw_sidekick_plugin 适配鸿蒙 HarmonyOS 实战:侧翼脚手架扩展,构建工程自动化与环境一致性治理架构

Flutter 组件 flutterw_sidekick_plugin 适配鸿蒙 HarmonyOS 实战:侧翼脚手架扩展,构建工程自动化与环境一致性治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 flutterw_sidekick_plugin 适配鸿蒙 HarmonyOS 实战:侧翼脚手架扩展,构建工程自动化与环境一致性治理架构 前言 在鸿蒙(OpenHarmony)生态迈向大规模团队协作、涉及多分支并行开发及复杂的 SDK 版本管控的背景下,如何确保每一位开发者的本地构建环境(Flutter/Dart SDK)与生产基准完全对齐,已成为保障项目交付质量的“工程定海神针”。在鸿蒙设备这类强调定制化编译工具链与私有插件依赖的环境下,如果团队缺乏统一的脚手架工具,由于由于本地 SDK 版本的微小代差(如空安全检测差异),极易由于由于“环境不一致”导致代码在不同机器上产生不可预知的编译崩溃。 我们需要一种能够深度集成 Sidekick、支持自定义命令扩展且具备“强制版本锁死”能力的脚手架治理方案。 flutterw_sidekick_plugin 为 Flutter 开发者引入了基于 Sidekick

By Ne0inhk
KingbaseES数据库:兼容 SQL 语法及 Oracle 过程化语言的语法基础

KingbaseES数据库:兼容 SQL 语法及 Oracle 过程化语言的语法基础

KingbaseES数据库:兼容 SQL 语法及 Oracle 过程化语言的语法基础 KingbaseES数据库:兼容 SQL 语法及 Oracle 过程化语言的语法基础,KingbaseES 数据库(Oracle 模式)以内核兼容为基础,实现了涵盖内核、工具和接口的全方位 Oracle 兼容,当前 Oracle 常用能力兼容性达 100%,能助力客户应用无感平滑迁移。在基础能力上,它兼容 SQL 语法、Oracle 过程化语言语法基础,覆盖数据类型、伪列、常用表达式、系统视图、内置函数等多方面;高级能力方面,支持 ROWID、BFILE 等特殊类型,DBLink 访问、物化视图等功能,还兼容客户端编程接口及 Oracle XML/JSON 能力,

By Ne0inhk