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

【C++高阶五】mapset对红黑树的封装

【C++高阶五】map&set对红黑树的封装

  • 1.将红黑树封装为泛型
  • 2.泛型红黑树的修改和Find接口
  • 3.迭代器
  • 4.insert接口
  • 5.map的operator[]接口
  • 6.总代码展示

C++的STL库中把红黑树封装为了两个容器mapset
本博客将基于红黑树来实现map和set的封装
如果不了解红黑树,可见博客【C++高阶四】红黑树

1.将红黑树封装为泛型

我们现有的红黑树结构如下:

enum color//颜色枚举
{RED,BLACK
};template<class K, class V>
struct RBTreeNode//红黑树节点结构体
{RBTreeNode<K, V>* _left;//左指针RBTreeNode<K, V>* _right;//右指针RBTreeNode<K, V>* _parent;//父亲指针color _color;//颜色标记位pair<K, V> _kv;//KV值RBTreeNode(const pair<K, V>& KV)//初始化列表构造:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(KV), _color(RED){}
};template<class K, class V>
class RBTree//红黑树
{typedef RBTreeNode<K, V> Node;//定义节点为Node
public:private:Node* _root = nullptr;
};

这颗红黑树有一个问题,它的节点,存储的值是pair<K, V> _kv,是以键值对的形式来存储数据。但是在STL中,set只是存储key值的,只有map才存储键值对。如果我们要让红黑树可以同时作为map和set的底层,那么我们就要让这个红黑树的节点可以存储更多种类型的数据,所以要让节点RBTreeNode内部存储泛型:

template<class T>
struct RBTreeNode//红黑树节点结构体
{RBTreeNode<T>* _left;//左指针RBTreeNode<T>* _right;//右指针RBTreeNode<T>* _parent;//父亲指针color _color;//颜色标记位T _data;//data值RBTreeNode(const T& data)//初始化列表构造:_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _color(RED){}
};

把原先的模板template<class K, class V>换成了template<class T>,并把pair<K, V> _kv;改为了T _data

当我们需要存储键值对,那么T就是pair<K, V>
当我们只存储key值,那么T就是K

相应的,我们的RBTree本体也要修改一下:

template<class T>
class RBTree//红黑树
{typedef RBTreeNode<T> Node;//定义节点为Node
public:private:Node* _root = nullptr;
};

现在尝试封装红黑树,写出map和set的基本框架:
my_set.h

template<calss K>
class set
{
public:private:RBTree<K> _tree;
}

my_map.h:

template<class K,class V>
class map
{
public:
private:RBTree<pair<K,V>> _tree;
}

经过这一层封装,我们的map和set的基本框架就有了
但是有一个小问题:map和set中的key值是不可以修改的,所以我们要给K的类型加上const修饰,不过要注意map中的value是可以修改的,所以不是给pair加上const修饰,而是只修饰K
my_set.h

template<calss K>
class set
{
public:private:RBTree<const K> _tree;
}

my_map.h:

template<class K,class V>
class map
{
public:
private:RBTree<pair<const K,V>> _tree;
}

2.泛型红黑树的修改和Find接口

原本红黑树的Find接口:

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;
}
  1. 第一个问题:我们已经把模板从template<class K, class V>换成了template<class T>,现在已经没有K这个类型了,意味着我们在给Find传参的时候,不可以直接传const K& key
    但我们在红黑树中查找一定是需要key的,所以我们要在模板参数中加一个K,也就是template<class K, class T>,前面的K用于传入key的类型,后面的T用于传入红黑树存储的数据类型

所以修改后的泛型红黑树框架如下:

template<class K ,class T>
class RBTree//红黑树
{typedef RBTreeNode<T> Node;//定义节点为Node
public:private:Node* _root = nullptr;
};
  1. 第二个问题:我们对红黑树的节点类RBTreeNode改造后,其内部存储的已经不是_kv了,而是_data,对于set而言,这个_data是没有first这个成员的,因此我们不能直接通过_kv.first这种方式来访问key;但是对于map而言,这个key还是_data中的成员变量first。也就是map和set取用key值的方式不同,我们无法统一处理

