AVL树、红黑树理解
C+±AVL&红黑树
1. AVL树
1.1 概念
1.1.1 定义
AVL树是一种高度平衡的二叉搜索树,满足:
-
是二叉搜索树(BST)
-
任意节点的左右子树高度差不超过1(平衡因子∈[-1,0,1])
1.1.2 平衡因子(Balance Factor)
对于任意节点:平衡因子=右子树高度−左子树高度平衡因子 = 右子树高度 - 左子树高度平衡因子=右子树高度−左子树高度 或 平衡因子=左子树高度−右子树高度平衡因子 = 左子树高度 - 右子树高度平衡因子=左子树高度−右子树高度。
1.2 AVL树的结构
-
包含 parent 指针:
-
优点:便于回溯到父节点进行平衡调整
-
缺点:增加了内存开销和维护复杂度
-
有些实现选择不使用 parent 指针,而是通过递归参数传递父节点信息
-
-
平衡因子存储:
-
也可以选择不存储 bf,而是在需要时计算
-
存储 bf 可以节省计算时间,但增加内存使用
-
template<typename Key , typename Value>
struct AVLTreeNode {std::pair<Key , Value> data;AVLTreeNode<Key , Value>* left;AVLTreeNode<Key , Value>* right;AVLTreeNode<Key , Value>* parent;int bf; // balance factorAVLTreeNode(const std::pair<Key , Value>& _data):data(_data) , left(nullptr) , right(nullptr) , parent(nullptr) , bf(0){}
};
1.3 AVL树的插入
1.3.1 插入节点的流程
-
标准的BST插入节点
-
更新平衡因子。插入节点后,只会影响祖先路径上节点的平衡因子
-
更新平衡因子过程中都满足AVL性质,则插入结束
-
更新平衡因子过程中不满足AVL性质,则需要旋转调整
-
1.3.2 平衡因子的更新
更新原则:
-
只有⼦树⾼度变化才会影响当前结点平衡因⼦。
-
插⼊结点,会增加⾼度,所以新增结点在parent节点的右⼦树,parent的平衡因⼦+1parent的平衡因⼦+ 1parent的平衡因⼦+1,新增结点在parent的左⼦树,parent平衡因⼦−1parent平衡因⼦ - 1parent平衡因⼦−1,并继续向上更新。
更新的情况:
-
当前节点的平衡因子变为 0
- 解释: 当某个节点的平衡因子从 1或-1 变为 0,说明它的子树高度没有变化(插入的节点填补了原本较矮的一侧)。此时,祖先节点的高度不会受影响,无需继续向上更新。
-
当前节点的平衡因子变为1或-1
- 解释: 当某个节点的平衡因子从 0 变为 1 或 0 变为 -1,说明它的子树高度发生变化(插入的节点导致 parent 某一侧子树高度增加),由于其子树高度 整体增加了 1,这可能会影响其祖先节点的平衡因子,继续向上更新。
-
当前节点的平衡因子变为2或-2
- 解释: 当某个节点的平衡因子从 1 变为 2 或 -1 变为 -2,说明更新前, parent 子树已经存在高度差(一边比另一边高1),更新后,新节点插入到较高一侧,使高度差扩大到 2 破坏了AVL树的平衡条件,需通过旋转恢复平衡,旋转完成后,parent子树的高度与插入前相同,无需继续向上更新。
1.3.3 右单旋
右单旋是用于解决左左不平衡的情况(即节点的左子树比右子树高2层,且左子树的左子树导致的不平衡)。
抽象图
具体图
代码
void rotateR(TreeNode* parent) {TreeNode* subL = parent->left;TreeNode* subLR = subL->right;TreeNode* pParent = parent->parent;parent->left = subLR;// 当h = 0时,subLR可能为空if (subLR) {subLR->parent = parent;}subL->right = parent;parent->parent = subL;if (root == parent) {// 更新到根发生不平衡root = subL;subL->parent = nullptr;} else {// 只是局部树发生不平衡if (pParent->left == parent)pParent->left = subL;elsepParent->right = subL;subL->parent = pParent;}// 更新平衡因子subL->bf = parent->bf = 0;
}
1.3.4 左单旋
左单旋是用于解决右右不平衡的情况(即节点的右子树比左子树高2层,且右子树的右子树导致的不平衡)。
抽象图
左单旋具体图类似右单旋
…
代码
void rotateL(TreeNode* parent) {TreeNode* subR = parent->right;TreeNode* subRL = subR->left;TreeNode* pParent = parent->parent;parent->right = subRL;// 当h = 0时,subRL可能为空if (subRL) {subRL->parent = parent;}subR->left = parent;parent->parent = subR;if (root == parent) {// 更新到根发生不平衡root = subR;subR->parent = nullptr;} else {// 只是局部树发生不平衡if (pParent->left == parent)pParent->left = subR;elsepParent->right = subR;subR->parent = pParent;}// 更新平衡因子subR->bf = parent->bf = 0;
}
1.3.5 左右双旋
左右单旋是用于解决左右不平衡的情况(即节点的左子树比右子树高2层,且左子树的右子树导致的不平衡)。
-
以subL为基准点进行左旋
-
再以parent为基准点进行右旋
平衡因子的更新情况
-
插入在subLR的左子树导致的左右旋转
-
parent−>balance_factor=1parent->balance\_factor = 1parent−>balance_factor=1
-
subL−>balance_factor=0subL->balance\_factor = 0subL−>balance_factor=0
-
subLR−>balance_factor=0subLR->balance\_factor = 0subLR−>balance_factor=0
-
-
插入在subLR的右子树导致的左右旋转
-
parent−>balance_factor=0parent->balance\_factor = 0parent−>balance_factor=0
-
subL−>balance_factor=1subL->balance\_factor = 1subL−>balance_factor=1
-
subLR−>balance_factor=0subLR->balance\_factor = 0subLR−>balance_factor=0
-
-
h=0h = 0h=0,subLRsubLRsubLR为新插入的节点
-
parent−>balance_factor=0parent->balance\_factor = 0parent−>balance_factor=0
-
subL−>balance_factor=0subL->balance\_factor = 0subL−>balance_factor=0
-
subLR−>balance_factor=0subLR->balance\_factor = 0subLR−>balance_factor=0
-
代码
void rotateLR(TreeNode* parent) {TreeNode* subL = parent->left;TreeNode* subLR = subL->right;// 提前保存subLR的平衡因子int subLR_bf = subLR->bf;rotateL(subL);rotateR(parent);// 更新平衡因子if (subLR_bf == -1) {// 1.插入节点在subRL的左子树parent->bf = 1;subL->bf = 0;subLR->bf = 0;} else if (subLR_bf == 1) {// 2.插入节点在subRL的右子树parent->bf = 0;subL->bf = -1;subLR->bf = 0;} else if (subLR_bf == 0) {// 3.h = 0且subRL为新插入的节点parent->bf = 0;subL->bf = 0;subLR->bf = 0;} else {// 不符合AVL树规则assert(false);}
}
1.3.6 右左双旋
左右单旋是用于解决右左不平衡的情况(即节点的右子树比左子树高2层,且右子树的左子树导致的不平衡)。
-
以subR为基准点进行右旋
-
再以parent为基准点进行左旋
平衡因子的更新情况
-
插入在subRL的左子树导致的右左旋转
-
parent−>balance_factor=0parent->balance\_factor = 0parent−>balance_factor=0
-
subR−>balance_factor=1subR->balance\_factor = 1subR−>balance_factor=1
-
subRL−>balance_factor=0subRL->balance\_factor = 0subRL−>balance_factor=0
-
-
插入在subRL的右子树导致的右左旋转
-
parent−>balance_factor=1parent->balance\_factor = 1parent−>balance_factor=1
-
subR−>balance_factor=0subR->balance\_factor = 0subR−>balance_factor=0
-
subRL−>balance_factor=0subRL->balance\_factor = 0subRL−>balance_factor=0
-
-
h=0h = 0h=0,subRLsubRLsubRL为新插入的节点
-
parent−>balance_factor=0parent->balance\_factor = 0parent−>balance_factor=0
-
subR−>balance_factor=0subR->balance\_factor = 0subR−>balance_factor=0
-
subRL−>balance_factor=0subRL->balance\_factor = 0subRL−>balance_factor=0
-
代码
void rotateRL(TreeNode* parent) {TreeNode* subR = parent->right;TreeNode* subRL = subR->left;// 提前保存subRL的平衡因子int subRL_bf = subRL->bf;rotateR(subR);rotateL(parent);// 更新平衡因子if (subRL_bf == -1) {// 1.插入节点在subRL的左子树parent->bf = 0;subR->bf = 1;subRL->bf = 0;} else if (subRL_bf == 1) {// 2.插入节点在subRL的右子树parent->bf = -1;subR->bf = 0;subRL->bf = 0;} else if (subRL_bf == 0) {// 3.h = 0且subRL为新插入的节点parent->bf = 0;subR->bf = 0;subRL->bf = 0;} else {// 不符合AVL树规则assert(false);}
}
1.3.7 完整代码
#pragma once
#include <iostream>
#include <utility>
#include <cassert>namespace AVL
{template <typename Key, typename Value>struct AVLTreeNode{std::pair<Key, Value> data;AVLTreeNode<Key, Value> *left;AVLTreeNode<Key, Value> *right;AVLTreeNode<Key, Value> *parent;int bf; // balance factorAVLTreeNode(const std::pair<Key, Value> &_data): data(_data), left(nullptr), right(nullptr), parent(nullptr), bf(0){}};template <typename Key, typename Value>class AVLTree{using TreeNode = AVLTreeNode<const Key, Value>;public:bool insert(const std::pair<Key, Value> &data){if (root == nullptr){root = new TreeNode(data);return true;}TreeNode *parent = nullptr;TreeNode *cur = root;while (cur){if (cur->data.first < data.first){parent = cur;cur = cur->right;}else if (cur->data.first > data.first){parent = cur;cur = cur->left;}else{return false; // 存在相同元素不插入}}cur = new TreeNode(data);if (parent->data.first < cur->data.first)parent->right = cur;elseparent->left = cur;cur->parent = parent;// 更新cur祖先路径上节点的平衡因子while (parent){// 判断在parent左还是右插入节点,parent平衡因子++还是--if (parent->left == cur){parent->bf--;}else{parent->bf++;}if (parent->bf == 0){// 1.当前节点的平衡因子变为 0,无需向上更新break;}else if (parent->bf == 1 || parent->bf == -1){// 2.当前节点的平衡因子变为1或-1,继续向上更新cur = parent;parent = parent->parent;}else if (parent->bf == 2 || parent->bf == -2){// 3.当前节点的平衡因子变为2或-2,旋转处理,旋转后无需向上更新if (cur->bf == -1 && parent->bf == -2){// 右单旋rotateR(parent);}else if (cur->bf == 1 && parent->bf == 2){// 左单旋rotateL(parent);}else if (cur->bf == -1 && parent->bf == 2){// 右左双旋rotateRL(parent);}else if (cur->bf == 1 && parent->bf == -2){// 左右双旋rotateLR(parent);}else{assert(false);}break;}else{assert(false);}}return true;}void rotateR(TreeNode *parent){TreeNode *subL = parent->left;TreeNode *subLR = subL->right;TreeNode *pParent = parent->parent;parent->left = subLR;// 当h = 0时,subLR可能为空if (subLR){subLR->parent = parent;}subL->right = parent;parent->parent = subL;if (root == parent){// 更新到根发生不平衡root = subL;subL->parent = nullptr;}else{// 只是局部树发生不平衡if (pParent->left == parent)pParent->left = subL;elsepParent->right = subL;subL->parent = pParent;}// 更新平衡因子subL->bf = parent->bf = 0;}void rotateL(TreeNode *parent){TreeNode *subR = parent->right;TreeNode *subRL = subR->left;TreeNode *pParent = parent->parent;parent->right = subRL;// 当h = 0时,subRL可能为空if (subRL){subRL->parent = parent;}subR->left = parent;parent->parent = subR;if (root == parent){// 更新到根发生不平衡root = subR;subR->parent = nullptr;}else{// 只是局部树发生不平衡if (pParent->left == parent)pParent->left = subR;elsepParent->right = subR;subR->parent = pParent;}// 更新平衡因子subR->bf = parent->bf = 0;}void rotateRL(TreeNode *parent){TreeNode *subR = parent->right;TreeNode *subRL = subR->left;// 提前保存subRL的平衡因子int subRLbf = subRL->bf;rotateR(subR);rotateL(parent);// 更新平衡因子if (subRLbf == -1){// 1.插入节点在subRL的左子树parent->bf = 0;subR->bf = 1;subRL->bf = 0;}else if (subRLbf == 1){// 2.插入节点在subRL的右子树parent->bf = -1;subR->bf = 0;subRL->bf = 0;}else if (subRLbf == 0){// 3.h = 0且subRL为新插入的节点parent->bf = 0;subR->bf = 0;subRL->bf = 0;}else{// 不符合AVL树规则assert(false);}}void rotateLR(TreeNode *parent){TreeNode *subL = parent->left;TreeNode *subLR = subL->right;// 提前保存subLR的平衡因子int subLRbf = subLR->bf;rotateL(subL);rotateR(parent);// 更新平衡因子if (subLRbf == -1){// 1.插入节点在subRL的左子树parent->bf = 1;subL->bf = 0;subLR->bf = 0;}else if (subLRbf == 1){// 2.插入节点在subRL的右子树parent->bf = 0;subL->bf = -1;subLR->bf = 0;}else if (subLRbf == 0){// 3.h = 0且subRL为新插入的节点parent->bf = 0;subL->bf = 0;subLR->bf = 0;}else{// 不符合AVL树规则assert(false);}}private:TreeNode* root = nullptr;public:// 以下为 test codevoid InOrder(){_InOrder(root);std::cout << std::endl;}int Height(){return _Height(root);}int Size(){return _Size(root);}bool IsBalanceTree(){return _IsBalanceTree(root);}TreeNode *Find(const Key &key){TreeNode *cur = root;while (cur){if (cur->data.first < key){cur = cur->right;}else if (cur->data.first > key){cur = cur->left;}else{return cur;}}return nullptr;}private:void _InOrder(TreeNode *root){if (root == nullptr){return;}_InOrder(root->left);std::cout << root->data.first << ":" << root->data.second << std::endl;_InOrder(root->right);}int _Height(TreeNode *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(TreeNode *root){if (root == nullptr)return 0;return _Size(root->left) + _Size(root->right) + 1;}bool _IsBalanceTree(TreeNode *root){// 空树也是AVL树if (nullptr == root)return true;// 计算pRoot结点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(root->left);int rightHeight = _Height(root->right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (abs(diff) >= 2){std::cout << root->data.first << "高度差异常" << std::endl;return false;}if (root->bf != diff){std::cout << root->data.first << "平衡因子异常" << std::endl;return false;}// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->left) && _IsBalanceTree(root->right);}};
}
2. Red-Black Tree
2.1 概念
红⿊树是⼀种平衡⼆叉搜索树,他的每个节点增加⼀个存储位来表⽰节点的颜⾊,可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个节点的颜⾊进⾏约束,红黑树的性质能够保证从根节点到任意叶子节点的最长路径不会超过最短路径的两倍,从而维持树的近似平衡状态。
2.2 性质
-
每个节点必须是红色或者黑色
-
根节点始终为黑色
-
如果⼀个节点是红⾊的,则它的两个孩⼦节点必须是⿊⾊的,即不存在连续的红色节点
-
对于任意⼀个节点,从该节点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊节点(称为black height)
-
红黑树中所有的空节点(NULL)被称为 NIL 节点,NIL节点都是黑色的
2.3 红⿊树如何确保最⻓路径不超过最短路径的2倍的?
关键约束:黑高(Black Height)相同
红黑树的 性质4 规定:
从任意节点到其所有叶子节点的路径,必须包含相同数量的黑色节点(黑高相同)。
这意味着:
-
无论路径如何走,黑色节点的数量是固定的(记为 bhbhbh)。
-
最短路径:全黑节点(路径长度=bh路径长度 = bh路径长度=bh)。
-
最长路径:黑红交替(路径长度≤2bh路径长度 ≤ 2bh路径长度≤2bh)。
示例(假设 bh=2bh = 2bh=2):
-
最短路径:
黑 → 黑
(长度=2长度=2长度=2) -
最长路径:
黑 → 红 → 黑 → 红
(长度=4长度=4长度=4)
但实际红黑树的 性质3(红色节点不能连续)限制了最长路径:
-
最长路径只能是
黑 → 红 → 黑 → 红
(长度=4长度=4长度=4),而最短路径是黑 → 黑
(长度=2长度=2长度=2)。 -
因此,最长路径(4)不超过最短路径(2)的2倍。
2.4 红黑树的结构
enum Color{RED , BLACK
};
template <typename Key, typename Value>
struct RBTreeNode
{std::pair<Key, Value> data;RBTreeNode<Key, Value> *left;RBTreeNode<Key, Value> *right;RBTreeNode<Key, Value> *parent;Color color;RBTreeNode(const std::pair<Key, Value> &_data): data(_data), left(nullptr), right(nullptr), parent(nullptr), color(RED) // 默认插入节点是红色{}
};
2.5 红黑树的插入
2.5.1 插入节点的流程
-
基础插入规则
-
按二叉搜索树规则找到插入位置,新增节点初始颜色遵循:
-
空树插入:新节点设为黑色(满足规则1)。
-
非空树插入:新节点必须为红色(避免直接破坏规则4)。
-
-
-
插入后检查规则
-
情况1:父节点为黑色
- 未违反任何规则(规则3、4均满足),插入结束。
-
情况2:父节点为红色(违反规则3,需进一步处理)
-
此时祖父节点(g)必为黑色(因规则3)。
-
关键变量:叔父节点(u,即父节点的兄弟)的颜色决定处理策略。
-
-
循环条件
p && ->color == RED
父亲是红色,出现连续的红色节点,需要处理。
-
2.5.2 插入节点情况1
c为红,p为红,g为黑,u存在且为红
- 将p和u改为黑色,g改为红色,然后以g为当前节点c继续向上检查。若g是根节点,则再把g变回黑色
2.5.3 插入节点情况2
c为红,p为红,g为黑,u不存在且为黑
- 如果p是g的左,c是p的左,那么以g为旋转点进⾏右单旋,再把p变⿊,g变红即可,且不需要继续往上更新。
- 如果p是g的右,c是p的右,那么以g为旋转点进⾏左单旋,再把p变⿊,g变红即可,且不需要继续往上更新。
2.5.4 插入节点情况3
c为红,p为红,g为黑,u不存在且为黑
-
如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变⿊,g变红即可,且不需要继续往上更新。
-
如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变⿊,g变红即可,且不需要继续往上更新。
2.6 完整代码
#pragma once
#include <iostream>
#include <utility>
#include <cassert>enum Color
{RED,BLACK
};namespace RBT
{template <typename Key, typename Value>struct RBTreeNode{std::pair<Key, Value> data;RBTreeNode<Key, Value> *left;RBTreeNode<Key, Value> *right;RBTreeNode<Key, Value> *parent;Color color;RBTreeNode(const std::pair<Key, Value> &_data): data(_data), left(nullptr), right(nullptr), parent(nullptr), color(RED) // 默认插入节点是红色{}};template <typename Key, typename Value>class RBTree{using TreeNode = RBTreeNode<Key, Value>;public:bool insert(const std::pair<Key, Value> &data){if (root == nullptr){root = new TreeNode(data);root->color = BLACK; // 根节点必须是黑色return true;}TreeNode *parent = nullptr;TreeNode *cur = root;while (cur){if (cur->data.first < data.first){parent = cur;cur = cur->right;}else if (cur->data.first > data.first){parent = cur;cur = cur->left;}else{return false; // 存在相同元素不插入}}// 新插入节点为红色cur = new TreeNode(data);if (parent->data.first < cur->data.first)parent->right = cur;elseparent->left = cur;cur->parent = parent;// 父亲是红色,出现连续的红色节点,需要处理while (parent && parent->color == RED){TreeNode *g = parent->parent;if (g->left == parent){TreeNode *u = g->right;if (u && u->color == RED){// 情况1:变色处理 + 继续向上处理parent->color = u->color = BLACK;g->color = RED;cur = g;parent = cur->parent;}else{// u == nullptr || u->color == BLACKif (parent->left == cur){// 情况2:右单旋 + 变色rotateR(g);g->color = RED;parent->color = BLACK;}else{// 情况3:左右双旋 + 变色rotateL(parent);rotateR(g);cur->color = BLACK;g->color = RED;}break; // 无需继续向上更新}}else{TreeNode *u = g->left;if (u && u->color == RED){// 情况1:变色处理 + 继续向上处理parent->color = u->color = BLACK;g->color = RED;cur = g;parent = cur->parent;}else{// u == nullptr || u->color == BLACKif (parent->right == cur){// 情况2:左单旋 + 变色rotateL(g);g->color = RED;parent->color = BLACK;}else{// 情况3:右左双旋 + 变色rotateR(parent);rotateL(g);cur->color = BLACK;g->color = RED;}break; // 无需继续向上更新}}}root->color = BLACK; // root始终为黑色return true;}// 旋转里会改变rootvoid rotateR(TreeNode *parent){TreeNode *subL = parent->left;TreeNode *subLR = subL->right;TreeNode *pParent = parent->parent;parent->left = subLR;// 当h = 0时,subLR可能为空if (subLR){subLR->parent = parent;}subL->right = parent;parent->parent = subL;if (root == parent){// 更新到根发生不平衡root = subL;subL->parent = nullptr;}else{// 只是局部树发生不平衡if (pParent->left == parent)pParent->left = subL;elsepParent->right = subL;subL->parent = pParent;}}void rotateL(TreeNode *parent){TreeNode *subR = parent->right;TreeNode *subRL = subR->left;TreeNode *pParent = parent->parent;parent->right = subRL;// 当h = 0时,subRL可能为空if (subRL){subRL->parent = parent;}subR->left = parent;parent->parent = subR;if (root == parent){// 更新到根发生不平衡root = subR;subR->parent = nullptr;}else{// 只是局部树发生不平衡if (pParent->left == parent)pParent->left = subR;elsepParent->right = subR;subR->parent = pParent;}}private:TreeNode *root = nullptr;// test codepublic:void inorder(){_inorder(root);std::cout << std::endl;}bool isRBT(){if (root == nullptr)return true;if (root->color == RED)return false;// 参考值int refNum = 0;TreeNode*cur = root;while (cur){if (cur->color == BLACK){++refNum;}cur = cur->left;}return Check(root, 0, refNum);}private:void _inorder(TreeNode *root){if (root == nullptr){return;}_inorder(root->left);std::cout << root->data.first << ":" << root->data.second << std::endl;_inorder(root->right);}bool Check(TreeNode *root, int blackNum, const int refNum){if (root == nullptr){// 前序遍历走到空时,意味着一条路径走完了// cout << blackNum << endl;if (refNum != blackNum){std::cout << "存在黑色结点的数量不相等的路径" << std::endl;return false;}return true;}// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了if (root->color == RED && root->parent->color == RED){std::cout << root->data.first << "存在连续的红色结点" << std::endl;return false;}if (root->color == BLACK){blackNum++;}return Check(root->left, blackNum, refNum) && Check(root->right, blackNum, refNum);}};
点" << std::endl;return false;}if (root->color == BLACK){blackNum++;}return Check(root->left, blackNum, refNum) && Check(root->right, blackNum, refNum);}};
} // namespace RBT end
今天的看不懂,会成为明天的太简单。