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

C++ STL之封装红黑树实现map/set

一.核心源码分析

经过map/set的使用以及红黑树的讲解,本文将通过分析源码来模拟实现红黑树封装的map/set

1.map和set包含的头文件

可以看到set中包含了set和mutiset的头文件,因此我们在使用时只需要包含一个set头文件就能使用set和mutiset了,map和mutimap也同理。

// 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>

2.set和map的头文件解析

1.我们首先看set。可以看到set的模板参数有三个值,分别是Key(键),比较器Compare以及空间配置器Alloc。我们已经很熟悉set的结构,它结点存储的是一个Key(键),并且不可被修改。但是观察源码发现,先贤们却将Key重定义了两份:一个为key_type,一个为value_type——键值对的形式。这其实是为了泛型编程做出的妥协,大佬们不愿意将同一份红黑树的代码写Key和Key—Value两份,这里的两个Key其实是为了和map统一写法做出的妥协。等稍后讲map时我们就会发现,源码的Key,Value不再是传统意义上的Key,Value了。

2.接着看map。它的模板参数有四个,Key(键),T(值),比较器和空间配置器。这里再看map的重定义就会清晰许多,将Key重定义为key_type,T定义为mapped_type,而pair类型内就封装了前两种类型,并且重定义为value_type。

// stl_set.h
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
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
}

3.tree的头文件解析

1.node_base即作为基类的结点类,而下面的node类则是继承了这个类。我们可以看到node_base中定义了红黑树的颜色,父结点指针,左右孩子指针。它的派生类node类则有一个模板参数Value,正是用于我们上面提到的——通过一棵红黑树实现的泛型编程,判断这里的Value传入的是什么类型(set的key还是map的key-value)。它的作用十分类似我们在讲链表迭代器时的Ref模板参数,它可以传入T*也可以传入const T*。

2.有定义结点的类自然就会有定义树的类。tree就是树类,可以看到它有五个模板参数,我们需要特别关注这个KeyOfValue,后面会讲到。

// 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;
};
// stl_tree.h
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 tree
link_type header;
};
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
typedef __rb_tree_node<Value>* link_type;
Value value_field;
};

4.总结

• 通过下图对框架的分析,我们可以看到源码中rb_tree⽤了⼀个巧妙的泛型思想实现,rb_tree是实

现key的搜索场景,还是key/value的搜索场景不是直接写死的,⽽是由第⼆个模板参数Value决定

_rb_tree_node中存储的数据类型。

• set实例化rb_tree时第⼆个模板参数给的是key,map实例化rb_tree时第⼆个模板参数给的是

pair<const key, T>,这样⼀颗红⿊树既可以实现key搜索场景的set,也可以实现key/value搜索场

景的map。

• 要注意⼀下,源码⾥⾯模板参数是⽤T代表value,⽽内部写的value_type不是我们我们⽇常

key/value场景中说的value,源码中的value_type反⽽是红⿊树结点中存储的真实的数据的类型。

• rb_tree第⼆个模板参数Value已经控制了红⿊树结点中存储的数据类型,为什么还要传第⼀个模板参数Key呢?尤其是set,两个模板参数是⼀样的,这是很多同学这时的⼀个疑问。要注意的是对于map和set,find/erase时的函数参数都是Key,所以第⼀个模板参数是传给find/erase等函数做形

参的类型的。对于set⽽⾔两个参数是⼀样的,但是对于map⽽⾔就完全不⼀样了,map insert的

是pair对象,但是find和erase的是Key对象。

• 吐槽⼀下,这⾥源码命名⻛格⽐较乱,set模板参数⽤的Key命名,map⽤的是Key和T命名,⽽

rb_tree⽤的⼜是Key和Value,可⻅⼤佬有时写代码也不规范。

二.模拟实现map/set

容易想到这个工程文件中有三个文件,红黑树的头文件(RBTree.h),set的实现文件(my_set.h)和map的实现文件(my_map.h)。

1.对红黑树代码的部分修改

要通过封装红黑树实现set和map,我们需要对上篇文章实现的红黑树进行一些修改。首先红黑树的结构分为三部分,红黑树结点,红黑树迭代器和红黑树

1.先对红黑树结点的代码进行修改。我们将这里的模板参数改为T,结点内数据也改为T _data。这是为了适应上面我们反复强调的泛型编程:用一棵红黑树同时支持实现set和map,因此这里的结点存储数据用模板类型代替。

template<class T>
struct RBTreeNode
{// 这里更新控制平衡也要加入parent指针T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};

2.接着对树的代码进行部分修改。发生最明显变化的就是Insert函数。因为对于它究竟是用于实例化map还是set,树这个作为底层的并不知道,所以我们在进行比较逻辑时就遇到了一点困难:在set插入值时直接比较key即可,但是map结点存储的是一个pair类型的值,我们要如何拿到这个pair的first类型呢(map在进行比较逻辑时按照key去比)?其实不难想到这里用仿函数实现比较逻辑是比较合理的。不过这个工作需要由上层的map和set去支持:如果是set,我们就直接返回key去比较;如果是map,用仿函数拿到它的first即可。

//此处仅展示比较逻辑的处理
KeyOfT kot;Node* parent = nullptr;Node* cur = _root;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 { Iterator(cur, _root), false };}}