为了用统一的方式从_data中获得 map和set 各自的key,我们在红黑树的外部写仿函数,然后在仿函数的内部返回key值
所以给红黑树传入第三个模板参数KeyOfT

template<class K ,class Tclass KeyOfT>//KeyOfT是仿函数,用于得到T类对象中的key
class RBTree//红黑树
{typedef RBTreeNode<T> Node;//定义节点为Node
public:private:Node* _root = nullptr;
};

修改完红黑树后,要分别给set和map类添加仿函数:
set

template<calss K>
class set
{
public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};
private:RBTree<K,K, SetKeyOfT> _tree;
}

map:

template<class K,class V>
class map
{
public:struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};
private:RBTree<K,pair<const K, V>, MapKeyOfT> _tree;
}

再对Find函数进行改造:

bool Find(const K& key)
{KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if(kot(cur->_data) > key){cur = cur->_left;}else{return true;}}return false;
}

3.迭代器

先搭建出迭代器iterator的基本框架:

template<class T,class Ptr,class Ref>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T,Ptr,Ref> Self;Node* _node;RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};

中序遍历可以通过递归按照“左子树 - 根 - 右子树”的顺序遍历,但迭代器该如何移动?
在这里插入图片描述
当迭代器指向蓝色方框位置时,说明迭代器就遍历到了这里,说明左子树已经遍历完成,遍历蓝色方框(根)后就要遍历右子树(找到右子树的最左节点)
蓝色方框移动:
在这里插入图片描述

当迭代器指向蓝色方框位置时,右子树为空,说明以蓝色框为根的整颗子树都遍历完毕了,也说明以紫色框为根的整颗子树都遍历完毕了(中序遍历,根节点一定在右子树前遍历完),那么我们就要向上移动蓝色方框,直到蓝色方框是其父节点的左子树时,将蓝色方框向上移动给其父节点
蓝色方框移动:

在这里插入图片描述

  1. 如果迭代器当前节点的右子树不为空,遍历右子树(找到右子树的最左节点)
  2. 如果迭代器当前节点的右子树为空,向上找第一个满足条件的节点:该节点其左子树是自己的前一个节点

代码如下:

Self& operator++()
{if (_node->_right){Node* cur = _node->_right;while (cur->left){cur = cur->_left;}_node = cur;}else{Node* cur = _node;Node* parent = _node->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}

operator–同理,只是规律相反:

  1. 如果迭代器当前节点的左子树不为空,遍历左子树(找到左子树的最右节点)
  2. 如果迭代器当前节点的左子树为空,向上找第一个满足条件的节点:该节点的右子树是自己的前一个节点
Self& operator--()
{if (_node->_left){Node* cur = _node->_left;while (cur->right){cur = cur->_right;}_node = cur;}else{Node* cur = _node;Node* parent = _node->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}

接下来我们要把迭代器的类封装进RBTree中,首先在RBTree中定义出iterator和const_iterator:

typedef RBTreeIterator<T, T*, T&> iterator;
typedef RBTreeIterator<T, const T*, const T&> const_iterator;

接着写出begin,end的iterator接口:

iterator begin()//中序遍历的第一个节点是整棵树的最左侧节点
{Node* cur = _root;while (cur->_left){cur = cur->_left;}return iterator(cur);
}iterator end()
{return iterator(nullptr);
}

const_iterator的begin和end同理:

const_iterator begin()const
{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_iterator(cur);
}const_iterator end()const
{return const_iterator(nullptr);
}

我们再封装出map和set的迭代器,直接复用RBTree的迭代器接口即可:
mySet.h:

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin()
{return _tree.begin();
}iterator end()
{return _tree.end();
}const_iterator begin()const
{return _tree.begin();
}const_iterator end()const
{return _tree.end();
}

myMap.h:

typedef typename RBTree<K, pair<K,V>, MapKeyOfT>::iterator  iterator;
typedef typename RBTree<K, pair<K,V>, MapKeyOfT>::const_iterator  const_iterator;iterator begin()
{return _tree.begin();
}iterator end()
{return _tree.end();
}const_iterator begin()const
{return _tree.begin();
}const_iterator end()const
{return _tree.end();
}

typedef的时候,由于RBTree是一个模板,我们到模板的域中访问了变量,而编译器无法区分模板中const_iterator的是变量还是类型,所以编译器会默认把它视为一个变量。如果它是一个类型,那么我们要明确告诉编译器,即通过typename 关键词,否则const_iterator会被识别为一个变量,而不是一个类型

由于STL中,find接口返回的是迭代器,所以我们还要把find的返回值改为迭代器,而不是使用布尔值返回

iterator Find(const K& key)
{KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return iterator(cur);}}return end();
}

