【c++】set和map的封装
一、框架搭建 如何实现红黑树的复用set和map的底层都是红黑树但由于存储元素的差异一个只存储key一个既存储key又存储value我们要么创造出两棵稍微不一样的红黑树或者是改变红黑树的结构使其能完美匹配上set和map。库里面采用了后者。1. 分析库里set与map的实现分析RBTree的两个模板参数 Key, Value库里面RBTree的实现一共有5个模板参数分别是 KeyValueKeyOfValueCompare和Alloc我们主要分析前三个模板参数的作用。Compare是控制比较规则的仿函数类型Alloc是内存池。在这里插入图片描述对于set它只有一个用来接收key类型的模板参数Key并把Key同一个类型命名为key_type与value_type。对于map它有两个分别用来接收key和 value类型的模板参数Key和T。将Key重命名为key_type将pairconst Key, T 重命名为value_type。它们复用同一棵红黑树将4个类型传给红黑树。在这里插入图片描述重点观察Value这个模板参数它的类型是节点所存储的元素的类型。set传过来的是Key类型map传过来的是pairconst Key, T类型。利用模板来控制红黑树所存储的类型满足set和map不同的存储需求。在这里插入图片描述我们传递给map的只是key和value的类型而map要存储的是pairconst key, value类型实现key和value的绑定const实现不可修改以后再加所以我们需要做一个加工。既然有了接收存储元素类型的Value类型而为什么不删除第一个用来单独存储key类型的模板参数Key似乎用不上它这个以后就能知道了。2. 分析第三个模板参数KeyofValue我们在进行插入时需要根据key的大小来找到插入的位置而由于set和map存储类型的不同set直接用Valuekey类型的元素就好而map则需要取出Value(pairconst key, value)里面的key值来找到插入的位置。在这里插入图片描述而KeyOfValue这个模板参数就是用来接收功能为取出key的仿函数类型KeyOfValue是匿名对象。可以参照库里的实现大胆猜测。在这里插入图片描述set里面传给KeyOfValue的是一个ideneityvalue_type类型的仿函数identity在这里是本身的意思它的功能就是取出key值就是value_type类型的元素它本身而map里面传给KeyOfValue的是一个select1stvalue_type类型的仿函数它的功能就是取出key值就是value_type类型元素里的第一个值。在这里插入图片描述所以我们在set和map里面的实现里面都要实现一个仿函数用来传递给KeyOfValue。set代码语言javascriptAI代码解释namespace mosheng { templateclass K class identity { public: K operator()(const K key) { return key; } }; templateclass Key class set { typedef RBTreeKey, Key, identityKey rbType; }; }map:代码语言javascriptAI代码解释namespace mosheng { templateclass K, class KV class select1st { public: K operator()(const KV kv) { return kv.first; } }; templateclass Key, class Value class map { typedef RBTreeKey, std::pairKey, Value, select1stKey, std::pairKey, Value rbType; }; }不要忘了对应的红黑树的修改主要是修改insert和find还有对应的模板参数去用KeyOfValue提取key。然后框架搭建完毕。代码语言javascriptAI代码解释#pragma once enum Colour { RED, BLACK }; templateclass Value struct RBTreeNode { RBTreeNode(Value kv) :_kv(kv) , _left(nullptr) , _right(nullptr) , _parent(nullptr) { }; Value _kv; RBTreeNodeValue* _left; RBTreeNodeValue* _right; RBTreeNodeValue* _parent; Colour _col RED; }; templateclass Key, class Value, class KeyOfValue class RBTree { typedef RBTreeNodeValue Node; public: bool Insert(Value kv) { //处理空树的情况 if (_root nullptr) { _root new Node(kv); _root-_col BLACK; return true; } //找到要插入的位置 else { Node* cur _root; Node* parent nullptr; while (cur) { if (KeyOfValue()(kv) KeyOfValue()(cur-_kv)) { parent cur; cur cur-_left; } else if (KeyOfValue()(kv) KeyOfValue()(cur-_kv)) { parent cur; cur cur-_right; } else { return false; } } //插入新节点 cur new Node(kv); if (KeyOfValue()(parent-_kv) KeyOfValue()(kv)) { parent-_left cur; } else { parent-_right cur; } cur-_parent parent; //父节点为红色的情况下需要进行处理 if (parent-_col RED) { while (parent parent-_col RED) { //记录节点 Node* grandfather parent-_parent; Node* uncle nullptr; if (grandfather-_left parent) { uncle grandfather-_right; } else { uncle grandfather-_left; } //uncle为红色的情况 //仅变色 if (uncle uncle-_col RED) { uncle-_col parent-_col BLACK; grandfather-_col RED; //更新 cur grandfather; parent cur-_parent; } //uncle为黑色或为空的情况 else { //右旋转 if (grandfather-_left parent parent-_left cur) { RotateR(grandfather); parent-_col BLACK; grandfather-_col RED; break; } //左旋转 else if (grandfather-_right parent parent-_right cur) { RotateL(grandfather); parent-_col BLACK; grandfather-_col RED; break; }//左右双旋 else if (grandfather-_left parent parent-_right cur) { RotateL(parent); RotateR(grandfather); cur-_col BLACK; grandfather-_col RED; break; } //右左双旋 else { RotateR(parent); RotateL(grandfather); cur-_col BLACK; grandfather-_col RED; break; } } } } } //处理根节点颜色 _root-_col BLACK; return true; } void InOrder() { _InOrder(_root); cout endl; } Node* Find(const Key key) { Node* cur _root; while (cur) { if (KeyOfValue()(cur-_kv) key) { cur cur-_right; } else if (KeyOfValue()(cur-_kv) key) { cur cur-_left; } else { return cur; } } return nullptr; } Node* _root nullptr; };可以使用匿名对象但推荐构造一个对象kof来使用仿函数KefOfValue。为什么保留第一个Key的模板参数在find函数上就有所体现我们需要Key的类型因为要根据Key的类型来寻找。KefOfValue无法提取出类型。二、迭代器的实现迭代器的实现也就是在节点指针的基础之上做封装操作并重载一些运算符。map和set的迭代器是双向迭代器关键是需要实现和–的重载。1. 普通迭代器iterator的实现1.1 先搭一个基本框架代码语言javascriptAI代码解释templateclass Value class TreeIterator { typedef RBTreeNodeValue Node; typedef TreeIteratorValue Self; public: TreeIterator(Node* node) :_node(node) {} Value operator* () { return _node-_kv; } Value* operator-() { return (_node-_kv); } bool operator(const Self s)const { return _node s-_node; } bool operator!(const Self s)const { return _node ! s-_node; } private: Node* _node; };1.2 operator与operator- -实现operator我们希望达成的效果是从当前节点移动到下一个按照中序遍历的节点。- -则是反过来。只需要知道当前节点的下一个节点是哪一个。在这里插入图片描述现在我们有一棵红黑树若当前节点为18那么我们希望节点18后的节点是节点25节点25后的节点是节点30以此类推。首先看节点10它不是叶子节点后需要跳到节点15也就是它的右节点这是因为它的右子树不为空按照中序遍历顺序可以认为它的左子树是遍历完成的只需要看它的右子树就行。所以首先要判断当前节点的右子树是否为空。再来看节点30后需要跳到节点35是其右子树的最左节点。若不为空就会跳到节点30的左孩子节点40节点40的左子树是还未访问过的所以需要访问它的左子树一直到最左端。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2582137.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!