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

详细介绍:扒透 STL 底层!map/set 如何封装红黑树?迭代器逻辑 + 键值限制全手撕----《Hello C++ Wrold!》(23)--(C/C++)

详细介绍:扒透 STL 底层!map/set 如何封装红黑树?迭代器逻辑 + 键值限制全手撕----《Hello C++ Wrold!》(23)--(C/C++)

文章目录

  • 前言
  • map和set的封装
  • 底层红黑树的模拟实现
  • 迭代器的模拟实现

前言

你是不是也有过这种 “知其然不知其所以然” 的困惑:
用 map 存键值对、用 set 去重排序时很顺手,但一被问 “map 的 [] 怎么既插入又访问”“set 为啥不能改元素”“它们底层的红黑树到底存的啥”,就瞬间卡壳?甚至看 STL 源码时,被 “KeyOfT”“迭代器 ++ 逻辑” 绕得晕头转向?

其实 map 和 set 的本质,就是对红黑树的 “定制化封装” —— 红黑树是 “通用骨架”,map 和 set 通过 “提取键的规则(KeyOfT)”“迭代器权限控制”“键值修改限制”,分别适配了 “键值对存储” 和 “单一元素去重” 的需求。

这篇内容不搞虚的,直接从 “红黑树如何适配 map/set” 切入:拆透 map 的 KeyOfT 怎么提取 pair 的 first,set 为啥要用 const 迭代器锁死修改;讲清迭代器 +±- 的底层逻辑(右子树找最左 vs 回溯父节点);还附上手撕的红黑树封装、迭代器实现代码 —— 不管你是想搞懂 STL 底层,还是面试被问 “map/set 原理” 时想答到点子上,这篇都能让你把 “封装逻辑” 嚼得明明白白。

map和set的封装

这俩在封装的时候对红黑树里面的K,T的处理:

map的话K是存Key,T是搞的pair<Key,Value> --这两个Key是一样的哈

set的话K是存Key,T也是存Key–这两个Key是一样的哈(第二个T存东西单纯为了陪跑)

在这里插入图片描述

对于set的话KT都不能被修改–因为都是Key

对于map的话K不能被修改,T里面的value可以被修改

对于键和值的理解:

对于map:键用来排序查找啥的,值用来存信息

对于set:键承担了所有

map:
namespace renshen
{
template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}//一般不在里面直接封装比较,而是把要比较的值给出去--这样灵活一些};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;//这里必须加typename,不然编译器不指定RBTree<K, pair<K, V>, MapKeyOfT>::iterator是个类型//因为,类模板只有在被实例化的时候,编译器才会真正知道其中各种类型和表达式的含义//之后在源文件的话就可以eg:renshen::map<int,int>::iterator mit = ......//如果iterator被typedef过的话,RBTree<K, pair<const K, V>, MapKeyOfT>::iterator还是RBTree里面的iteratortypedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}//V()就是为了没有这个key的话就插入,有的话就返回已有的那个值pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;//这个const用的好,让普通迭代器的value可以修改,const迭代器啥也不能修改};}

map[]可以插入和修改和访问–string[]不能用来插入