4.insert接口

在map和set中,insert的返回值比较特殊,其会返回pair<iterator, bool>

  1. 如果插入值data原先存在,此时iterator指向原先的data节点,bool值返回false表示插入失败
  2. 如果插入值data原先不存在,此时iterator指向新插入的data节点,bool值返回true表示插入成功

RBTree:

pair<Node*,bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_color = BLACK;//根节点是黑的return make_pair(_root,true);}Node* cur = _root;Node* parent = nullptr;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(cur, false);//相等就插入失败}}cur = new Node(data);Node* newnode = cur;//cur会不停变动,但最后返回的是新插入的那个节点//链接if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}//此时new出来的节点的parent还指向空cur->_parent = parent;//新插入节点的父亲 存在 且是 红色,再调整while (parent && parent->_color == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left)//新插入的节点其父节点在子树左侧{Node* uncle = grandfather->_right;if (uncle && uncle->_color == RED)//uncle存在且为红{uncle->_color = parent->_color = BLACK;grandfather->color = RED;cur = grandfather;parent = cur->_parent;}else//uncle不存在 或uncle是黑色{if (cur = parent->left)//grandfather和parent和cur是线性{RotateR(grandfather);grandfather->color = RED;parent->color = BLACK;}else//grandfather和parent和cur是折线型{RotateL(parent);RotateR(grandfather);cur->color = BLACK;grandfather->_color = RED;}break;//旋转后一定满足要求,跳出循环}}else//新插入的节点其父节点在子树右侧{Node* uncle = grandfather->_left;if (uncle && uncle->_color == RED)//uncle 存在 且为 红色{uncle->_color = parent->_color = BLACK;grandfather->color = RED;cur = grandfather;parent = cur->_parent;}else//uncle 不存在 或为 黑色{if (cur = parent->right)//grandfather和parent和cur是线性{RotateL(grandfather);grandfather->color = RED;parent->color = BLACK;}else//grandfather和parent和cur是折线型{RotateR(parent);RotateL(grandfather);cur->color = BLACK;grandfather->_color = RED;}break;//旋转后一定满足要求,跳出循环}}}_root->color = BLACK;return make_pair(newnode,true);
}

map:

pair<Node*, bool> insert(const pair<K, V>& kv)
{return _tree.Insert(kv);
}

set:

pair<Node*, bool> insert(const K& key)
{return _tree.Insert(key);
}

5.map的operator[]接口

map还重载了[],这个重载比较复杂,但是非常有用,其声明:

V& operator[] (const K& key);

作用:接受一个key值,然后返回这个key对应的value的引用
等价于下列代码:

(*((this->insert(make_pair(key,V()))).first)).second

我们将以上代码拆解为四个部分解读
第一部分:

make_pair(key, V( ))

这是在利用参数k,通过make_pair()构造一个pair,这个pairvalue使用了V( )(V就是value的类型)来调用默认构造,最后就得到了一个pair<key, value>

第二层:

this->insert( )

上一层我们构造了一个pair<key, value>,然后它作为参数传入到这个insert()中,相当于把刚刚构造的节点插入进map中。map的插入后,不论成功与否,都会返回一个pair<Node*, bool>,Node*用于指向key,bool用于标识插入是否成功。所以这一层最后得到了一个pair,分别存储了指向key的指针和bool值

第三层:

( ).first

上一层中我们得到了pair<Node*, bool>,这一层访问它的first,也就是访问了Node*,所以这一层得到了指向key值的地址

第四层:

(*( )).second

