跳到主要内容C++伸展树与红黑树原理及实现 | 极客日志C++算法
C++伸展树与红黑树原理及实现
伸展树与红黑树是两种重要的平衡二叉搜索树。伸展树利用局部性原理,通过旋转将访问节点移至根部,适合缓存场景;红黑树通过颜色约束保持近似平衡,确保最坏情况下的对数时间复杂度,广泛用于 STL 容器。涵盖两者概念、性质、旋转操作、插入删除逻辑及 C++ 完整实现,包含相关算法题目解析。
星河入梦3 浏览 1. 伸展树介绍
伸展树(Splay Tree)是一种自调整的二叉搜索树,由 John Edward Hopcroft 和 Robert Endre Tarjan 于 1985 年提出。与 AVL 树不同,伸展树不要求严格的平衡,而是通过'伸展'操作将最近访问的节点移至根节点,利用局部性原理优化频繁访问的场景。
伸展树的旋转操作
每当访问(包括搜索、插入或删除)一个结点 s 时,伸展树就执行一次'展开'过程,将结点 s 移到二叉搜索树的根部。旋转有三种类型:
- 单旋转:被访问结点 s 的父结点是根结点时执行。s 成为新的根,原根 p 成为其子女。
- 一字形旋转:同构形状(如左 -左或右 -右)。先围绕 p 旋转 g,再围绕 s 旋转 p。
- 之字形旋转:异构形状(如左 -右或右 -左)。先围绕 s 旋转 p,再围绕 s 旋转 g。
伪代码描述如下:
void splaying(Node* g, Node* p, Node* s) {
while (s != root) {
if (p == root) {
Rotate(s);
} else if ((g->left == p && p->left == s) || (g->right == p && p->right == s)) {
Rotate(p); Rotate(s);
} else {
Rotate(s); Rotate(s);
}
}
}
伸展树并不要求每一个操作都是高效的,但对于 n 个结点执行 m 次操作,总时间复杂度为 O(m log n),均摊时间复杂度为 O(log n)。
2. 红⿊树介绍
2.1 红⿊树的概念
红⿊树是一棵二叉搜索树,每个结点增加一个存储位表示颜色(红色或黑色)。通过对路径上结点颜色的约束,确保没有一条路径比其他路径长出 2 倍,因而是接近平衡的。
2.2 红⿊树的性质
- 根结点和所有外部结点(NIL)的颜色是黑色。
- 从根结点到外部结点的途中没有连续两个结点的颜色是红色。
- 所有从根到外部结点的路径上都有相同数目的黑色结点。
设从根到外部结点的路径长度为 PL,若 P 与 Q 是两条路径,则 PL(P) <= 2 * PL(Q)。红黑树的高度 h 满足 h <= 2 * log2(n + 1),因此增删查改的时间复杂度为 O(log n)。
2.3 红⿊树如何确保最⻓路径不超过最短路径的 2 倍的?
- 从根到 NULL 结点的每条路径都有相同数量的黑色结点,最短路径即全黑路径。
- 任意一条路径不会有连续的红⾊结点,最长路径为一黑一红间隔组成,长度约为最短路径的 2 倍。
- 综合性质,理论上的全黑最短路径和一黑一红的最长路径保证了高度限制。
2.4 红⿊树的效率
红黑树通过颜色约束间接实现了近似平衡。相对于 AVL 树,红黑树在插入相同数量结点时旋转次数更少,因为对平衡的控制没那么严格,但效率在同一档次。
3. 红⿊树的实现
3.1 红⿊树的结构
#pragma once
#include <iostream>
using namespace std;
enum Color { RED, BLACK };
template<class K, class V>
struct RBTreeNode {
pair<K, V> _kv;
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
Color _col;
RBTreeNode(const pair<K, V>& kv)
:_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) {}
};
template<class K, class V>
class RBTree {
typedef RBTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv);
void InOrder();
bool IsBalance();
int Height();
int Size();
Node* Find(const K& key);
private:
Node* _root = nullptr;
};
3.2 红黑树的搜索
由于每一棵红黑树都是二叉搜索树,可以使用与普通二叉搜索树相同的算法进行搜索,不需要使用颜色信息。时间复杂度为 O(log n)。
3.3 红⿊树的插⼊
- 按二叉搜索树规则插入,新结点默认为红色。
- 若为空树,根结点设为黑色。
- 若父结点为黑色,无需调整。
- 若父结点为红色,违反性质,需根据叔叔结点颜色进行变色或旋转处理。
3.4 红⿊树的插⼊代码实现
bool Insert(const pair<K, V>& kv) {
if (_root == nullptr) {
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_kv.first < kv.first) {
parent = cur; cur = cur->_right;
} else if (cur->_kv.first > kv.first) {
parent = cur; cur = cur->_left;
} else {
return false;
}
}
cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first < kv.first) parent->_right = cur;
else parent->_left = cur;
cur->_parent = parent;
while (parent && parent->_col == RED) {
Node* grandfather = parent->_parent;
if (grandfather->_left == parent) {
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED) {
parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED;
cur = grandfather; parent = cur->_parent;
} else {
if (cur == parent->_left) {
RotateR(grandfather);
parent->_col = BLACK; grandfather->_col = RED;
} else {
RotateL(parent); RotateR(grandfather);
cur->_col = BLACK; grandfather->_col = RED;
}
break;
}
} else {
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED) {
parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED;
cur = grandfather; parent = cur->_parent;
} else {
if (cur == parent->_right) {
RotateL(grandfather);
parent->_col = BLACK; grandfather->_col = RED;
} else {
RotateR(parent); RotateL(grandfather);
cur->_col = BLACK; grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
3.5 红⿊树的查找
Node* Find(const K& key) {
Node* cur = _root;
while (cur) {
if (cur->_kv.first < key) cur = cur->_right;
else if (cur->_kv.first > key) cur = cur->_left;
else return cur;
}
return nullptr;
}
3.6 红⿊树的验证
bool Check(Node* cur, int blackNum, const int blackNumRef) {
if (cur == nullptr) {
if (blackNum != blackNumRef) return false;
return true;
}
if (cur->_col == RED && cur->_parent && cur->_parent->_col == RED) return false;
if (cur->_col == BLACK) ++blackNum;
return Check(cur->_left, blackNum, blackNumRef) && Check(cur->_right, blackNum, blackNumRef);
}
3.7 红⿊树的删除
删除逻辑类似二叉搜索树,若删除黑色结点可能破坏黑高度,需引入'双重黑色'概念并通过旋转变色消除,恢复红黑树特性。
3.8 红黑树模拟实现
#include<iostream>
#include<algorithm>
using namespace std;
enum Color { RED, BLACK };
template<typename K, typename V>
struct RBNode {
K key;
V value;
Color color;
RBNode* parent;
RBNode* left;
RBNode* right;
RBNode(const K& k, const V& v, Color c = RED)
:key(k), value(v), color(c), parent(nullptr), left(nullptr), right(nullptr) {}
};
template<typename K, typename V>
class RBTree {
private:
using Node = RBNode<K, V>;
Node* root;
Node* nil;
void left_rotate(Node* x) {
Node* y = x->right;
x->right = y->left;
if (y->left != nil) y->left->parent = x;
y->parent = x->parent;
if (x->parent == nil) root = y;
else if (x == x->parent->left) x->parent->left = y;
else x->parent->right = y;
y->left = x; x->parent = y;
}
void right_rotate(Node* y) {
Node* x = y->left;
y->left = x->right;
if (x->right != nil) x->right->parent = y;
x->parent = y->parent;
if (y->parent == nil) root = x;
else if (y == y->parent->left) y->parent->left = x;
else y->parent->right = x;
x->right = y; y->parent = x;
}
void insert_fixup(Node* z) {
while (z->parent->color == RED) {
if (z->parent == z->parent->parent->left) {
Node* uncle = z->parent->parent->right;
if (uncle->color == RED) {
z->parent->color = BLACK;
uncle->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->right) {
z = z->parent;
left_rotate(z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
right_rotate(z->parent->parent);
}
} else {
Node* uncle = z->parent->parent->left;
if (uncle->color == RED) {
z->parent->color = BLACK;
uncle->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->left) {
z = z->parent;
right_rotate(z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
left_rotate(z->parent->parent);
}
}
}
root->color = BLACK;
}
Node* bst_insert(const K& key, const V& value) {
Node* parent = nil;
Node* curr = root;
while (curr != nil) {
parent = curr;
if (key < curr->key) curr = curr->left;
else if (key > curr->key) curr = curr->right;
else { curr->value = value; return curr; }
}
Node* new_node = new Node(key, value, RED);
new_node->parent = parent;
new_node->left = nil; new_node->right = nil;
if (parent == nil) root = new_node;
else if (key < parent->key) parent->left = new_node;
else parent->right = new_node;
return new_node;
}
void inorder_traversal(Node* node) const {
if (node == nil) return;
inorder_traversal(node->left);
cout << "[" << node->key << ":" << node->value << "," << (node->color == RED ? "红" : "黑") << "] ";
inorder_traversal(node->right);
}
public:
RBTree() {
nil = new Node(K(), V(), BLACK);
root = nil;
}
~RBTree() { delete nil; }
void insert(const K& key, const V& value) {
Node* new_node = bst_insert(key, value);
if (new_node->color == RED) insert_fixup(new_node);
}
void inorder() const {
inorder_traversal(root); cout << endl;
}
Node* find(const K& key) const {
Node* curr = root;
while (curr != nil) {
if (key < curr->key) curr = curr->left;
else if (key > curr->key) curr = curr->right;
else return curr;
}
return nullptr;
}
};
int main() {
RBTree<int, string> rb_tree;
rb_tree.insert(10, "A"); rb_tree.insert(20, "B");
rb_tree.insert(30, "C"); rb_tree.insert(15, "D");
rb_tree.inorder();
return 0;
}
4. 相关 OJ 题
4.1 二叉搜索树中的插入操作
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
TreeNode* cur = root;
while (cur != nullptr) {
if (val < cur->val) {
if (cur->left == nullptr) { cur->left = new TreeNode(val); break; }
else cur = cur->left;
} else {
if (cur->right == nullptr) { cur->right = new TreeNode(val); break; }
else cur = cur->right;
}
}
return root;
}
};
4.2 将二叉搜索树变平衡
class Solution {
private:
void inorder(TreeNode* root, vector<int>& nums) {
if (root == nullptr) return;
inorder(root->left, nums); nums.push_back(root->val);
inorder(root->right, nums);
}
TreeNode* build(const vector<int>& nums, int start, int end) {
if (start > end) return nullptr;
int mid = start + (end - start) / 2;
TreeNode* node = new TreeNode(nums[mid]);
node->left = build(nums, start, mid - 1);
node->right = build(nums, mid + 1, end);
return node;
}
public:
TreeNode* balanceBST(TreeNode* root) {
vector<int> nums;
inorder(root, nums);
return build(nums, 0, nums.size() - 1);
}
};
4.3 判断是否为平衡二叉树
class Solution {
private:
int getHeight(TreeNode* node) {
if (node == nullptr) return 0;
int leftHeight = getHeight(node->left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right);
if (rightHeight == -1) return -1;
if (abs(leftHeight - rightHeight) > 1) return -1;
return max(leftHeight, rightHeight) + 1;
}
public:
bool isBalanced(TreeNode* root) {
return getHeight(root) != -1;
}
};
4.4 二叉搜索树迭代器
class BSTIterator {
private:
vector<int> inorderList;
int idx;
void inorder(TreeNode* node) {
if (node == nullptr) return;
inorder(node->left); inorderList.push_back(node->val);
inorder(node->right);
}
public:
BSTIterator(TreeNode* root) {
inorder(root); idx = 0;
}
int next() { return inorderList[idx++]; }
bool hasNext() { return idx < inorderList.size(); }
};
4.5 二叉树中的最大路径和
class Solution {
private:
int helper(TreeNode* node, int& maxSum) {
if (node == nullptr) return 0;
int leftContribution = max(helper(node->left, maxSum), 0);
int rightContribution = max(helper(node->right, maxSum), 0);
int currentPathSum = node->val + leftContribution + rightContribution;
maxSum = max(maxSum, currentPathSum);
return node->val + max(leftContribution, rightContribution);
}
public:
int maxPathSum(TreeNode* root) {
int maxSum = INT_MIN;
helper(root, maxSum);
return maxSum;
}
};
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online