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

《红黑树驱动的Map/Set实现:C++高效关联容器全解析》

前引:在C++标准库中,mapset作为关联容器的核心,凭借其对数级复杂度的查找效率成为开发利器。然而,理解其底层机制——红黑树的自平衡特性与迭代器设计——才是掌握高阶C++的关键。本文将以零依赖方式实现两套完整容器:基于红黑树的Map<K,V>Set<T>,涵盖节点结构、插入删除的平衡修复、迭代器失效策略等核心环节,并通过性能对比验证其与STL的等效性。无论您是数据结构学习者还是性能优化实践者,本指南将带您穿透抽象接口,直抵关联容器的设计精髓!

目录

【一】map与set结构分析

【二】封装底层红黑树思路讲解

【三】map与set的比较难点

【四】红黑树插入实现

【五】红黑树Find查找

【六】迭代器模板(初始)

(1)Set的普通迭代器

(2)Set的const迭代器

(3)map的普通迭代器

(4)map的const迭代器

【七】迭代器的注意事项(精髓)

【八】map的operator【】

【九】迭代器完整代码

(1)红黑树底层涉及的迭代器

(2)map底层涉及的迭代器

(3)Set底层涉及的迭代器


【一】map与set结构分析

map的数据为pair<key,value>,一个类

set的数据为key,普通的数据类型

两种容器的底层都是红黑树,那么我们需要实现两棵红黑树?根据源码,认为最佳实践方案为:

例如:

           map的模板参数:class int,class string

           底层红黑树接收的模板参数:<class int,class pair<int,string>>

           set的模板参数:class int

           底层红黑树接收的模板参数:<class int,class int>

【二】封装底层红黑树思路讲解

首先是set:

 其次是map:

enum color
{Red,Black
};
//节点结构
template<class V>
struct RBTree
{Map_Set_Node(const V& date):_date(date),_parent(nullptr),_left(nullptr),_right(nullptr),_co(Red){ }//数据V _date;//指针Map_Set_Node*<V> _parent;Map_Set_Node*<V> _left;Map_Set_Node*<V> _right;//颜色enum color _co;
};//红黑树底层
template<class K, class V>
class Map_Set
{typedef Map_Set_Node<V> Node;
public://实现private:Node* root = nullptr;
};

【三】map与set的比较难点

根据map和set模板参数不能直接比较,我们要借助仿函数作为红黑树的另一个模板参数:

随后可以在红黑树中直接根据类类型:Function实例化对象,调用“()”完成比较

【四】红黑树插入实现

注意:比较数据的大小我们是借助仿函数来实现的

//插入
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;
}

【六】迭代器模板(初始)

迭代器所需要实现的工具如下所示:

迭代器里面的模板参数T可以自由变换为 K 或者 pair<K,V>

//迭代器
template<class T>
struct iterator_Tree
{//   K  K          function//   K  pair<K,T>  functiontypedef Red_Black_Tree_Node<T> Node;typedef iterator_Tree<T> iterator;iterator_Tree(Node* cur):_node(cur){}Node* _node;T operator*(){return _node->_date;}bool operator!=(const iterator& s){return _node != s._node;}T* operator->(){return &_node->_date;}iterator& operator++(){//如果右不为空,访问树的最左节点if (_node->_right){Node* curleft = _node->_right;while (curleft->_left){curleft = curleft->_left;}_node = curleft;}else//右为空,访问树的左节点的那个父亲{Node* cur = _node;Node* parent = cur->_parent;while (parent){if (cur == parent->_left){break;}else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;}
};
(1)Set的普通迭代器

注意:必须使用typename,是因为typename 是解决模板中依赖名称歧义的关键工具,确保编译器正确解析嵌套类型或模板成员。忽略它可能导致编译错误或意外行为

但是这样的Set的数据可以修改,不符合Set的结构,因此我们还需要再提供const的迭代器

(2)Set的const迭代器

由于迭代器里面有节点的重定义,需要用到模板参数T,所以我们稍微增加一下迭代器的模板参数

注意:第一个模板参数T是留给节点的,所以我们再多给两个模板参数;

           同时Set的普通和const迭代器是同一个,因此需要更改一下下面的begin和end

(3)map的普通迭代器

但是此时map的左右两个值都是可以修改的,所以我们还需要再模仿库里面将单个first修饰一下

(4)map的const迭代器

const的迭代器的Key和value都不能修改,刚好直接用Set的const迭代器

【七】迭代器的注意事项(精髓)

(1)这里需要将第一个模板参数T给节点,故需要在后面增加模板参数

(2)map迭代器的精髓在于给pair的first增加了const的修饰,同时注意下面的底层也要一样

(3)Set同理

(4)self表示当前的迭代器,不局限于<T,T*,T&>,iterator是普通迭代器

(5)iterator有两个构造函数,第一个是从普通到const的转化构造,第二个是普通的迭代器构造

(6)对于红黑树的++,需要根据cur,parent的位置来不断找父节点

self& operator++()
{//如果右不为空,访问树的最左节点if (_node->_right){Node* curleft = _node->_right;while (curleft->_left){curleft = curleft->_left;}_node = curleft;}else//右为空,访问树的左节点的那个父亲{Node* cur = _node;Node* parent = cur->_parent;while (parent){if (cur == parent->_left){break;}else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;
}

(7)对于红黑树的--,即反过来调整:

如果左不为空:访问下一个左孩子的最右节点

如果左为空:下一个访问的是孩子是父亲右的那个祖先