上一层拿到了指向key的地址,这一层先对地址解引用*( ),此时就得到了一个map的节点。而map的节点是pair<key, value>,所以我们解引用得到了一个pair,随后通过( ).second访问pair<key, value>second,也就是value。最后返回这个value的引用

operator[]实现:

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

operator[ ]的作用:

  1. 插入一个key值:
    dict["left"];
    以上语句在dict中插入了一个key = "left"的节点,尽管其没有value
  2. 插入一对key - value
    dict["left"] = "左边";
    由于operator[ ]返回的是对应的引用,因此我们可以直接給返回值赋值,此时我们就插入了一个节点key = "left" value = "左边"
  3. 修改key对应的value
    dict[“coffe”] = "咖啡";
    如果我们的dict原先就存在key = "coffe"的节点,以上代码可以修改这个keyvalue
  4. 得到key对应的value
    cout << dict["coffe"] << endl;
    由于我们拿到的是value的引用,我们也可以把它作为一个值赋值给别人或者输出

由于我们拿到的是value的引用,我们也可以把它作为一个值赋值给别人或者输出

6.总代码展示

RBTree.h:

enum color//颜色枚举
{RED,BLACK
};template<class T>
struct RBTreeNode//红黑树节点结构体
{RBTreeNode<T>* _left;//左指针RBTreeNode<T>* _right;//右指针RBTreeNode<T>* _parent;//父亲指针color _color;//颜色标记位T _data;//data值RBTreeNode(const T& data)//初始化列表构造:_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _color(RED){}
};template<class T, class Ptr, class Ref>
struct RBTreeIterator//红黑树迭代器
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ptr, Ref> Self;RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_right){Node* cur = _node->_right;while (cur->left){cur = cur->_left;}_node = cur;}else{Node* cur = _node;Node* parent = _node->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self& operator--(){if (_node->_left){Node* cur = _node->_left;while (cur->right){cur = cur->_right;}_node = cur;}else{Node* cur = _node;Node* parent = _node->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}public:Node* _node;
};//set->RBTree<K,K,SetKeyOfT> _tree;
//map->RBTree<K,pair<const K,T>,MapKeyOfT> _tree;
template<class K, class T,class KeyOfT>
class RBTree//红黑树
{typedef RBTreeNode<T> Node;//定义节点为Node
public:typedef RBTreeIterator<T, T*, T&> iterator;typedef RBTreeIterator<T, const T*, const T&> const_iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}const_iterator begin()const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_iterator(cur);}const_iterator end()const{return const_iterator(nullptr);}pair<Node*,bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_color = BLACK;//根节点是黑的return make_pair(_root,true);}Node* cur = _root;Node* parent = nullptr;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(cur, false);//相等就插入失败}}cur = new Node(data);Node* newnode = cur;//cur会不停变动,但最后返回的是新插入的那个节点//链接if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}//此时new出来的节点的parent还指向空cur->_parent = parent;//新插入节点的父亲 存在 且是 红色,再调整while (parent && parent->_color == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left)//新插入的节点其父节点在子树左侧{Node* uncle = grandfather->_right;if (uncle && uncle->_color == RED)//uncle存在且为红{uncle->_color = parent->_color = BLACK;grandfather->color = RED;cur = grandfather;parent = cur->_parent;}else//uncle不存在 或uncle是黑色{if (cur = parent->left)//grandfather和parent和cur是线性{RotateR(grandfather);grandfather->color = RED;parent->color = BLACK;}else//grandfather和parent和cur是折线型{RotateL(parent);RotateR(grandfather);cur->color = BLACK;grandfather->_color = RED;}break;//旋转后一定满足要求,跳出循环}}else//新插入的节点其父节点在子树右侧{Node* uncle = grandfather->_left;if (uncle && uncle->_color == RED)//uncle 存在 且为 红色{uncle->_color = parent->_color = BLACK;grandfather->color = RED;cur = grandfather;parent = cur->_parent;}else//uncle 不存在 或为 黑色{if (cur = parent->right)//grandfather和parent和cur是线性{RotateL(grandfather);grandfather->color = RED;parent->color = BLACK;}else//grandfather和parent和cur是折线型{RotateR(parent);RotateL(grandfather);cur->color = BLACK;grandfather->_color = RED;}break;//旋转后一定满足要求,跳出循环}}}_root->color = BLACK;return make_pair(newnode,true);}void RotateL(Node* parent)//左单旋{Node* subR = parent->_right;Node* subRL = subR->_ + left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}Node* parentparent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (parentparent->_left = parent){parentparent->_left = subR;}else{parentparent->_right = subR;}subR->_parent = parentparent;}subR->_bf = parent->_bf = 0;}void RotateR(Node* parent)//右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}subL->_bf = parent->_bf = 0;}iterator Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return iterator(cur);}}return end();}
private:Node* _root = nullptr;
};

