目录
map与set的模拟实现
1.基本框架
2.模拟实现map与set所需要做的事
1.使用模板 , 达到泛性编程
2.比较问题
3.迭代器
RBTree中:
operator++
operator--
4.map [ ] 的实现
5.使用普通迭代器构造const迭代器
效果
map与set的模拟实现
1.基本框架
map
set
2.模拟实现map与set所需要做的事
- 调用模板, 使得用map与set复用同一棵树(解决map存pair, set存key的问题)
- 比较问题
- 迭代器中, ++,--的实现(遍历二叉树)
- map[ ] 的实现
- 维护底层搜索二叉树的性质, 要使用const迭代器, set可以都用const迭代器, 但是map需要达到key不能修改, 但允许value可以修改, 所以红黑树也需要实现普通迭代器,--使得key不能修改-->map再传递pair的时候,给K加上const就行 -->set的iterator 是红黑树中的const_iterator && const_iterator也是const_iterator -->map的使用了iterator 与 const_iterator set的iterator调用的时候, 会发生隐式类型转换, 普通迭代器-->const迭代器 如果没提供普通迭代器到const迭代器的构造会报错 --> 需要提供
1.使用模板 , 达到泛性编程
map: kv类型 set : k类型
map:
set:
2.比较问题
由于使用了泛性编程, RBTreeNode里面的_data类型不确定, 可能是K, 也可能是pair
--如果是K(set)的比较,不会有问题
--如果是pair(map)的比较, 比较会出现问题
pair的比较: 如果first小就小, 如果first不小, 比较second, second小就小
但我们只期望按K去比较
解决: 使用仿函数
红黑树并不知道传过来的T是K类型还是pair类型, 但是上一层知道, 于是我们给它加一个仿函数,
这个仿函数在map/set里面实现 用来获取key,然后传递给红黑树, 让其根据key来比较
像这样:
map
set
用法:
data 为T类型, 可能是K类型, 也可能是pair类型,我们将其传递给仿函数,来获取key
1.插入
2.查找
Node* cur ->_data 为T类型, 可能是K类型, 也可能是pair类型,我们将其传递给仿函数,来获取key
3.迭代器
map与set的迭代器是使用红黑树内部的迭代器
1.注意: 当我们去取一个类模板的内嵌类型的时候, 前面要加一个typename
2. 原因: 因为编译器无法区分, 你取得是类型还是静态变量
加上typename告诉编译器所取得是类型, 等该类模板实例化后, 再去找这个类型
3.示例:
map
set
框架:
RBTree中:
begin
使用最左边的节点构造迭代器
end
1.使用空节点构造迭代器(一般是最右边的节点的下一个--->是nullptr)
2.实现的时候增加哨兵位
--此时如果it走到最后一个节点,再++, 就直接走到end
--特殊处理:
优点:end--的时候, 直接到最右边的节点
operator++
左子树, 根 , 右子树 (中序,找完右子树后, 其树就被访问完了)
思路:
1.有右子树: 就往右子树走-->找右子树的最左节点2.没有右子树: 看是不是parent的左子树
--是的话就把parent给it
--不是的话向上调整 (调整最后没有父亲节点了,跳空)沿着根路径, 找孩子是父亲左孩子的那个祖先
对以下情况演示:
1.该节点有右子树
2.该节点没有右子树
3.走到最后一个节点
代码:
Self& operator++() { //1.右子树不为空,找右子树的最左节点 if (_node->_right) { Node* subRL = _node->_right; while (subRL->_left) { subRL = subRL->_left; } _node = subRL; } //2.右子树为空, 找节点是父亲左孩子的祖先 else { Node* cur = _node; Node* parent = cur->_parent; while (parent && parent->_right == cur) { cur = parent; parent = parent->_parent; } _node = parent; } return *this; }
operator--
和上面基本一样,只是换了个方向: 右子树, 根 , 左子树(该顺序访问, 走完左子树,该树就走完)
思路:
1.有左子树, 就往左子树走--->找左子树里最右边节点
2.没有左子树, 就找节点是父亲的右孩子的祖先
--如果当前节点就是父亲的右孩子, 直接把parent给it
--如果当前节点不是父亲的右孩子, 沿根路径向上调整, 找到为止(或找到p为nullptr)
找到了就把parent给it
代码:
Self& operator--() { //1.如果有左子树,就往左子树走,找其最右边的节点 if (_node->_left) { Node* subLR = _node->_left; while (subLR->_right) { subLR = subLR->_right; } //然后把这个节点给it _node = subLR; } //2.如果没有左子树, 就找节点是父亲右孩子的祖先 else { Node* cur = _node; Node* parent = cur->_parent; while (parent && parent->_left == cur) { cur = parent; parent = parent->_parent; } //然后把这个节点给it _node = parent; } return *this; }
4.map [ ] 的实现
1.修改insert的返回值为pair<iterator,bool>
--修改RBTree里Insert的返回值:
--空树插入成功
--非空树插入失败
--非空树插入成功
这里使用newnode记录以下cur节点, 下面需要对红黑树调整, cur 可能会发生改变
2.[ ]的实现
--调用RBTree里的Insert函数
--返回其second
pair<iterator, bool> insert(const pair<const K,V>& kv) { return _t.Insert(kv); } //[]的实现 V& operator[](const K& key) { pair<iterator,bool> ret = _t.Insert(make_pair(key, V())); return ret.first->second; }
5.使用普通迭代器构造const迭代器
1.问题:如果使用普通迭代器, 那么key的值会被修改, 不满足二叉搜索树性质
示例:
2.我们使用const迭代器
出现报错
--原因:发生隐式类型转换, 但我们没有提供使用普通迭代器构造const迭代器的构造函数,
转换不了, 会报错
--解决: 提供一个支持普通迭代器到const迭代器的转换
--为什么RBTree不都返回const迭代器??
---因为map与set复用的是同一个红黑树来set全用const可以, 但map允许修改V
代码:
--当Ref是T&, Ptr是T*的时候, 调用这个构造函数就是拷贝构造
--当Ref是constT& ,Ptr是constT*的时候, 调用这个构造函数就是支持普通迭代器构造const迭代器的转换
效果
代码:Map and Set/Map and Set · 朱垚/数据结构练习 - 码云 - 开源中国 (gitee.com)