当前位置:首页 » 《我的小黑屋》 » 正文

详解STL之set和map --- 关联式容器的再封装

22 人参与  2024年11月06日 09:22  分类 : 《我的小黑屋》  评论

点击全文阅读


1.源码及框架分析

SGI-STL30版本源代码, map和set 的源代码在map / set / stl_map.h / stl_set.h / stl_tree.h等几个头文件中。
// 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>

map和set的实现结构框架核心部分截取出来如下:

// stl_set.htemplate <class Key, class Compare = less<Key>, class Alloc = alloc>class set {public:// typedefs:typedef Key key_type;    // 不是日常意义上的value,而是红黑树结点中存储的真实的数据的类型(key)。typedef Key value_type; private:typedef rb_tree<key_type, value_type,identity<value_type>, key_compare, Alloc> rep_type;// key_compare 是仿函数,为了兼容map的仿函数比较,做出的牺牲rep_type t; // red-black tree representing set};// stl_map.htemplate <class Key, class T, class Compare = less<Key>, class Alloc = alloc>class map {public:// typedefs:typedef Key key_type;typedef T mapped_type;// pair的Key写成const,避免Key值被修改// 不是日常意义上的value,而是红黑树结点中存储的真实的数据的类型(pair)typedef pair<const Key, T> value_type; private:typedef rb_tree<key_type, value_type,select1st<value_type>, key_compare, Alloc> rep_type;// 仿函数key_compare,实现pair的first比较    // (因为pair直接比较会兼容second的比较,不是我们想要的)rep_type t; // red-black tree representing map};
// stl_tree.hstruct __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;};// 一份模版, 编译器根据所传过来的Value, // 实例化出value是key / pair的RB treetemplate <class Value> struct __rb_tree_node : public __rb_tree_node_base{typedef __rb_tree_node<Value>* link_type;Value value_field; // 节点值};// stl_tree.htemplate <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; // 传给Value的数据才是要存储到红黑树节点的数据public:// insert用的是第二个模板参数左形参pair<iterator, bool> insert_unique(const value_type& x);// erase和find用第一个模板(Key)参数做形参// 注:这里是泛型编程(template<>),所以得定义Key,所以RB tree模版仍要传Keysize_type erase(const key_type& x);iterator find(const key_type& x); protected:size_type node_count; // keeps track of size of treelink_type header; // 哨兵位节点};
• 通过下图对框架的分析,我们可以看到源码中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和ease的是Key对象。 • 这里源码命名风格比较乱,set模板参数用的Key命名,map用的是Key和T命名,而 rb_tree用的又是Key和Value。

2.模拟实现出set和map

(1) 实现出复用红黑树的框架,并支持insert

• 参考源码框架,map和set复用之前我们实现的红黑树。 • 我们这里相比源码调整一下,key参数就用K,value参数就用V,红黑树中的数据类型,我们使用 T(容易理解点)。 • 其次因为RBTree实现了泛型不知道T参数导致是K,还是pair<K, V>,那么insert内部进行插入逻辑比 较时,就没办法进行比较,因为pair的默认支持的是key和value一起参与比较,我们需要时的任 何时候只比较key,所以我们在map和set层分别实现一个MapKeyOfT和SetKeyOfT的仿函数传给 RBTree的 KeyOfT ,然后RBTree中通过KeyOfT仿函数取出T类型对象中的key,再进行比较,具体 细节参考如下代码实现。
// 源码中pair⽀持的<重载实现template <class T1, class T2>bool operator< (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs){ return lhs.first<rhs.first || (!(rhs.first<lhs.first) &&lhs.second<rhs.second); }
// Mymap.hnamespace bit{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:bool insert(const pair<K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<K, V>, MapKeyOfT> _t;};}// Myset.hnamespace bit{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:bool insert(const K& key){return _t.Insert(key);}private:RBTree<K, K, SetKeyOfT> _t;};}// RBTree.henum Colour{RED,BLACK};template<class T>struct RBTreeNode{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){}};// 实现步骤:// 1、实现红⿊树// 2、封装map和set框架,解决KeyOfT// 3、iterator// 4、const_iterator// 5、key不⽀持修改的问题// 6、operator[]template<class K, class T, class KeyOfT>class RBTree{private:typedef RBTreeNode<T> Node;Node* _root = nullptr;public:bool Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return 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 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;//...return true;}};

(2) 支持迭代器的实现

① iterator核心源码(stl_tree.h)