self& operator--()
{//如果左不为空,访问下一个左孩子的最右节点if (_node->_left){Node* cur = _node->_left;while (cur->_right){cur = cur->_right;}_node = cur;}else//如果左为空,访问下一个孩子是父亲右的那个父亲节点{Node* cur = _node;Node* parent = cur->_parent;while (parent){if (cur == parent->_right){break;}else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;
}

【八】map的operator【】

(1)需要修改红黑树的插入返回值为:pair<iterator,bool>

(2)完成operator【】:先接收insert的返回值,再返回接收的数据的second值

V& operator[](const K& key)
{pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;
}

【九】迭代器完整代码

//迭代器
template<class T,class Sef,class Ref>
struct iterator_Tree
{typedef Red_Black_Tree_Node<T> Node;typedef iterator_Tree<T, Sef, Ref> self;//self是当前类型typedef iterator_Tree<T, T*, T&> iterator;//                    T  T*   T&iterator_Tree(const iterator& it):_node(it._node){}iterator_Tree(Node* cur):_node(cur){}Node* _node;T operator*(){return _node->_date;}bool operator!=(const iterator& s){return _node != s._node;}Sef operator->(){return &_node->_date;}self& operator++(){//如果右不为空,访问树的最左节点if (_node->_right){Node* curleft = _node->_right;while (curleft->_left){curleft = curleft->_left;}_node = curleft;}else//右为空,访问树的左节点的那个父亲{Node* cur = _node;Node* parent = cur->_parent;while (parent){if (cur == parent->_left){break;}else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;}
(1)红黑树底层涉及的迭代器
typedef Red_Black_Tree_Node<T> Node;
typedef iterator_Tree<T, T*, T&> iterator;
typedef iterator_Tree<T,const T*,const T&> const_iterator;
//Begin、end
iterator begin()
{Node* leftMin = root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return leftMin;
}
iterator end()
{return nullptr;
}
const_iterator begin()const
{Node* leftMin = root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return leftMin;
}
const_iterator end()const
{return nullptr;
}
(2)map底层涉及的迭代器
typedef typename RBTree<K, pair<const K, V>, Function>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, Function>::const_iterator const_iterator;
////查找
//Node* find(const K& date)
//{
//	return Tree.find(date);
//}
//迭代器
iterator begin()
{return Tree.begin();
}
iterator end()
{return Tree.end();
}
//迭代器
const_iterator begin()const
{return Tree.begin();
}
const_iterator end()const
{return Tree.end();
}
(3)Set底层涉及的迭代器
typedef typename RBTree<K,const K, Function>::const_iterator iterator;
typedef typename RBTree<K,const K, Function>::const_iterator const_iterator;
//迭代器
const_iterator begin()const
{return Tree.begin();
}
const_iterator end()const
{return Tree.end();
}

【十】unordered_map or _set

对于实用性:我们只需要知道这两种容器的使用相较于正常的map和Set在特性上有些区别:

Set:

  • 无序性:元素存储顺序与插入顺序无关,取决于哈希函数的结果和冲突解决策略。
  • 自定义类型支持:若存储自定义类型,需提供哈希函数和相等比较函数。

map:

  • 无序性:键值对的存储顺序与插入顺序无关,由哈希函数决定
  • 自定义键类型:使用自定义类型作为键时,需提供哈希函数和相等比较函数

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

相关文章:

  • 基于微信小程序的生态农产销售管理的设计与实现/基于C#的生态农产销售系统的设计与实现、基于asp.net的农产销售系统的设计与实现
  • Ubuntu24.04桌面版安装wps
  • 深入分析Linux文件系统核心原理架构与实现机制
  • RS485转profinet网关接M8-11 系列 RFID 读卡模块实现读取卡号输出
  • 元数据与反射:揭开程序的“自我认知”能力
  • 【递归、搜索与回溯算法】穷举、暴搜、深搜、回溯、剪枝
  • 第七章:OLED温湿度显示系统
  • 数据库连接池如何进行空闲管理
  • 光伏板横铺VS竖铺,布局决定发电量!
  • MySQL数据库知识体系总结 20250813
  • iOS混淆工具有哪些?数据安全与隐私合规下的防护实践
  • [ai]垂直agent|意图识别|槽位提取
  • 基于Tensorflow2.15的图像分类系统
  • MySQL三大存储引擎对比:InnoDB vs MyISAM vs MEMORY
  • 【Unity3D】Spine黑线(预乘问题)、贴图边缘裁剪问题
  • Effective C++ 条款39:明智而审慎地使用private继承
  • RabbitMQ:Windows版本安装部署
  • Java研学-RabbitMQ(六)
  • 基于js和html的点名应用
  • B站 韩顺平 笔记 (Day 17)
  • Spring Security 前后端分离场景下的会话并发管理
  • Spring Boot项目调用第三方接口的三种方式比较
  • 【Linux学习|黑马笔记|Day3】root用户、查看权限控制信息、chmod、chown、快捷键、软件安装、systemctl、软连接、日期与时区
  • Go 微服务限流与熔断最佳实践:滑动窗口、令牌桶与自适应阈值
  • NLP学习之Transformer(1)
  • 深度学习(4):数据加载器
  • Redis7学习——Redis的初认识
  • 51c自动驾驶~合集14
  • Docker:快速部署 Temporal 工作流引擎的技术指南
  • 3DM游戏运行库合集离线安装包下载, msvcp140.dll丢失等问题修复