my_set.h:

#include "RBTree.h"namespace lhc
{template<class K>class set{public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;iterator begin(){return _tree.begin();}iterator end(){return _tree.end();}const_iterator begin()const{return _tree.begin();}const_iterator end()const{return _tree.end();}pair<Node*, bool> insert(const K& key){return _tree.Insert(key);}private:RBTree<K,K, SetKeyOfT> _tree;};
}

my_map.h:

#include "RBTree.h"namespace lhc
{template<class K, class V>class map{public:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef typename RBTree<K, pair<K,V>, MapKeyOfT>::iterator  iterator;typedef typename RBTree<K, pair<K,V>, MapKeyOfT>::const_iterator  const_iterator;iterator begin(){return _tree.begin();}iterator end(){return _tree.end();}const_iterator begin()const{return _tree.begin();}const_iterator end()const{return _tree.end();}V& operator[](const K& key){pair<Node*, bool>& ret = insert(make_pair(key, V()));return ret.first->second;}pair<Node*, bool> insert(const pair<K, V>& kv){return _tree.Insert(kv);}private:RBTree<K,pair<const K, V>, MapKeyOfT> _tree;};
}
http://www.xdnf.cn/news/17302.html

相关文章:

  • Nestjs框架: 接口安全与响应脱敏实践 --- 从拦截器到自定义序列化装饰器
  • 【昇腾】Atlas 500 A2 智能小站制卡从M.2 SATA盘启动Ubuntu22.04系统,重新上电卡死没进系统问题处理_20250808
  • 大语言模型提示工程与应用:提示词基础使用方式
  • Redis原理,命令,协议以及异步方式
  • 分布式膛压应变测量系统
  • 中国电信清华:大模型驱动的具身智能发展与挑战综述
  • BGP综合实验
  • 代码随想录算法训练营第三十八天、三十九天|动态规划part11、12
  • 考研复习-计算机组成原理-第四章-指令系统
  • 机器人焊机智能流量调节
  • 内容分发机制研究:实测一款多源短视频聚合App
  • isulad + harbor私有仓库登录
  • 从安卓兼容性困境到腾讯Bugly的救赎:全链路崩溃监控解决方案-卓伊凡|bigniu
  • 机器学习概念1
  • STM32HAL 快速入门(二):用 CubeMX 配置点灯程序 —— 从工程生成到 LED 闪烁
  • 服务器登上去,显示 failed to send WATCHDOG 重启有效吗?
  • Android 之 ANR问题的全面解析与优化方案
  • Godot ------ 制作属于自己的卡牌
  • 讲一讲@ImportResource
  • C++ WonderTrader源码分析之自旋锁实现
  • 宁商平台税务新举措:合规护航,服务暖心
  • 视频质量检测中准确率↑32%:陌讯多模态评估方案实战解析
  • Web Worker 性能革命:让浏览器多线程为您的应用加速
  • 使用 Gulp 替换 XML 文件内容
  • 解决MinIO上传图片后返回URL无法访问的问题
  • 从深度伪造到深度信任:AI安全的三场攻防战
  • web端-登录页面验证码的实现(springboot+vue前后端分离)超详细
  • 5- Python 网络爬虫 — 如何突破 JS 动态渲染壁垒?工具原理与实战全解析
  • K8s 常见故障案例分析
  • KLA/TENCOR ALTAIR 8900