当前位置: 首页 > news >正文

《红黑树核心机制解析:C++ STL中map/set高效实现原理与工程实践》

前言:

在C++标准库中,map和set凭借其稳定对数级查找复杂度(O(log n))成为高性能开发的关键组件。然而,它们的真正威力源于底层的红黑树自平衡机制,以及精心设计的迭代器体系。本文将以零依赖方式完整实现两套符合STL标准的容器:

Map<K, V>:键值映射容器
Set<T>:唯一键集合

涵盖的核心技术细节包括:
✔ 红黑树节点结构设计(颜色标记、父子指针维护)
✔ 插入/删除的平衡修复策略(左旋、右旋及颜色调整)
✔ 迭代器失效机制(与STL标准的一致性保证)
✔ 性能基准测试(与std::map/std::set的对比分析)

目录

一、源码剖析

二、红黑树插入实现

三、红黑树Find查找

四、Set的普通迭代器

五、Map的普通迭代器

祝大家1024程序员节快乐!


一、源码剖析

在 SGI - STL30 版本的源代码里,map 和 set 相关的实现代码分布在 mapsetstl_map.hstl_set.hstl_tree.h 等几个头文件中。

下面我们就看一看 map 和 set 实现结构框架的部分核心内容:


// set 容器相关声明与包含
#ifndef __SGI_STL_INTERNAL_TREE_H
#include <stl_tree.h>
#endif
#include <stl_set.h>
#include <stl_multiset.h>// map 容器相关声明与包含
#ifndef __SGI_STL_INTERNAL_TREE_H
#include <stl_tree.h>
#endif
#include <stl_map.h>
#include <stl_multimap.h>// stl_set.h 中 set 类模板定义
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set 
{
public:// typedefs:typedef Key key_type;typedef Key value_type;
private:typedef rb_tree<key_type, value_type,identity<value_type>, key_compare, Alloc> rep_type;rep_type t;  // red-black tree representing set
};// stl_map.h 中 map 类模板定义
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map 
{
public:// typedefs:typedef Key key_type;typedef T mapped_type;typedef pair<const Key, T> value_type;
private:typedef rb_tree<key_type, value_type,select1st<value_type>, key_compare, Alloc> rep_type;rep_type t;  // red-black tree representing map
};/*----------------------红黑树 节点基类/类模板 定义----------------------*/// stl_tree.h 中红黑树节点基类定义
struct __rb_tree_node_base
{typedef __rb_tree_color_type color_type;typedef __rb_tree_node_base* base_ptr;color_type color;base_ptr parent;base_ptr left;base_ptr right;
};template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree 
{
protected:typedef void* void_pointer;typedef __rb_tree_node_base* base_ptr;typedef __rb_tree_node<Value> rb_tree_node;typedef rb_tree_node* link_type;typedef Key key_type;typedef Value value_type;
public:// insert用的是第二个模板参数左形参pair<iterator, bool> insert_unique(const value_type& x);// erase和find用第一个模板参数做形参size_type erase(const key_type& x);iterator find(const key_type& x);
protected:size_type node_count;  // keeps track of size of treelink_type header;
};// stl_tree.h 中红黑树节点类模板定义
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{typedef __rb_tree_node<Value>* link_type;Value value_field;
};

红黑树(rb_tree)泛型设计思想解析

通过对框架的分析,我们能看到 SGI STL 源码中 rb_tree 的泛型设计非常巧妙

  • 它不直接写死“仅支持 key 搜索” 或 “仅支持 key/value 搜索”
  • 而是通过第二个模板参数 Value 灵活控制:红黑树节点(__rb_tree_node)中实际存储的数据类型,由 Value 决定

这样一颗红黑树,既能适配 set 的 “纯 key 搜索场景”,也能适配 map 的 “key/value 搜索场景”。

二、红黑树插入实现

由于红黑树(RBTree)采用泛型设计,无法直接判断模板参数 T 具体是单纯的键类型 K(如:set 的场景 ),还是键值对类型 pair<K, V>(如:map 的场景 )

  • 这会导致一个问题:在 insert 逻辑里进行 “节点比较” 时,默认的比较规则无法满足需求
  • 因为 pair 的默认比较会同时涉及 key 和 value,但我们实际需要只比较 key