struct __rb_tree_base_iterator{  typedef __rb_tree_node_base::base_ptr base_ptr;  typedef bidirectional_iterator_tag iterator_category;  typedef ptrdiff_t difference_type;  base_ptr node;  void increment()  {    if (node->right != 0) {      node = node->right;      while (node->left != 0)        node = node->left;    }    else {      base_ptr y = node->parent;      while (node == y->right) {        node = y;        y = y->parent;      }      if (node->right != y)        node = y;    }  }  void decrement()  {    if (node->color == __rb_tree_red &&        node->parent->parent == node)      node = node->right;    else if (node->left != 0) {      base_ptr y = node->left;      while (y->right != 0)        y = y->right;      node = y;    }    else {      base_ptr y = node->parent;      while (node == y->left) {        node = y;        y = y->parent;      }      node = y;    }  }};template <class Value, class Ref, class Ptr>struct __rb_tree_iterator : public __rb_tree_base_iterator{  typedef Value value_type;  typedef Ref reference;  typedef Ptr pointer;  typedef __rb_tree_iterator<Value, Value&, Value*>             iterator;  typedef __rb_tree_iterator<Value, const Value&, const Value*> const_iterator;  typedef __rb_tree_iterator<Value, Ref, Ptr>                   self;  typedef __rb_tree_node<Value>* link_type;  __rb_tree_iterator() {}  __rb_tree_iterator(link_type x) { node = x; }  __rb_tree_iterator(const iterator& it) { node = it.node; }  reference operator*() const { return link_type(node)->value_field; }#ifndef __SGI_STL_NO_ARROW_OPERATOR  pointer operator->() const { return &(operator*()); }#endif /* __SGI_STL_NO_ARROW_OPERATOR */  self& operator++() { increment(); return *this; }  self operator++(int) {    self tmp = *this;    increment();    return tmp;  }      self& operator--() { decrement(); return *this; }  self operator--(int) {    self tmp = *this;    decrement();    return tmp;  }};inline bool operator==(const __rb_tree_base_iterator& x,                       const __rb_tree_base_iterator& y) {  return x.node == y.node;}inline bool operator!=(const __rb_tree_base_iterator& x,                       const __rb_tree_base_iterator& y) {  return x.node != y.node;}

② iterator实现思路分析

• iterator实现的大框架跟list的iterator思路是一致的,用一个类型封装结点的指针,再通过重载运算 符实现,迭代器像指针一样访问的行为。 • 这里的难点是operator++和operator--的实现。之前使用部分,我们分析了,map和set的迭代器走 的是中序遍历,左子树->根结点->右子树,那么begin()会返回中序第一个结点的iterator。 • 迭代器++的核心逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下一个结点。 • 迭代器++时,如果it指向的结点的右子树不为空,代表当前结点已经访问完了,要访问下一个结点 是右子树的中序第一个,一棵树中序第一个是最左结点,所以直接找右子树的最左结点即可。 • 迭代器++时,如果it指向的结点的右子树空,代表当前结点已经访问完了且当前结点所在的子树也 访问完了,要访问的下一个结点在当前结点的祖先里面,所以要沿着当前结点到根的祖先路径向上 找。 • 如果当前结点是父亲的左,根据中序左子树->根结点->右子树,那么下一个访问的结点就是当前结 点的父亲。 • 如果当前结点是右亲的右,根据中序左子树->根结点->右子树,当前当前结点所在的子树访问完 了,当前结点所在父亲的子树也访问完了,那么下一个访问的需要继续往根的祖先中去找,直到找 到孩子是父亲左的那个祖先就是中序要问题的下一个结点 • end()如何表示呢?模拟实现我们用 nullptr去充当end需要注意的是stl源码中,红黑树增加了一个哨兵位头结点做为end(),这哨兵位头结点和根互为父亲,左指向最左结点,右指向最右结点。 相比我们用 nullptr作为end(),差别不大,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处 理一下,让迭代器结点指向最右结点。具体参考迭代器--实现。 • 迭代器--的实现跟++的思路完全类似,逻辑正好反过来即可,因为他访问顺序是右子树->根结点-> 左子树,具体参考下面代码实现。 • 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;

③ iterator模拟实现

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;}// 请完善下面两个操作,让迭代器可以像指针一样操作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;}};

④ rbtree迭代器版模拟实现

namespace zyt{enum Color{RED,BLACK};template<class T>struct RBTreeNode{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Color _color;RBTreeNode(const T& data = T()):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _color(RED){}};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;}// 请完善下面两个操作,让迭代器可以像指针一样操作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;}};// 注意:红黑树完整操作参考课堂代码// 此处给红黑树添加迭代器,其他用不到的操作暂被拿掉,只留下红黑树构建的核心操作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;public:// 请给红黑树的迭代器取别名,方便后序使用// ________________________// 请完善Begin() 和 End()方法Iterator Begin(){Node* cur = _root;while (cur->_left)cur = cur->_left;return Iterator(cur,_root);}Iterator End(){return Iterator(nullptr,_root); // 理解上end指向的是最右节点,实际上指向的是空}ConstIterator Begin() const{Node* cur = _root;while (cur->_left)cur = cur->_left;return ConstIterator(cur,_root);}ConstIterator End() const{return ConstIterator(nullptr,_root); // 理解上end指向的是最右节点,实际上指向的是空}~RBTree(){Destroy(_root);_root = nullptr;}pair<Iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_color = BLACK;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->_color = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_color == RED){Node* grandfather = parent->_parent;//    g//  p   uif (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_color == RED){// u存在且为红 -》变色再继续往上处理parent->_color = uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = cur->_parent;}else{// u存在且为黑或不存在 -》旋转+变色if (cur == parent->_left){//    g//  p   u//c//单旋RotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else{//    g//  p   u//    c//双旋RotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}else{//    g//  u   pNode* uncle = grandfather->_left;// 叔叔存在且为红,-》变色即可if (uncle && uncle->_color == RED){parent->_color = uncle->_color = BLACK;grandfather->_color = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{if (cur == parent->_right){RotateL(grandfather);parent->_color = BLACK;grandfather->_color = RED;}else{//g//   u     p//      cRotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;}break;}}}_root->_color = BLACK;return { Iterator(newnode,_root),true };}private: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;}}void  RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}private:Node* _root = nullptr;};}

⑤ map要支持operator[]

map要支持[]主要需要修改insert返回值支持,修改RBtree中的insert返回值为 pair<Iterator, bool> Insert(const T& data)

 ⑥myset和mymap模拟实现

#include"RBTree.h"namespace zyt{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;};}
namespace zyt{template<class K, class V>struct map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;public: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;};}

点击全文阅读


本文链接:http://zhangshiyu.com/post/183060.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1