set:
namespace renshen
{
template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;//注意这里typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}pair<iterator, bool> insert(const K& key){pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);return pair<iterator, bool>(ret.first, ret.second);}private:RBTree<K, K, SetKeyOfT> _t;};}

底层红黑树的模拟实现

enum Colour
{
RED,
BLACK
};
template<class T>struct RBTreeNode{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}};template<class K, class T, class KeyOfT>struct RBTree{typedef RBTreeNode<T> Node;public:typedef __TreeIterator<T, T*, T&> iterator;typedef __TreeIterator<T, const T*, const T&> const_iterator;iterator begin(){Node* leftMin = _root;while (leftMin && leftMin->_left)//这里前面那个leftMin是防止这个树是空树{leftMin = leftMin->_left;}//最左边的那个节点return iterator(leftMin);//调用了iterator的构造函数}iterator end(){return iterator(nullptr);}const_iterator begin() const{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return const_iterator(leftMin);}const_iterator end() const{return const_iterator(nullptr);}Node* Find(const K& key){Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < kot(data))//这样搞比较好些{parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);//返回已经存在的那个节点的迭代器}}cur = new Node(data);cur->_col = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;// u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else // u不存在 或 存在且为黑{if (cur == parent->_left){//     g//   p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   p//		cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else // parent == grandfather->_right{Node* uncle = grandfather->_left;// u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){// g//	  p//       cRotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{// g//	  p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}//旋转相关的代码就参考AVL树的就行了private:Node* _root = nullptr;};

库里面的红黑树和这里的模拟实现的区别:

库里面其实还有个头节点,头节点的父亲是红黑树的根节点,头节点的left是红黑树里面最小的数,头节点的right是红黑树里面最大的数,然后根节点的父亲也是头节点

注意:区分头节点和根节点!

迭代器的模拟实现

这里的迭代器的beginend的位置要注意:

begin是最左边的那个节点

end是根节点的父亲–nullptr–这个beginend是在底层红黑树那里定义的

这里的迭代器的++:

1.当前节点的右子树不为空,则访问右树的最左节点

2.当前节点的右子树为空,如果当前节点的父亲是其父亲的左孩子,就访问当前节点的父亲,不然就把当前节点的父亲变成当前节点,直到遇到那种情况或者当前节点成根节点的父亲了

这里迭代器的--:

1.当前节点的左子树不为空,则访问左树的最右节点

2.当前节点的右子树为空,如果当前节点的父亲是其父亲的右孩子,就访问当前节点的父亲,不然就把当前节点的父亲变成当前节点,直到遇到那种情况或者当前节点变成根节点的父亲了

引申:普通迭代器可以隐式转换为const迭代器

   const迭代器不能隐式转换成普通迭代器,强转有时可以
template<class T, class Ptr, class Ref>struct __TreeIterator{typedef RBTreeNode<T> Node;typedef __TreeIterator<T, Ptr, Ref> Self;typedef __TreeIterator<T, T*, T&> Iterator;__TreeIterator(const Iterator& it):_node(it._node){}Node* _node;__TreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}//跟链表的那个处理有点像哈Ptr operator->(){return &_node->_data;}//跟链表的那个处理有点像哈bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node != s._node;}Self& operator--(){if (_node->_left){Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator++(){if (_node->_right){// 右树的最左节点(最小节点)Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = cur->_parent;parent = parent->_parent;}_node = parent;}return *this;}};
http://www.jsqmd.com/news/6294/

相关文章:

  • 死锁的处理策略-预防死锁
  • 跨网文件安全交换系统:提升数据传输安全性和合规性
  • ArcGIS 公众号推荐
  • 跨网文件交换系统:数字化时代企业与机构的数据安全传输利器
  • 缩放 div
  • Redis从零讲解 - 详解
  • 【2025-09-29】团队合作
  • 杂凑算法学习笔记
  • pg库支持扩展postgis
  • kuboard部署启用3个etcd(k8s单个master)
  • odoo18应用、队列服务器分离(SSHFS) - 详解
  • 数据库服务分布架构(MyCAT)
  • 题解:P14038 [PAIO 2025] Adventure Plan
  • 20231414_王仕琪_密码技术密码杂凑算法学习笔记
  • 调度算法易错概念总结
  • 堆设置了8G,java进程却占用了12G内存
  • Huxe 推出主动式 AI 音频服务,无感内容消费;OpenAI 推出 ChatGPT Pulse:主动提供个性化信息丨日报
  • C++学习:C++类型转换专栏 - 指南
  • NAFNet (Simple Baselines for Image Restoration) 阅读笔记 - 教程
  • 解决OpenWrt系统上出现“git: remote-https is not a git command...”的问题
  • 密码技术概论
  • IntelliJ IDEA 中 Shared Build Process Heap Size 的重要性与配置
  • 企业数字化转型战略规划:从愿景到落地的完整路径
  • 贝叶斯学习笔记 - 详解
  • 设计模式-结构性设计模式(针对类与对象的组织结构) - 指南
  • 凯利公式在期货交易中的应用
  • 在确定性之外:关于AGI与ASI愿景的一些补充思考 (附阿里CEO云栖大会演讲全文) - 指南
  • AT_agc054_c [AGC054C] Roughly Sorted
  • Ubuntu 24和25配置apt国内源
  • 完整教程:医疗编程AI技能树与培训技能树报告(国内外一流大学医疗AI相关专业分析2025版,上)