这里其实就明白源码中KeyOfT是做什么用的了,只需要在使用map或者set时传入各自的仿函数即可。

map的仿函数

struct MapKeyOfT
{const K& operator()(const pair<K, V>& kv){return kv.first;}
};

set的仿函数

struct SetKeyOfT
{const K& operator()(const K& key){return key;}
};

3.红黑树的迭代器

iterator实现思路分析

• iterator实现的⼤框架跟list的iterator思路是⼀致的,⽤⼀个类型封装结点的指针,再通过重载运算

符实现,迭代器像指针⼀样访问的⾏为。

• 这⾥的难点是operator++和operator--的实现。之前使⽤部分我们发现map和set的迭代器⾛的是中序遍历,左⼦树->根结点->右⼦树,那么begin()会返回中序第⼀个结点的iterator也就是10所在结点的迭代器。要找到begin只需要从根结点开始一直走到最左结点即可。

Iterator Begin()
{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return Iterator(cur, _root);
}

• 迭代器++的核⼼逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下⼀个结点

• 迭代器++时,如果it指向的结点的右⼦树不为空,代表当前结点已经访问完了,要访问下⼀个结点是右⼦树的中序第⼀个,⼀棵树中序第⼀个是最左结点,所以直接找右⼦树的最左结点即可。

• 迭代器++时,如果it指向的结点的右⼦树空,代表当前结点已经访问完了且当前结点所在的⼦树也访问完了,要访问的下⼀个结点在当前结点的祖先⾥⾯,所以要沿着当前结点到根的祖先路径向上找。

• 如果当前结点是⽗亲的左,根据中序左⼦树->根结点->右⼦树,那么下⼀个访问的结点就是当前结点的⽗亲;如下图:it指向25,25右为空,25是30的左,所以下⼀个访问的结点就是30。

• 如果当前结点是⽗亲的右,根据中序左⼦树->根结点->右⼦树,当前当前结点所在的⼦树访问完

了,当前结点所在⽗亲的⼦树也访问完了,那么下⼀个访问的需要继续往根的祖先中去找,直到找

到孩⼦是⽗亲左的那个祖先就是中序要问题的下⼀个结点。如下图:it指向15,15右为空,15是10

的右,15所在⼦树话访问完了,10所在⼦树也访问完了,继续往上找,10是18的左,那么下⼀个

访问的结点就是18。

Self operator++()
{if (_node->_right){// 右不为空,中序下一个访问的节点是右子树的最左(最小)节点Node* min = _node->_right;while (min->_left){min = min->_left;}_node = min;}else{// 右为空,祖先里面孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}

• end()如何表⽰呢?如下图:当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18

到根没有⽗亲,没有找到孩⼦是⽗亲左的那个祖先,这是⽗亲为空了,那我们就把it中的结点指针

置为nullptr,我们⽤nullptr去充当end。需要注意的是stl源码空,红⿊树增加了⼀个哨兵位头结点

做为end(),这哨兵位头结点和根互为⽗亲,左指向最左结点,右指向最右结点。相⽐我们⽤

nullptr作为end(),差别不⼤,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处

理⼀下,让迭代器结点指向最右结点。具体参考迭代器--实现。

Iterator End()
{return Iterator(nullptr, _root);
}

• 迭代器--的实现跟++的思路完全类似,逻辑正好反过来即可,因为他访问顺序是右⼦树->根结点->左⼦树,具体参考下⾯代码实现。

Self operator--()
{if (_node == nullptr)  // --end(){// --end(),特殊处理,走到中序最后一个结点,整棵树的最右结点Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else if (_node->_left){// 左子树不为空,中序左子树最后一个Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else{// 孩子是父亲右的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}

• set的iterator也不⽀持修改,我们把set的第⼆个模板参数改成const K即可, RBTree<K,

const K, SetKeyOfT> _t;

• map的iterator不⽀持修改key但是可以修改value,我们把map的第⼆个模板参数pair的第⼀个参

数改成const K即可, RBTree<K, pair<const K, V>, MapKeyOfT> _t;

•map的operator[]是复用Insert逻辑的:若当前元素存在,充当查询;若不存在,则充当插入的功能。若存在并且给赋值,则充当修改的功能。

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

2.完整的模拟实现

1.RBTree.h

#pragma onceenum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{// 这里更新控制平衡也要加入parent指针T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;Node* _root;RBTreeIterator(Node* node, Node* root):_node(node),_root(root){}Self operator++(){if (_node->_right){// 右不为空,中序下一个访问的节点是右子树的最左(最小)节点Node* min = _node->_right;while (min->_left){min = min->_left;}_node = min;}else{// 右为空,祖先里面孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self operator--(){if (_node == nullptr)  // --end(){// --end(),特殊处理,走到中序最后一个结点,整棵树的最右结点Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else if (_node->_left){// 左子树不为空,中序左子树最后一个Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else{// 孩子是父亲右的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}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;}
};template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T, const T&, const T*> ConstIterator;Iterator Begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return Iterator(cur, _root);}Iterator End(){return Iterator(nullptr, _root);}ConstIterator Begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return ConstIterator(cur, _root);}ConstIterator End() const{return ConstIterator(nullptr, _root);}//析构RBTree() = default;~RBTree()
{Destroy(_root);_root = nullptr;
}pair<Iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//return pair<Iterator, bool>(Iterator(_root, _root), true);return { Iterator(_root, _root), true };}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;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 { Iterator(cur, _root), false };}}cur = new Node(data);Node* newnode = cur;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){//   g// p   uNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_left){//     g//   p    u// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//      g//   p    u//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{//   g// u   pNode* uncle = grandfather->_left;// 叔叔存在且为红,-》变色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色//   g// u   p//       cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return { Iterator(newnode, _root), true };}void RotateR(Node * parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pParent->_left == parent){pParent->_left = subL;}else{pParent->_right = subL;}subL->_parent = pParent;}}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 (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}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;}int Height(){return _Height(_root);}int Size(){return _Size(_root);}private:int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}int _Size(Node* root){if (root == nullptr)return 0;return _Size(root->_left) + _Size(root->_right) + 1;}private:void Destroy(Node* root)
{if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;
}Node* _root = nullptr;
};