为解决这个问题,我们在 map 和 set 这两个容器层,分别实现了仿函数 MapKeyOfT 和 SetKeyOfT,并将它们传递给红黑树的 KeyOfT 模板参数

这样,红黑树内部就能通过 KeyOfT 仿函数:

  • 先从 T 类型对象中提取出 key
  • 再用这个 key 进行比较
  • 从而实现 “仅按 key 排序 / 插入” 的逻辑。

//插入
bool insert(const V& date)
{if (root==nullptr){root = new Node(date);root->_co = Black;return true;}//找插入位置Node* cur = root;Node* parent = nullptr;Function Sort;while (cur){parent = cur;if (Sort(cur->_date) > Sort(date)){cur = cur->_left;}else if (Sort(cur->_date) < Sort(date)){cur = cur->_right;}else//如果相等就退出{cout << "值相等无法插入" << endl;return false;}}//连接+插入cur = new Node(date);if (Sort(parent->_date) < Sort(date)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//调整Node* grandfather = nullptr;Node* uncle = nullptr;while (parent && parent->_co == Red){grandfather = parent->_parent;//左边调整if (parent == grandfather->_left){uncle = grandfather->_right;//如果uncle存在且为红if (uncle && uncle->_co == Red){parent->_co = Black;uncle->_co = Black;grandfather->_co = Red;//向上更新cur = grandfather;parent = cur->_parent;}else{//根据cur的位置旋转if (cur == parent->_left){//右旋Whirl_R(grandfather);}else{//左右双旋Whirl_L_R(grandfather);}break;}}else//右边调整{uncle = grandfather->_left;//如果uncle存在且为红if (uncle && uncle->_co == Red){parent->_co = Black;uncle->_co = Black;grandfather->_co = Red;//向上调整cur = grandfather;parent = cur->_parent;}else//如果uncle不存在或者存在为黑{//根据cur选择旋转if (cur == parent->_right){//左旋Whirl_L(grandfather);}else{//右左双旋Whirl_R_L(grandfather);}break;}}}root->_co = Black;return true;
}
//左旋
void Whirl_L(Node* parent)
{Node* ppnode = parent->_parent;Node* cur = parent->_right;Node* curleft = cur->_left;//连接cur和parentcur->_left = parent;parent->_parent = cur;//连接curleft和parentparent->_right = curleft;if (curleft){curleft->_parent = parent;}//连接ppnode和curif (ppnode){cur->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}else{root = cur;cur->_parent = nullptr;}//更新颜色cur->_co = Black;parent->_co = Red;
}
//右旋
void Whirl_R(Node* parent)
{Node* ppnode = parent->_parent;Node* cur = parent->_left;Node* curright = cur->_right;//连接cur和parentcur->_right = parent;parent->_parent = cur;//连接curleft和parentparent->_left = curright;if (curright){curright->_parent = parent;}//连接ppnode和curif (ppnode){cur->_parent = ppnode;if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}else{root = cur;cur->_parent = nullptr;}//更新颜色cur->_co = Black;parent->_co = Red;
}
//左右双旋
void Whirl_L_R(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;//左旋Whirl_L(cur);//右旋Whirl_R(parent);//更新颜色curright->_co = Black;cur->_co = Red;parent->_co = Red;
}
//右左双旋
void Whirl_R_L(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;//右旋Whirl_R(cur);//左旋Whirl_L(parent);//更新颜色curleft->_co = Black;cur->_co = Red;parent->_co = Red;
}

三、红黑树Find查找

不论是map还是set都是根据Key查找的,因此map里面的仿函数需要重载

//Find查找
Node find(const V& date)
{Function Find;if (root == nullptr){cout << "查找失败,根为空" << endl;return nullptr;}Node* cur = root;while (cur){if (Find(cur->_date) > Find(date)){cur = cur->_left;}else if(Find(cur->_date) < Find(date)){cur = cur->_right;}else{return cur;}}return nullptr;
}

