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

【C++篇】STL的关联容器:map和set(下篇):用一颗红黑树同时封装出map和set

文章目录

    • 一、红黑树源码
    • 二、控制红黑树的模板参数
    • 三、提取Key,仿函数的添加
    • 四、普通迭代器的实现
      • operator++实现
    • 五、const迭代器的实现
    • 六、设置Key值不可修改
    • 七、修改insert的返回值 + operator[]的实现
      • 1. 修改insert返回值
      • 2. operator[]
    • 八、封装后的源代码
      • 1. 红黑树
      • 2. 迭代器
      • 3. set
      • 4. map


一、红黑树源码

本文用于封装的红黑树代码(无erase接口):

enum Color
{BLACK,RED
};template<class K, class V>
struct BRTreeNode
{BRTreeNode<K, V>* _left;BRTreeNode<K, V>* _right;BRTreeNode<K, V>* _parent;pair<K, V> _kv;Color _col;BRTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){ }
};template<class K, class V>
class BRTree
{typedef BRTreeNode<K, V> Node;
public:// 在红黑树中插入值为kv的节点bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;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);//新增在左if (parent->_kv.first > kv.first){parent->_left = cur;}else//新增在右{parent->_right = cur;}cur->_parent = parent;//更新颜色//检查是否需要更新颜色,若parent为黑则无需更新while (parent && parent->_col == RED){Node* grandfather = parent->_parent;Node* uncle = nullptr;if (grandfather->_left == parent){uncle = grandfather->_right;}else{uncle = grandfather->_left;}if (uncle && uncle->_col == RED)//uncle存在且为红——变色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;if (grandfather->_parent){if (grandfather->_col == BLACK){break;}else{cur = grandfather;parent = cur->_parent;}}else{grandfather->_col = BLACK;break;}}else//uncle不存在或uncle存在且为黑——旋转+变色{//判断何种情况,该用何种旋转if (grandfather->_right == parent){if (cur == parent->_right){//  g//     p//		  cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//  g//     p//  cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}else{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;}}_root->_col = BLACK;return true;}

二、控制红黑树的模板参数

我们知道,map是KV模型,键值是pair<Key, Value>;而set是K模型,键值是Key
那如何使得一颗KV模型的红黑树可以同时适配这两种模型呢?

首先我们控制map和set传入底层红黑树的模板参数,为了与原红黑树的模板参数进行区分,我们将红黑树第二个模板参数的名字改为T,意为通用参数。

template<class K, class T>
class BRTree

对于set,我们传入Key:

template<class K>
class set
{
public://...
private:BRTree<K, K> _t;
};

对于map,我们传入键值对:

template<class K, class V>
class map
{
public://...
private:BRTree<K, pair<K, V>> _t;
};

细心的朋友可以发现,在红黑树中,第一个模板参数K似乎多余了,因为模板参数T中也含有Key。
那么,可以省略第一个模板参数K吗?

看似无伤淡雅,实则不然。
对于set来说,确实没啥影响,因为set传入红黑树的两个模板参数是一样的。
但对于map来说,部分接口的函数参数是要求直接给出Key值的,例如find和erase。

既然我们改了红黑树的参数了,那节点类也得改改了。

对于模板参数,我们只需T即可,并没有任何用到K的地方。

成员我们依次更改一下类型即可:

template<class T>
struct BRTreeNode
{BRTreeNode<T>* _left;BRTreeNode<T>* _right;BRTreeNode<T>* _parent;T _data;Color _col;BRTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){ }
};

三、提取Key,仿函数的添加

在红黑树的find、insert等的接口中,需要对T类型的数据进行比较操作。
对于set来说,没啥问题;
但对于map,其类型为pair<K, V>,我们且来看看pair的比较运算符的重载是否符合我们的要求(只比较Key(first)的值)。
在这里插入图片描述
可以看到,pair的比较方式是:先比较first,若不为真,就再比较second

显然,这是不符合map的要求的。
既然如此,我们就需要利用仿函数来手动解决了。

目标仿函数功能:提取出pair<Key,Val>中的Key

设置红黑树仿函数模板参数为KeyOfT:

template<class K, class T, class KeyOfT>
class BRTree

在map中:

template<class K, class V>
class map
{//作为内部类//提取keystruct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public://...
private:BRTree<K, pair<K, V>, MapKeyOfT> _t;

虽然对于set无需这般操作,但红黑树必须添加这个模板参数,set也只能无奈“陪跑”了。

template<class K>//仿函数提取keystruct SetKeyOfT{const K& operator()(const K& Key){return Key;}};
class set
{
public://...
private:BRTree<K, K, SetKeyOfT> _t;
};

四、普通迭代器的实现

迭代器的本质就是对指针进行封装,然后重载一些操作符,以至于我们可以像普通指针一样地对数据结构进行操作。

在红黑树中,迭代器必然封装的是节点指针,因此节点指针就是我们迭代器的成员。

template<class T, class T*, class T&>
struct TreeIterator
{typedef BRTreeNode<T> Node;//节点类型typedef TreeIterator<T, Ptr, Ref> Self;//迭代器类型Node* _node;//迭代器所封装的指针
};

接下来我们来一一实现他重要的运算符的重载:

当对迭代器进行解引用操作时,直接返回其对应结点数据的引用即可

T& operator*()
{return _node->_data; //返回结点数据的引用
}

当对迭代器进行->操作时,直接返回其对应结点数据的指针即可

T* operator->()
{return &_node->_data; //返回结点数据的指针
}

当对两迭代器进行比较时,我们还需要==!=运算符

//判断两个正向迭代器是否不同
bool operator!=(const Self& s) const
{return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//判断两个正向迭代器是否相同
bool operator==(const Self& s) const
{return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个
}

上面几个都是“开胃小菜”,真正的大餐来了😏

operator++实现

迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点。

在这里插入图片描述

核心逻辑

  1. 如果当前节点的右子树不为空时,则下一个访问节点应该在右子树的最左节点(最小节点)
  2. 如果当前节点的右子树为空时,则下一个访问节点应该在该节点的的祖先节点中,找到上一个孩子是父亲的左孩子的那个祖先。

代码:

// 迭代器的++操作,让迭代器可以移动
Self& operator++()
{//右子树存在,下一个访问节点是右子树的最左节点if (_node->_right){Node* LeftMax = _node->_right;while (LeftMax->_left){LeftMax = LeftMax->_left;}_node = LeftMax;}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;
}

还有operator--,是同理的,左右反一下就好了,笔者这里留下发挥空间。

迭代器的实现到这基本告一段落了,我们现在来给红黑树、map、set配置迭代器:

红黑树:

template<class K, class T, class KeyOfT>
class BRTree
{typedef BRTreeNode<T> Node;
public://注意这里要typedef在public内,否则就是私有成员了,map和set就无法访问了typedef TreeIterator<T, T*, T&> Iterator;//返回最左节点Iterator _begin(){Node* LeftMax = _root;while (LeftMax->_left){LeftMax = LeftMax->_left;}return Iterator(LeftMax);}Iterator _end(){return Iterator(nullptr);}//…………
};

map:

template<class K, class V>
class map
{//提取keystruct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public:typedef typename BRTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;iterator begin(){return _t._begin();}iterator end(){return _t._end();}
private:BRTree<K, pair<const K, V>, MapKeyOfT> _t;
};

set:

template<class K>
class set
{//仿函数提取keystruct SetKeyOfT{const K& operator()(const K& Key){return Key;}};
public:typedef typename BRTree<K, K, SetKeyOfT>::Iterator iterator;iterator begin() {return _t._begin();}iterator end() {return _t._end();}private:BRTree<K, K, SetKeyOfT> _t;
};

五、const迭代器的实现

const迭代器的实现并不难,难的是后续会引发一系列复杂的问题,后文基本上都是在解决这些问题。

将红黑树传入迭代器的T*T&参数用const修饰

typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator const_iterator;

此时,迭代器的模板参数就不能用具体的参数了,将T*换成PtrT&换成Ref,以便const T*const T&的传入。

template<class T, class Ptr, class Ref>
struct TreeIterator

然后在map和set中也需要和普通迭代器一样去定义

最后在红黑树、map、set中实现const_iterator的begin()、end()接口


六、设置Key值不可修改

我们知道,map和set中的Key是不可修改的,如何做到呢?

对于set,我们使用“障眼法”(记住这个障眼法,后文要考):

typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator iterator;
typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator const_iterator;

对于map,我们巧妙地将传入底层红黑树的pair<K, V>改为pair<const K, V>,方可实现Key值无法修改。


七、修改insert的返回值 + operator[]的实现

我们知道,operator[]的本质是插入,它是调用insert的接口实现的,且insert的返回值被设置为pair<iterator, bool>。(上篇有详细介绍)

1. 修改insert返回值

非常简单,我们将原本的返回值和当前插入节点的迭代器make_pair即可。

然后我们修改map和set的接口:

对于map,直接修改insert返回值即可

pair<iterator, bool> insert(const pair<K, V>& kv)
{return _t._Insert(kv);
}

然而对于set,出大事了!
如果和map一样处理的话,是会报错的,为什么呢?

//错误写法
pair<iterator, bool> insert(const K& key)
{return _t._Insert(key);
}

还记得我们之前使用的妙计“障眼法”吗?
没错,我们现在要来付出代价了🤣
在set中,iterator是“假“”的,它本质上是const_iterator,而我们insert返回值中的iterator是“货真价实”的iterator
错误点:用pair<const_iterator, bool>类型作为返回值类型去返回pair<iterator, bool>类型的值。

别看它们长得像,它们可是两个不同的类型!

解决方案
用一个pair<iterator, bool>类型的变量ret去接收insert的返回值,然后再用iterator去构造成const_iterator,再返回。

//正确写法
pair<iterator, bool> insert(const K& key)
{pair<typename BRTree<K, K, SetKeyOfT>::Iterator, bool> Ret = _t._Insert(key);//用iterator去构造成const_iterator,再返回return pair<iterator, bool>(Ret.first, Ret.second);
}

还没完,我们的迭代器还不支持用普通迭代器去构造const迭代器,我们需要去写一个构造函数:

typedef TreeIterator<T, T*, T&> iterator;TreeIterator(const iterator& it):_node(it._node)
{}

别看这个函数普普通通,其实别有洞天:

  • 当这个迭代器类被实例化为const迭代器,这个函数的作用是一个构造函数。可以用普通迭代器去构造一个const迭代器。
  • 当这个迭代器类被实例化为普通迭代器,这个函数就是一个拷贝构造函数。

2. operator[]

直接返回insert返回的迭代器的value即可

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

八、封装后的源代码

虽然封装过程已经阐述完毕了,但在代码更改过程中还是有许多细节的,下面给出完整封装后的代码。

1. 红黑树

enum Color
{BLACK,RED
};template<class T>
struct BRTreeNode
{BRTreeNode<T>* _left;BRTreeNode<T>* _right;BRTreeNode<T>* _parent;Color _col;T _data;BRTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){ }
};template<class K, class T, class KeyOfT>
class BRTree
{typedef BRTreeNode<T> Node;
public:typedef TreeIterator<T, T*, T&> Iterator;typedef TreeIterator<T, const T*, const T&> const_Iterator;Iterator _begin(){Node* LeftMax = _root;while (LeftMax->_left){LeftMax = LeftMax->_left;}return Iterator(LeftMax);}Iterator _end(){return Iterator(nullptr);}const_Iterator _begin() const{Node* LeftMax = _root;while (LeftMax->_left){LeftMax = LeftMax->_left;}return const_Iterator(LeftMax);}const_Iterator _end() const{return const_Iterator(nullptr);}//查找Node* _find(const K& key){if (_root == nullptr){return nullptr;}Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}// 在红黑树中插入值为val的节点pair<Iterator, bool> _Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(_root, true);}KeyOfT kot;Node* cur = _root;Node* parent = nullptr;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(cur, false);}}cur = new Node(data);Node* newnode = cur;//新增在左if (kot(parent->_data) > kot(data)){parent->_left = cur;}else//新增在右{parent->_right = cur;}cur->_parent = parent;//更新颜色//检查是否需要更新颜色,若parent为黑则无需更新while (parent && parent->_col == RED){Node* grandfather = parent->_parent;Node* uncle = nullptr;if (grandfather->_left == parent){uncle = grandfather->_right;}else{uncle = grandfather->_left;}if (uncle && uncle->_col == RED)//uncle存在且为红——变色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;if (grandfather->_parent){if (grandfather->_col == BLACK){break;}else{cur = grandfather;parent = cur->_parent;}}else{grandfather->_col = BLACK;break;}}else//uncle不存在或uncle存在且为黑——旋转+变色{//判断何种情况,该用何种旋转if (grandfather->_right == parent){if (cur == parent->_right){//  g//     p//		  cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//  g//     p//  cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}else{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;}}_root->_col = BLACK;return make_pair(newnode, true);}

2. 迭代器

template<class T, class Ptr, class Ref>
struct TreeIterator
{typedef BRTreeNode<T> Node;typedef TreeIterator<T, Ptr, Ref> Self;typedef TreeIterator<T, T*, T&> iterator;Node* _node;TreeIterator(const iterator& it):_node(it._node){ }TreeIterator(Node* node):_node(node){ }Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}// 迭代器的++操作,让迭代器可以移动Self& operator++(){//右子树存在,下一个访问节点是右子树的最左节点if (_node->_right){Node* LeftMax = _node->_right;while (LeftMax->_left){LeftMax = LeftMax->_left;}_node = LeftMax;}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;}Self operator++(int){Self tmp(*this);//右子树存在,下一个访问节点是右子树的最左节点if (_node->_right){Node* LeftMax = _node->_right;while (LeftMax->_left){LeftMax = LeftMax->_left;}_node = LeftMax;}else//右子树不存在,沿着祖先路径往上寻找存在左孩子的那个祖先{Node* cur = _node;Node* parent = cur->_parent;;while (parent){if (cur == parent->_left)break;else{cur = parent;parent = parent->_parent;}}_node = parent;}return tmp;}Self& operator--(){//左子树存在,下一个访问节点是左子树的最右节点if (_node->_left){Node* RightMax = _node->_left;while (RightMax->_right){RightMax = RightMax->_right;}_node = RightMax;}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;}Self operator--(int){Self tmp(*this);//左子树存在,下一个访问节点是左子树的最右节点if (_node->_left){Node* RightMax = _node->_left;while (RightMax->_right){RightMax = RightMax->_right;}_node = RightMax;}else//左子树不存在,沿着祖先路径往上寻找存在右孩子的那个祖先{Node* cur = _node;Node* parent = cur->_parent;;while (parent){if (cur == parent->_right)break;else{cur = parent;parent = parent->_parent;}}_node = parent;}return tmp;}// 让迭代器能够支持比较bool operator!=(const Self& s)const{return _node != s._node;}bool operator==(const Self& s)const{return _node == s._node;}
};

3. set

#pragma once#include"BRTree.h"namespace Surplus
{template<class K>class set{//仿函数提取keystruct SetKeyOfT{const K& operator()(const K& Key){return Key;}};public:typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator iterator;typedef typename BRTree<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 BRTree<K, K, SetKeyOfT>::Iterator, bool> Ret = _t._Insert(key);return pair<iterator, bool>(Ret.first, Ret.second);}private:BRTree<K, K, SetKeyOfT> _t;};
}

4. map

#pragma once#include"BRTree.h"namespace Surplus
{template<class K, class V>class map{//提取keystruct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename BRTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename BRTree<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;}pair<iterator, bool> insert(const pair<K, V>& kv){return _t._Insert(kv);}private:BRTree<K, pair<const K, V>, MapKeyOfT> _t;};}
http://www.dtcms.com/a/300339.html

相关文章:

  • CCFRec-人大高瓴-KDD2025-序列推荐中充分融合协同信息与语义信息
  • Item13:以对象管理资源
  • 人工智能论文辅导:Prompt Engineering(特征工程)
  • 倍思鹿数值仿真-实现各类提示、快捷键功能,提高工作效率
  • Android Jetpack 组件库 ->Jetpack Navigation (下)
  • 通过不同坐标系下的同一向量,求解旋转矩阵
  • 深度学习入门(2)
  • 实验-OSPF多区域
  • 告别Vite脚手架局限!MixOne Beta测试招募:你的需求,我们来实现
  • 【Java】基础概念-构造函数详解
  • [Python] -进阶理解7- Python中的内存管理机制简析
  • 基于springboot的在线数码商城/在线电子产品商品销售系统的设计与实现
  • (二)使用 LangChain 从零开始构建 RAG 系统 RAG From Scratch
  • 7月26号打卡
  • Unity GenericMenu 类详解
  • 技术 — 资本双螺旋:AI 时代的投资浪潮与技术突破
  • 模型训练部署流程
  • 电磁兼容三:电磁干扰三要素详解
  • 【大模型框架】LangChain入门:从核心组件到构建高级RAG与Agent应用
  • 系统性学习C语言-第二十三讲-文件操作
  • 渗透艺术系列之Laravel框架(一)
  • Effective C++ 条款03:尽可能使用const
  • 检验类设备中,交叉导轨如何确保加样精度?
  • mysql-数据表-DDL语句
  • Triton源代码分析 - 目录
  • freeRTOS 静态创建任务
  • TIM 输入捕获
  • pip, github 突然连不上?报错和解决方法如下
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-29,(知识点:非易失存储器)
  • 【AI周报】2025年7月26日