【高阶数据结构】AVL树:从原理到旋转平衡艺术(附完整代码)

【高阶数据结构】AVL树:从原理到旋转平衡艺术(附完整代码)
🔥拾Ծ光:个人主页👨🏻‍💻

👏👏👏欢迎来到我的专栏:

🎉《C++》

📌《数据结构》

💡《C语言》

🚀《Linux》

前言:

AVL树,又称平衡二叉搜索树,即AVL树是基于二叉搜索树实现的。

如果你对二叉搜索树的结构还不太熟悉,看看这个呢👇️👇️👇️

【数据结构】二叉搜索树C++实现:增删查改全攻略

为什么我们有二叉搜索树还要实现AVL树这样的数据结构呢?原因就是,二叉搜索树当数据有序时或者接近有序时,其结构就会退化为单支或趋近与单支给结构,此时时间复杂度为O(N)。

所以,就出现了AVL树这种更稳定的高效数据结构。因为,AVL树可以在插入数据的同时,可以保持其左右子树的平衡,所以AVL树的高度始终保持 logN,即时间复杂度为O(logN)

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

一、AVL树的概念与性质

AVL树即对于任意结点的左右子树高度差不超过1,这里我们再引入一个概念:平衡因子((balance factor),每个节点都有一个平衡因子,其值等于该节点的左右子树的高度差(右子树高度 - 左子树高度),所以,AVL树任意节点的平衡因子取值为0/-1/1

AVL树性质如下:

1、AVL树可以是空树;

2、AVL树的左右子树仍为AVL树;

3、AVL树任意节点的左右子树高度差不超过1(即 -1<= 平衡因子 <=1)

如图所示:

二、AVL树的节点结构

对于AVL树的节点,根据前面对二叉搜索树的学习,节点中肯定要有一个left指针指向左孩子节点,right指针指向右孩子节点,用一个pair类来存储key(键值)和value(映射值)。

还需要用一个变量来存储上面提到的平衡因子,平衡因子可以帮我们快速地判断当前节点的左右子树高度是否平衡,当平衡因子等于2或-2时,即平衡被破坏。

此外,当我们再调整平衡的过程中需要频繁的访问节点的父亲节点,所以,还需要一个记录父亲节点的指针,即AVL树的结构为一个三叉链的结构,每个节点同时指向其左孩子,右孩子与父亲节点。

说明:我们选择实现key_value类型且数据不冗余(即数据不重复)。

节点结构定义如下:

template<class K,class V> struct AVLTreeNode { pair<K, V> _kv; // 存储键值key与映射值value AVLTreeNode<K, V>* _left; // 指向左孩子节点 AVLTreeNode<K, V>* _right; // 指向右孩子节点 AVLTreeNode<K, V>* _parent; // 指向父亲节点的指针 int _bf; // 记录平衡因子 // 构造 AVLTreeNode(const pair<K, V>& kv) :_kv(kv) ,_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_bf(0) {} };

三、AVL树的插入

AVL树插入节点时,遵循二叉搜索树的规则,即大的往右走,小的往左走。不同的是:当插入节点之后,如果不满足平衡要求,就需要调整数的结构使其任意节点的左右子树高度差不超过1。具体插入节点有以下四种情况:

前两种为:由于节点已经有一个左孩子或者右孩子,所以,插入新节点后高度不发生变化,即树结构的平衡不会被破坏,因此,不需要处理。

后两种为:新节点的父亲节点的左右孩子为空,插入新节点后高度改变,此时如果parent为空,即8为根结点,结构满足平衡条件,就不需要处理;如果parent不为空,那么平衡就有可能被破坏,我们需要向上更新平衡因子来进一步判断,具体又需要分情况讨论。

•  更新到10结点,平衡因子为2,10所在的子树已经不平衡,需要旋转处理

•  更新到中间结点,3为根的子树高度不变,不会影响上一层,更新结束

•  最坏更新到根停止

分析到这里,插入新节点的大概的代码框架我们就可以先搭建起来了,然后,对于需要旋转调整的情况,我们再封装函数来处理。

template<class K,class V> class AVLTree { typedef AVLTreeNode<K, V> Node; public: bool insert(const pair<K, V>& kv) { // 处理空树 if (_root == nullptr) { _root = new Node(kv); return true; } Node* cur = _root; Node* parent = nullptr; // 根据二叉搜索树规则查找新节点插入位置 while (cur) { if (kv.first > cur->_kv.first) { parent = cur; cur = cur->_right; } else if (kv.first < cur->_kv.first) { parent = cur; cur = cur->_left; } else { return false; } } // 插入 cur = new Node(kv); if (kv.first < parent->_kv.first) { parent->_left = cur; } else { parent->_right = cur; } // 链接父亲节点(三叉链) cur->_parent = parent; // 调整平衡因子 while (parent) { if (cur == parent->_left) { parent->_bf--; // 左边插入节点,平衡因子-- } else { parent->_bf++; // 右边插入节点,平衡因子++ } // 父亲节点平衡因子为0,即前两种情况(不需要处理) if (parent->_bf == 0) { break; } // 后两种情况,需要进行向上调整平衡因子 else if (parent->_bf == 1 || parent->_bf == -1) { cur = parent; parent = parent->_parent; } // 当平衡因子为2或-2时,平衡被破坏,开始旋转调整平衡 else if (parent->_bf == 2 || parent->_bf == -2) { // 旋转调整平衡 // ... break; } // 多加一个判断,提高程序的健壮性 else { assert(false); } } return true; } private: Node* _root = nullptr; };

四、AVL树的旋转平衡

4.1、右单旋

对于我们上面提到的后两种情况,我们将AVL树的结构抽象为下图所示,当我们插入节点后,在平衡因子向上更新的过程中,parent节点的平衡因子变成了-2,平衡被破坏,此时SubL节点的平衡因子为-1,即最左边一条路径的高度大于右边,对于这种情况就需要右旋。

即我们需要让parent的_left 指向SubLR,SubL的_right 指向parent,SubL成为子树的根结点,注意链接彼此的父亲节点(即 _parent的指向)。

有以下四点需要注意

• 若parent为根结点,由于旋转后根结点变为SubL,则需要更新_root,且根结点的_parent指针为空。

• parent不是根结点,需要记下parent的父亲节点pParent,防止在SubL链接时找不到对应的父亲节点。且链接时需要注意,原来是pParent 的 _left指向parent,还是_right指向parent。

• SubLR 节点可能为空,若SubLR为空,则不需要SubLR节点链接parent节点,防止对空指针解引用操作。

• 当旋转平衡之后,需要更新parent和SubL 的平衡因子,AVL旋转后平衡则SubL的平衡因子为0,且parent的平衡因子也为0(画图可知)。

代码实现如下:

void RotateR(Node* parent) { Node* SubL = parent->_left; Node* SubLR = SubL->_right; Node* pParent = parent->_parent; // 记下parent的父亲节点 // 旋转(右) parent->_left = SubLR; if (SubLR) // 处理SubLR为空的情况 { SubLR->_parent = parent; // SubLR不为空才链接父亲节点 } SubL->_right = parent; parent->_parent = SubL; // 链接SubL与parent的父亲节点 if (parent == _root) // 处理parent为根结点的情况 { _root = SubL; // 更新根结点的值 SubL->_parent = nullptr; } else { if (parent == pParent->_left) { pParent->_left = SubL; } else { pParent->_right = SubL; } SubL->_parent = pParent; } // 更新平衡因子 SubL->_bf = 0; parent->_bf = 0; }

4.2、左单旋

在平衡因子向上更新的过程中,parent节点的平衡因子变成了 2,平衡被破坏,此时SubR节点的平衡因子为 1,即最右边一条路径的高度大于左边,对于这种情况就需要左旋。

即需要让parent的 _right指向SubRL,让SubR的 _left 指向parent,SubR成为子树的根结点,注意链接彼此的父亲节点(即 _parent的指向)。

代码实现如下(与右单旋完全类似):

void RotateL(Node* parent) { Node* SubR = parent->_right; Node* SubRL = SubR->_left; Node* pParent = parent->_parent; // 旋转(左) parent->_right = SubRL; if (SubRL) { SubRL->_parent = parent; // SubRL不为空才链接父亲节点 } SubR->_left = parent; parent->_parent = SubR; // 链接SubR与parent的父亲节点 if (parent == _root) // 处理为根结点的情况 { _root = SubR; SubR->_parent = nullptr; } else { if (parent == pParent->_left) { pParent->_left = SubR; } else { pParent->_right = SubR; } SubR->_parent = pParent; } // 更新平衡因子 parent->_bf = SubR->_bf = 0; }

4.3、左右双旋

通过观察,不难发现,上面的单旋无非就是最左边一条路径的高度或最右边一条路径的高度破坏了平衡,即一个方向上插入导致高度不满足平衡。相反,双旋则是在不同方向插入节点导致的高度不平衡,此时,parent的平衡因子为 -2,SubL 的平衡因子为 1

这里比较麻烦的是,更新平衡因子分为三种情况:

情况一:新节点为SubLR的左孩子

💦SubL的平衡因子为 0,SubLR的平衡因子为0,parent的平衡因子为1。

情况二:新节点为SubLR 的右孩子

💦SubL的平衡因子为-1,SubLR的平衡因子为0,parent的平衡因子为0。

情况三:SubLR为新节点

💦SubL的平衡因子为0,SubLR的平衡因子为0,parent的平衡因子为0。

代码实现如下:

void RotateLR(Node* parent) { Node* SubL = parent->_left; Node* SubLR = SubL->_right; // 记下SubLR的平衡因子用来区分更新平衡因子的不同情况 int flag = SubLR->_bf; RotateL(SubL); // 先左旋 RotateR(parent); // 后右旋 // 更新平衡因子 if (flag == -1) { parent->_bf = 1; SubL->_bf = 0; SubLR = 0; } else if (flag == 1) { parent->_bf = 0; SubL->_bf = -1; SubLR = 0; } else { parent->_bf = 0; SubL->_bf = 0; SubLR = 0; } }

4.4、右左双旋

右左双旋不同于左右双旋的就是:parent的平衡因子为 2,SubL 的平衡因子为 -1。

更新平衡因子也分为三种情况:

情况一:新节点为SubRL 的左孩子(即SubRL 的平衡因子为 -1)

parent的平衡因子更新为 0,SubR 的平衡因子更新为1,SubRL 的平衡因子更新为0。

情况二:新节点为SubRL 的右孩子(即SubRL 的平衡因子为 1)

parent的平衡因子更新为 -1,SubR 的平衡因子更新为0,SubRL 的平衡因子更新为0。

情况三:新节点为SubRL(即SubRL 的平衡因子为0)

parent的平衡因子更新为 0,SubR 的平衡因子更新为0,SubRL 的平衡因子更新为0。

代码实现如下:

void RotateRL(Node* parent) { Node* SubR = parent->_right; Node* SubRL = SubR->_left; // 记下SubRL的平衡因子用来区分更新平衡因子的不同情况 int flag = SubRL->_bf; RotateR(SubR); RotateL(parent); // 更新平衡因子 if (flag == -1) { parent->_bf = 0; SubR->_bf = 1; SubRL->_bf = 0; } else if (flag == 1) { parent->_bf = -1; SubR->_bf = 0; SubRL->_bf = 0; } else { parent->_bf = 0; SubR->_bf = 0; SubRL->_bf = 0; } }

五、其他接口实现

5.1、中序遍历

void _InOrder(Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_kv.first << ": " << root->_kv.second << endl; _InOrder(root->_right); }

5.2、AVL树的高度

size_t _Height(Node* root) { if (root == nullptr) { return 0; } int heightleft = _Height(root->_left); int heightright = _Height(root->_right); return heightleft > heightright ? heightleft + 1 : heightright + 1; // 需要加上根结点高度1 }

5.3、判断平衡

bool _IsBalanceTree(Node* root) { if (root == nullptr) { return true; } int leftheight = _Height(root->_left); int rightheight = _Height(root->_right); int bf = rightheight - leftheight; // 右子树高度 - 左子树高度 if (abs(bf) >= 2) { cout << "高度异常" << endl; return false; } if(root->_bf != bf) { cout << "平衡因子异常" << endl; return false; } return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right); }

5.4、节点个数

size_t _size(Node* root) { if (root == nullptr) { return 0; } return _size(root->_left) + _size(root->_right) + 1; }

5.5、查找

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

六、完整代码

头文件:AVLTree.h

#include<iostream> #include<assert.h> #include<vector> using namespace std; template<class K,class V> struct AVLTreeNode { pair<K, V> _kv; // 存储键值与映射值 AVLTreeNode<K, V>* _left; // 指向左孩子节点 AVLTreeNode<K, V>* _right; // 指向右孩子节点 AVLTreeNode<K, V>* _parent; // 指向父亲节点的指针 int _bf; // 记录平衡因子 // 构造 AVLTreeNode(const pair<K, V>& kv) :_kv(kv) ,_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_bf(0) {} }; template<class K,class V> class AVLTree { typedef AVLTreeNode<K, V> Node; public: bool Insert(const pair<K, V>& kv) { // 处理空树 if (_root == nullptr) { _root = new Node(kv); return true; } Node* cur = _root; Node* parent = nullptr; // 根据二叉搜索树规则查找新节点插入位置 while (cur) { if (kv.first > cur->_kv.first) { parent = cur; cur = cur->_right; } else if (kv.first < cur->_kv.first) { parent = cur; cur = cur->_left; } else { return false; } } // 插入 cur = new Node(kv); if (kv.first < parent->_kv.first) { parent->_left = cur; } else { parent->_right = cur; } // 链接父亲节点(三叉链) cur->_parent = parent; // 调整平衡因子 while (parent) { if (cur == parent->_left) { parent->_bf--; // 左边插入节点,平衡因子-- } else { parent->_bf++; // 右边插入节点,平衡因子++ } // 父亲节点平衡因子为0,即前两种情况(不需要处理) if (parent->_bf == 0) { break; } // 后两种情况,需要进行向上调整平衡因子 else if (parent->_bf == 1 || parent->_bf == -1) { cur = parent; parent = parent->_parent; } // 当平衡因子为2或-2时,平衡被破坏,开始旋转调整平衡 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); } break; } // 多加一个判断,提高程序的健壮性 else { assert(false); } } return true; } void RotateR(Node* parent) { Node* SubL = parent->_left; Node* SubLR = SubL->_right; Node* pParent = parent->_parent; parent->_left = SubLR; if (SubLR) // 处理SubLR为空的情况 { SubLR->_parent = parent; } SubL->_right = parent; parent->_parent = SubL; // 处理parent为根结点的情况 if (parent == _root) { _root = SubL; SubL->_parent = nullptr; } else { if (parent == pParent->_left) { pParent->_left = SubL; } else { pParent->_right = SubL; } SubL->_parent = pParent; } // 更新平衡因子 SubL->_bf = 0; parent->_bf = 0; } void RotateL(Node* parent) { Node* SubR = parent->_right; Node* SubRL = SubR->_left; Node* pParent = parent->_parent; parent->_right = SubRL; if (SubRL) { SubRL->_parent = parent; } SubR->_left = parent; parent->_parent = SubR; if (parent == _root) { _root = SubR; SubR->_parent = nullptr; } else { if (parent == pParent->_left) { 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; // 记下SubLR的平衡因子用来区分更新平衡因子的不同情况 int flag = SubLR->_bf; RotateL(parent->_left); // 先左旋 RotateR(parent); // 后右旋 // 更新平衡因子 if (flag == -1) { parent->_bf = 1; SubL->_bf = 0; SubLR = 0; } else if (flag == 1) { parent->_bf = 0; SubL->_bf = -1; SubLR = 0; } else { parent->_bf = 0; SubL->_bf = 0; SubLR = 0; } } void RotateRL(Node* parent) { Node* SubR = parent->_right; Node* SubRL = SubR->_left; // 记下SubRL的平衡因子用来区分更新平衡因子的不同情况 int flag = SubRL->_bf; RotateR(SubR); RotateL(parent); // 更新平衡因子 if (flag == -1) { parent->_bf = 0; SubR->_bf = 1; SubRL->_bf = 0; } else if (flag == 1) { parent->_bf = -1; SubR->_bf = 0; SubRL->_bf = 0; } else { parent->_bf = 0; SubR->_bf = 0; SubRL->_bf = 0; } } void InOrder() { _InOrder(_root); } size_t size() { return _size(_root); } size_t Height() { return _Height(_root); } Node* find(const K& key) { Node* cur = _root; while (cur) { if (cur->_kv.first > key) { cur = cur->_left; } else if (cur->_kv.first < key) { cur = cur->_right; } else { return cur; } } return nullptr; } bool IsBalanceTree() { return _IsBalanceTree(_root); } private: void _InOrder(Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_kv.first << ": " << root->_kv.second << endl; _InOrder(root->_right); } size_t _size(Node* root) { if (root == nullptr) { return 0; } return _size(root->_left) + _size(root->_right) + 1; } size_t _Height(Node* root) { if (root == nullptr) { return 0; } int heightleft = _Height(root->_left); int heightright = _Height(root->_right); return heightleft > heightright ? heightleft + 1 : heightright + 1; // 需要加上根结点高度1 } bool _IsBalanceTree(Node* root) { if (root == nullptr) { return true; } int leftheight = _Height(root->_left); int rightheight = _Height(root->_right); int bf = rightheight - leftheight; // 右子树高度 - 左子树高度 if (abs(bf) >= 2) { cout << "高度异常" << endl; return false; } if(root->_bf != bf) { cout << "平衡因子异常" << endl; return false; } return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right); } private: Node* _root = nullptr; };

测试:test.cpp

#include"AVLTree.h" void test01() { vector<int> R({ 10,12,7,5,8,4,3 }); vector<int> L({ 10,15,6,20,14,25 }); vector<int> vv({ 16, 3, 7, 11, 9, 26, 18, 14, 15 }); AVLTree<int, int> tree; for (auto e : vv) { tree.Insert(make_pair(e, e)); } tree.InOrder(); tree.Height(); if (tree.IsBalanceTree()) { cout << "平衡" << endl; } } int main() { test01(); return 0; }

Read more

Flutter 三方库 flutter_adaptive_scaffold 的鸿蒙化适配指南 - 掌握一套代码适配全场景终端的自适应架构技术、助力鸿蒙应用构建从手机到平板及折叠屏的极致无缝交互体系

Flutter 三方库 flutter_adaptive_scaffold 的鸿蒙化适配指南 - 掌握一套代码适配全场景终端的自适应架构技术、助力鸿蒙应用构建从手机到平板及折叠屏的极致无缝交互体系

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 flutter_adaptive_scaffold 的鸿蒙化适配指南 - 掌握一套代码适配全场景终端的自适应架构技术、助力鸿蒙应用构建从手机到平板及折叠屏的极致无缝交互体系 前言 在 OpenHarmony 鸿蒙应用追求“万物互联、全场景覆盖”的伟大进程中,屏幕尺寸的多样性(从 6 英寸手机到 12 英寸平板,再到 2D/3D 模式切换的折叠屏)是每一位 UI 开发者必须正面迎接的挑战。如何在不为每种设备重写 UI 的前提下,实现导航栏自动从“底部”平滑流转到“侧边”?如何在宽屏模式下自动开启“双栏(Master-Detail)”布局?flutter_adaptive_scaffold 作为一个由 Flutter

By Ne0inhk
在 macOS 上通过 Docker 本地安装 OpenClaw 完整教程

在 macOS 上通过 Docker 本地安装 OpenClaw 完整教程

在 macOS 上通过 Docker 本地安装 OpenClaw 完整教程 什么是 OpenClaw?—— 你的本地 AI 智能体执行框架 OpenClaw 不仅仅是一个聊天机器人,而是一个功能强大的 AI 智能体执行框架。你可以把它想象成一个能自主思考、调用工具、并替你完成复杂任务的数字员工。 🧠 核心概念 * 智能体:OpenClaw 的核心大脑。它能理解你的自然语言指令,拆解任务,并决定调用哪些工具来执行。 * 网关:所有外部访问的入口。它负责处理 WebSocket 连接、管理设备配对、路由消息,是你与智能体交互的桥梁。 * 技能:智能体可调用的具体工具,比如访问文件、操作浏览器、发送消息、查询数据库等。你可以根据需要扩展技能库。 * 记忆:OpenClaw 可以存储对话历史和重要信息,实现长期记忆和上下文理解,让交互更连贯。 * 通道:连接外部聊天平台的渠道,如

By Ne0inhk
HarmonyOS6半年磨一剑 - RcIcon组件实战案例集与应用开发指南

HarmonyOS6半年磨一剑 - RcIcon组件实战案例集与应用开发指南

文章目录 * 前言 * 项目简介 * 核心特性 * 开源计划 * rchoui官网 * 文档概述 * 第一章: 基础用法实战 * 1.1 三种符号引用方式 * 1.2 应用场景 - 工具栏快速导航 * 第二章: 尺寸系统实战 * 2.1 响应式尺寸配置 * 2.2 应用场景 - 统一设计系统尺寸规范 * 第三章: 颜色系统实战 * 3.1 多彩色系配置 * 3.2 应用场景 - 状态指示系统 * 第四章: 双风格系统实战 * 4.1 线型与实底风格对比 * 4.2 应用场景 - 底部导航栏 * 第五章: 圆角系统实战 * 5.

By Ne0inhk
Flutter 组件 short_uuids 适配鸿蒙 HarmonyOS 实战:唯一标识微缩技术,构建高性能短 ID 生成与分布式索引架构

Flutter 组件 short_uuids 适配鸿蒙 HarmonyOS 实战:唯一标识微缩技术,构建高性能短 ID 生成与分布式索引架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 short_uuids 适配鸿蒙 HarmonyOS 实战:唯一标识微缩技术,构建高性能短 ID 生成与分布式索引架构 前言 在鸿蒙(OpenHarmony)生态迈向万物互联、涉及海量离线资源标识、蓝牙广播载荷(BLE Payload)及二维码数据极限压缩的背景下,如何生成既能保留 UUID 强随机性、又能极大缩减字符长度的唯一标识符,已成为优化存储与通讯效率的“空间必修课”。在鸿蒙设备这类强调分布式软总线传输与每一字节功耗敏感的环境下,如果应用依然直接传输长度达 36 字符的标准 UUID,由于由于有效载荷溢出,极易由于由于传输协议限制导致数据截断或多次分包带来的延迟。 我们需要一种能够实现高进制转换、支持双向编解码且具备低碰撞概率的短 ID 生成方案。 short_uuids 为 Flutter 开发者引入了将标准 UUID 转化为短格式字符串的高性能算法。它利用

By Ne0inhk