四、Set的普通迭代器

template <typename T>
class RBTreeSet {
private:struct Node {T value;Node* parent;Node* left;Node* right;bool is_red;// 查找子树最小节点(用于begin())static Node* minimum(Node* x) noexcept {while (x->left != nullptr) {x = x->left;}return x;}// 查找子树最大节点(用于end()前驱)static Node* maximum(Node* x) noexcept {while (x->right != nullptr) {x = x->right;}return x;}};public:// 迭代器类定义class iterator {using iterator_category = std::bidirectional_iterator_tag;using value_type = T;using difference_type = std::ptrdiff_t;using pointer = const T*;    // set元素不可修改using reference = const T&;  // set元素不可修改Node* current;const RBTreeSet* tree;  // 用于边界检查public:explicit iterator(Node* node = nullptr, const RBTreeSet* t = nullptr): current(node), tree(t) {}// 解引用操作符reference operator*() const {return current->value;}pointer operator->() const {return &(operator*());}// 前置++iterator& operator++() {if (current == nullptr) {// 处理end()++的情况throw std::out_of_range("RBTreeSet iterator out of range");}if (current->right != nullptr) {// 情况1:存在右子树,找右子树的最小节点current = Node::minimum(current->right);} else {// 情况2:向上查找第一个是左子节点的祖先Node* parent = current->parent;while (parent != nullptr && current == parent->right) {current = parent;parent = parent->parent;}current = parent;  // 可能为nullptr(到达end)}return *this;}// 后置++(标准实现方式)iterator operator++(int) {iterator tmp = *this;++(*this);return tmp;}// 前置--(逆向遍历)iterator& operator--() {if (current == nullptr) {// 处理begin()--的情况throw std::out_of_range("RBTreeSet iterator out of range");}if (current->left != nullptr) {// 情况1:存在左子树,找左子树的最大节点current = Node::maximum(current->left);} else {// 情况2:向上查找第一个是右子节点的祖先Node* parent = current->parent;while (parent != nullptr && current == parent->left) {current = parent;parent = parent->parent;}current = parent;  // 必须非nullptr(已校验)}return *this;}// 比较操作符bool operator==(const iterator& other) const noexcept {return current == other.current;}bool operator!=(const iterator& other) const noexcept {return !(*this == other);}// 允许从非const转为const迭代器operator typename RBTreeSet::const_iterator() const {return typename RBTreeSet::const_iterator(current, tree);}};// const迭代器(继承自普通迭代器)class const_iterator : public iterator {public:using iterator::iterator;// 继承所有功能,元素始终保持只读};// 获取迭代器iterator begin() noexcept {return iterator(Node::minimum(root), this);}iterator end() noexcept {return iterator(nullptr, this);}const_iterator cbegin() const noexcept {return const_iterator(Node::minimum(root), this);}const_iterator cend() const noexcept {return const_iterator(nullptr, this);}
};

五、Map的普通迭代器

template <typename Key, typename Value>
class RBTreeMap {
private:using value_type = std::pair<const Key, Value>; // 关键:Key必须为conststruct Node {value_type data;  // 存储pair<const Key, Value>Node* parent;Node* left;Node* right;bool is_red;// 获取子树最小节点(用于begin())static Node* minimum(Node* x) noexcept {while (x->left != nullptr) {x = x->left;}return x;}// 获取子树最大节点(用于rbegin())static Node* maximum(Node* x) noexcept {while (x->right != nullptr) {x = x->right;}return x;}};public:// 迭代器类(支持->和*操作符访问pair)class iterator {using iterator_category = std::bidirectional_iterator_tag;using difference_type = std::ptrdiff_t;using reference = value_type&;using pointer = value_type*;Node* current;const RBTreeMap* tree; // 用于边界检查public:explicit iterator(Node* node = nullptr, const RBTreeMap* t = nullptr): current(node), tree(t) {}// 解引用为pair<const Key, Value>&reference operator*() const {return current->data;}// 箭头操作符返回pair指针pointer operator->() const {return &(current->data);}// 前置++(中序后继)iterator& operator++() {if (current == nullptr) {throw std::out_of_range("RBTreeMap iterator out of range");}if (current->right != nullptr) {current = Node::minimum(current->right);} else {Node* parent = current->parent;while (parent != nullptr && current == parent->right) {current = parent;parent = parent->parent;}current = parent;}return *this;}// 后置++iterator operator++(int) {iterator tmp = *this;++(*this);return tmp;}// 前置--(中序前驱)iterator& operator--() {if (tree->empty()) {throw std::out_of_range("RBTreeMap iterator out of range");}if (current == nullptr) {// end()--时指向最大节点current = Node::maximum(tree->root);} else if (current->left != nullptr) {current = Node::maximum(current->left);} else {Node* parent = current->parent;while (parent != nullptr && current == parent->left) {current = parent;parent = parent->parent;}current = parent;}return *this;}// 比较操作符bool operator==(const iterator& other) const noexcept {return current == other.current;}bool operator!=(const iterator& other) const noexcept {return !(*this == other);}};// const迭代器(禁止修改value)class const_iterator : public iterator {public:using const_reference = const value_type&;using const_pointer = const value_type*;const_iterator(Node* node = nullptr, const RBTreeMap* t = nullptr): iterator(node, t) {}// 重载解引用操作符返回const引用const_reference operator*() const {return iterator::operator*();}// 重载箭头操作符返回const指针const_pointer operator->() const {return iterator::operator->();}};// 迭代器获取方法iterator begin() noexcept {return iterator(Node::minimum(root), this);}iterator end() noexcept {return iterator(nullptr, this);}const_iterator cbegin() const noexcept {return const_iterator(Node::minimum(root), this);}const_iterator cend() const noexcept {return const_iterator(nullptr, this);}
};

祝大家1024程序员节快乐!

http://www.dtcms.com/a/524182.html

相关文章:

  • Spring Boot 使用 Redis 实现消息队列
  • 从renderToString到hydrate,从0~1手写一个SSR框架
  • git报错no new changes、does not match any
  • 公司做网站的费用怎么做账望野作品
  • 【第五章:计算机视觉-项目实战之推荐/广告系统】2.粗排算法-(4)粗排算法模型多目标算法(Multi Task Learning)及目标融合
  • Prometheus 监控系统全维度指南
  • Gradle 增量构建与构建缓存:自定义 Task 如何实现 “只构建变化内容”?
  • 【笑脸惹桃花】1024,阶段性回望与小结
  • 农产品网站建设策划方案网站获取qq号码 代码
  • 网站服务器的作用和功能有哪些福田欧辉是国企吗
  • R语言高效数据处理-变量批量统计检验
  • 云图-地基云图
  • R语言基于selenium模拟浏览器抓取ASCO数据-连载NO.03
  • 多分组火山图绘制-Rscript_version1.0
  • 【开题答辩全过程】以基于Hadoop的电商数据分析系统为例,包含答辩的问题和答案
  • 申威服务器安装Java11(swjdk-11u-9.ky10.sw_64.rpm)详细操作步骤(附安装包)
  • UVa 1596 Bug Hunt
  • 企业数据仓库
  • 如何用 HTML 生成 PC 端软件
  • Apache Spark算法开发指导-特征转换Normalizer
  • 泗洪网站建设变化型网页网站有哪些
  • 渭南哪家公司可以做网站小程序开发制作价格
  • 广州城市建设规划局网站我想给网站网站做代理
  • 蓝蜂 MQTT 网关打通 120 台设备数据,助汽车零部件厂降本 40%​
  • C++异常详细介绍
  • AKKO 3180键盘插到联想笔记本无法使用问题
  • 打造高清3D虚拟世界|零基础学习Unity HDRP高清渲染管线(第六天)
  • 从项目中学习CAN和CANFD报文结构(新手入门)
  • SpreadJS 赋能在线 Excel:协同编辑与精细化权限管控的技术实现
  • RabbitMQ 持久性详解