2.my_set.h

具体的插入,查询,begin和end迭代器均已经在红黑树中实现,这里直接调用即可

#pragma once#include"RBTree.h"namespace wjh
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator 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();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}private:RBTree<K, const K, SetKeyOfT> _t;};
}

3.my_map.h

#pragma once#include"RBTree.h"namespace wjh
{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;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator 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();}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = insert({ key, V() });return ret.first->second;}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};
}

4.对my_map和my_set进行用例测试

#include"Myset.h"
#include"Mymap.h"void Print(const bit::set<int>& s)
{wjh::set<int>::const_iterator it = s.end();while (it != s.begin()){--it;cout << *it << " ";}cout << endl;
}int main()
{wjh::set<int> s;s.insert(8);s.insert(9);s.insert(7);s.insert(4);s.insert(6);bit::set<int>::iterator sit = s.begin();//*sit += 10;while (sit != s.end()){cout << *sit << " ";++sit;}cout << endl;for (auto& e : s){cout << e << " ";}cout << endl;Print(s);wjh::map<string, string> dict;dict.insert({ "sort", "排序" });dict.insert({ "left", "左边" });dict.insert({ "right", "右边" });dict["left"] = "左边,剩余";dict["insert"] = "插入";dict["string"];wjh::map<string, string>::iterator it = dict.begin();while (it != dict.end()){// 不能修改first,可以修改second//it->first += 'x';it->second += 'x';cout << it->first << ":" << it->second << endl;++it;}cout << endl;for (auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}return 0;
}

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

相关文章:

  • linux系统学习(15.启动管理)
  • Anaconda安装与conda使用详细版
  • 杨校老师竞赛课堂之C++语言GESP一级笔记
  • JUC并发编程09 - 内存(01) - JMM/cache
  • HITTER——让双足人形打乒乓球(且可根据球的走向移动脚步):高层模型规划器做轨迹预测和击球规划,低层RL控制器完成击球
  • windows下安装redis
  • fcitx5-rime自动部署的实现方法
  • ​Windows8.1-KB2934018-x64.msu 怎么安装?Windows 8.1 64位补丁安装教程​(附安装包下载)
  • Linux按键驱动开发
  • 基于 Vue + Interact.js 实现可拖拽缩放柜子设计器
  • 忆联参与制定消费级SSD团体标准正式出版! 以“高可靠”引领行业提质增效与用户体验升级
  • 图扑 HT 农林牧数据可视化监控平台
  • 【从零开始搭建你的 AI 编程助手知识库】
  • 静态库生成及使用流程
  • playbook剧本
  • 4. LangChain4j 模型参数配置超详细说明
  • LangChain框架入门02:开发环境配置
  • 光伏发多少电才够用?匹配家庭用电需求
  • 【C/C++】柔性数组
  • 用html+js下拉菜单的demo,当鼠标点击后展开,鼠标点击别的地方后折叠
  • 高斯滤波的简介、C语言实现和实测
  • simd笔记
  • 嵌入式-定时器的从模式控制器、PWM参数测量实验-Day24
  • 命令拓展(草稿)
  • C++ 并发编程:全面解析主流锁管理类
  • 虚拟私有网络笔记
  • HDMI2.1 8K验证平台
  • websocket建立连接过程
  • 航电系统路径规划技术解析
  • C++Primer笔记——第六章:函数(下)