C++——STL——封装红黑树实现mymap与myset
目录
1.map与set的底层框架以及源码剖析
1.1 源码以及框架分析
2.模拟实现map 与 set
2.1 实现出复用红黑树的框架,并支持insert
2.2 支持iterator的实现
2.2.1 iterator核心源码
2.2.2 iterator 实现思路分析:
2.2.2.1 operator()++:
2.2.2.2 operator()--:
2.2.3 迭代器不可修改问题
2.2.4 map的[]
2.2.5 总代码以及一些注意事项
1.map与set的底层框架以及源码剖析
咱们之前学过map与set,multimap与multiset的使用了,那么咱们今天来学习如何使用红黑树来实现map与set的底层原理。
1.1 源码以及框架分析
SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h等几个头文件 中。
OK,咱们先来看map与set实现结构框架的核心部分吧:
// 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>
// 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
};
// 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 treelink_type header;
};
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{typedef __rb_tree_node<Value>* link_type;Value value_field;
};
ok,接下来,咱们就来一点一点的进行分析:
先来看set底层源码,可以看出,这个set底层是有两个key的,但是这两个key的作用不同,这也就是为什么不只用一个key,咱们待会讲他们的作用分别是什么。
这事map的底层源码,需要注意的是,这里的第二个模板参数是pair类型,第一个模板参数同样也是key,从这也可以看出,第一个模板参数的功能应该相同。
好了,到了重点的地方了,咱们之前的map与set不是都有两个模板参数嘛?这里第二个模板参数决定了你底层的node节点应该用什么数据类型。
画出整体的图就是这样的:
这样,就可以实现用一个tree模板,同时实现map与set这两个分别要传什么数据。
1.通过对框架的分析,我们可以看到源码中rb_tree用了⼀个巧妙的泛型思想实现,rb_tree是实 现key的搜索场景,还是key/value的搜索场景不是直接写死的,而是由第二个模板参数Value决定 _rb_tree_node中存储的数据类型。
2. set实例化rb_tree时第⼆个模板参数给的是key,map实例化rb_tree时第二个模板参数给的是 pair,这样一颗红黑树既可以实现key搜索场景的set,也可以实现key/value搜索场 景的map。
3. 要注意⼀下,源码里面模板参数是用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对象。(所以第一个模板参数主要是为了给find/erase,做参数准备的)
2.模拟实现map 与 set
2.1 实现出复用红黑树的框架,并支持insert
参考源码框架,map和set复用之前我们实现的红黑树。
我们这里相比源码调整⼀下,key参数就用K,value参数就用V,红黑树中的数据类型,我们使用 T。
好,那么在这里,有一个问题,就是但凡涉及到比较大小的问题,比如插入时候,需要比较大小吧,比如插入的数值比当前数值大,那么我要往右子树移动,等等。咱们前面还说了,map与set底层的数据是依靠第二个模板参数来实现的,但是!第二个模板参数,map的是pair类型的呀,咱们知道,pair类型的比较大小,是first(key)要比较,second(value)也要比较,就是要比较两个。但是,咱们的insert比较,只需要比较一个即可,我们需要时的任 何时候只比较key,所以这里,咱们看源码中,是不是有一个KeyOfValue呀,就是构造一个仿函数,可以用这个仿函数取出pair类型中的first。所以我们在map和set层分别实现⼀个MapKeyOfT和SetKeyOfT的仿函数传给 RBTree的KeyOfT,然后RBTree中通过KeyOfT仿函数取出T类型对象中的key,再进行比较。
口头上光说,可能有点晦涩难懂,所以,咱们上代码:
//mymap.h
template<class K, class V>
class map
{struct MapKeyOfT{//map 和 set 分别提供不同的仿函数(如 _KeyOfValue),用于从节点中提取键值://map 提取 pair::first。//set 直接返回节点值本身。const K& operator()(const pair<K, V>& kv){return kv.first;}};
public:bool insert(const pair<K, V>& kv){return _t.Insert(kv);}
private:// pair可以修改,pair的K类型用const修饰,保证key不能被修改,但是value是可以被修改的RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
//myset.h
template<class K>
class set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
private:// 第一个模板参数带const,保证key不能被修改RBTree<K, const K, SetKeyOfT> _t;
};
//RBTree.h
// red black
template<class T>
struct RBTreeNode
{//这里的T就相当于是红黑树中的数据类型,即源码中对应的valueT _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}
};
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;}}
这里的插入代码,咱们只放出来一部分,因为重点不在插入代码,而是在构造仿函数。在RBTree.h中,可以看出使用仿函数提取pair类型中的first数据了。因为_data为T实例化出的对象,也就是说_data就是pair类型,所以可以使用kot(cur->_data),直接获取里面的key。
OK,这个问题解决了,咱们来看下一个:
2.2 支持iterator的实现
2.2.1 iterator核心源码
struct __rb_tree_base_iterator{typedef __rb_tree_node_base::base_ptr base_ptr;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*> __rb_tree_iterator() {}__rb_tree_iterator(link_type x) { node = x; }__rb_tree_iterator(const iterator& it) { node = it.node; }iterator;reference operator*() const { return link_type(node)->value_field; }#ifndef __SGI_STL_NO_ARROW_OPERATORpointer operator->() const { return &(operator*()); }#endif /* __SGI_STL_NO_ARROW_OPERATOR */self& operator++() { increment(); return *this; }self& operator--() { decrement(); return *this; }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;
}
2.2.2 iterator 实现思路分析:
1.iterator实现的大框架跟list的iterator思路是⼀致的,用一个类型封装结点的指针,再通过重载运算 符实现,迭代器像指针一样访问的行为。
首先,我要声明一下的是,map与set迭代器是双向迭代器,只支持++,--,不支持+,-。所以说咱们在实现iterator的时候,直接实现++,--,就可以了。
2.这里的难点是operator++和operator--的实现。之前使用部分,我们分析了,map和set的迭代器走的是中序遍历,左子树->根结点->右子树,那么begin()会返回中序第一个结点的iterator也就是10 所在结点的迭代器。
中序第一个节点,肯定就是根据左根右,是最左节点喽。
Node* minLeft = _root;
while (minLeft && minLeft->_left)
{minLeft = minLeft->_left;
}
上面的代码是寻找begin()节点的代码。
3.迭代器++的核心逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下一个结点。
2.2.2.1 operator()++:
对于重载++,其实根据左-根-右,就可以推断出,一个节点的下一个节点是哪个节点,因为这都是类似的。但是在这里,我还是将方法总结一下吧。
【1】如果当前结点是父亲的左,根据中序左子树->根结点->右子树,那么下一个访问的结点就是当前结 点的父亲;如下图:it指向25,25右为空,25是30的左,所以下一个访问的结点就是30。
这个符合左根右的规则吧,那么到了30呢?下一步应该怎么去做呢?
【2】迭代器++时,如果it指向的结点的右子树不为空,代表当前结点已经访问完了,要访问下一个结点 是右子树的中序第⼀个,一棵树中序第⼀个是最左结点,所以直接找右子树的最左结点即可。
还是上面那个图,此时的it指向的是30,那么30的右子树不是空,那也就是说,咱们下一步就是要去找那棵不为空的右子树的中序第一个节点(最左节点),就是35吧。
【3】迭代器++时,如果it指向的结点的右子树空,代表当前结点已经访问完了且当前结点所在的子树也 访问完了,要访问的下一个结点在当前结点的祖先里面,所以要沿着当前结点到根的祖先路径向上 找。(那么具体怎么找,找什么?下面马上就会讲了)
【4】如果当前结点是父亲的右,根据中序左子树->根结点->右子树,当前当前结点所在的子树访问完 了,当前结点所在父亲的子树也访问完了,那么下一个访问的需要继续往根的祖先中去找,直到找 到孩子是父亲左的那个祖先就是中序要问题的下⼀个结点(如果发现孩子是父亲的右,不要着急,将孩子移到父亲的位置,父亲移动祖先的位置,继续寻找,直到找到为止)。如下图:it指向15,15右为空,15是10 的右,15所在子树话访问完了,10所在子树也访问完了,继续往上找,10是18的左,那么下⼀个 访问的结点就是18。
多说无意,咱们还是来看代码吧:
if (_node->_right){// 1、当前节点的右不为空// 下一个就是右子树中序第一个(最左节点)Node* minLeft = _node->_right;while (minLeft->_left){minLeft = minLeft->_left;}_node = minLeft;}else{// 2、当前节点的右为空// 下一个是当前节点的祖先,孩子是父亲左那个祖先Node* cur = _node;Node* parent = cur->_parent;// parent为空,cur是根,说明当前树走完了,// nullptr给_node,nullptr做end()//并且这个地方需要的是孩子是父亲的左,所以说,//如果遇到孩子是父亲的右的,进入循环,继续寻找while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}
与代码相关的一些注意事项,我也都写在了代码的旁边。
好,那么下面来思考另外一个问题,如果it已经走到了整个二叉树的最右节点,那么下一步是不是要走到end()了,但是二叉树的end()该如何表示呢?
end()如何表示呢?如上图:当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18 到根没有父亲,没有找到孩子是父亲左的那个祖先,这是父亲为空了,那我们就把it中的结点指针 置为nullptr,我们用nullptr去充当end。
需要注意的是stl源码空,红黑树增加了⼀个哨兵位头结点 做为end(),这哨兵位头结点和根互为父亲,左指向最左结点,右指向最右结点。
相比我们用 nullptr作为end(),差别不大,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处 理⼀下,让迭代器结点指向最右结点。
其实用不用header去实现,差别不大,那么header的作用是什么呢?
header 的作用与设计原理
结构设计
header 是一个辅助节点,不存储实际数据,其成员含义:
parent:指向红黑树的根节点。
left:指向树的最小节点(最左叶子)。
right:指向树的最大节点(最右叶子)。
根节点的 parent 也指向 header,形成闭环。
边界处理:
end() 迭代器:指向 header。
begin() 迭代器:指向 header->left(最小节点)。
关键行为:
end()--(即 header--)返回 header->right(最大节点)。
最大节点的 ++ 会走到 header(即 end())。
最小节点的 -- 会走到 header,但通常不允许对 begin() 执行 --。
那为什么end()(也就是header)--才可以到最右节点呢?别急,你这个东西你用常规的思想是理解不了的,这个得借助咱的operator()--(重载--)来实现。
2.2.2.2 operator()--:
减减的逻辑与++的逻辑基本是相反的,++的情况是以右子树为空进行分类,那么--,就是以左子树为空进行分类:
1.左子树不为空,那么就进入左子树,去寻找左子树中的最右节点。之后按照右-根-左的顺序进行减减。
2.左子树为空,说明这个局部的二叉树已经减完了(因为左是减减的最后一个部分)。那么就需要往祖先里面去寻找要减的东西了。就去找当前孩子节点位于父亲节点的右的那个祖先节点。
还是以这幅图为例子,那么it从50,先减减到35,之后发现35的左侧没有子树了,这个时候,就需要向上进行回溯。找孩子节点位于父亲的右的那个祖先,嗷,找到了,是30,那么35--后的下一个节点就是30.
OK,咱们来看代码:
//从根节点 _root 出发,不断向右子树遍历,直到找到最右侧节点(最大值节点)。
//将 _node 设为该节点,完成从 end() 到最后一个元素的移动。
if (_node == nullptr)//这个为什么要判断多余的一步呢?//其实也不是多余,因为你如果没有这一句判断//那么你的end()指向的是空,则_node也是空,// 访问 _node->_left 会导致 空指针解引用,引发未定义行为(如程序崩溃),//这不铁铁的坑了吗?
{// --end(),到最右节点//这一句判断代码,才是从end()到最右节点的关键代码。Node* maxRight = _root;while (maxRight && maxRight->_right){maxRight = maxRight->_right;}_node = maxRight;
}
//在红黑树(或标准容器)中,end() 通常指向容器末尾的“超尾”位置(如 nullptr)
// ,表示一个无效的哨兵位置。
//当用户对 end() 迭代器执行 -- 操作时,预期是获取容器中的最后一个有效元素。
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;
}
代码里面的一些注意事项,我都有注释在旁边进行讲解。
2.2.3 迭代器不可修改问题
咱们知道set的迭代器是不可以修改的,所以,我们把set的第二个模板参数改成constK即可, RBTree<k,const K , SetKeyOfT> _t;
而map的iterator不支持修改key但是可以修改value,我们把map的第二个模板参数pair的第一个参 数改成constK即可, RBTree <k,pair<const k,v>, MapKeyOfT> _t;
2.2.4 map的[]
咱们知道,map支持方括号,那么底层是由于insert,才可以实现的,而insert的返回类型是pair类型。接下来,请看代码:
//operator[]的实现是关键,这里首先尝试插入一个键为key,值默认初始化的V类型的pair。
//如果键存在,插入失败,但返回的迭代器指向已存在的元素;如果不存在,则插入新元素.
// 插入新元素后,返回的迭代器也指向这个新插入的元素
//然后返回该迭代器的second,也就是值的引用,返回新插入的节点的值。这符合标准map的operator[]的行为,
//即允许通过[]访问并可能插入新元素。这里的V()是默认构造的,例如如果是int就是0,
//如果是string就是空字符串。
V& operator[](const K& key)
{pair<iterator, bool> ret = _t.Insert({ key, V() });iterator it = ret.first;//it->second,而it的类型是RBTree的迭代器。当RBTree存储的是pair<const K, V>时,//迭代器的operator->应该返回指向该pair的指针,因此it->second应该是V& 类型,允许修改return it->second;
}
2.2.5 总代码以及一些注意事项
那么到这里,基本上主要的咱们都实现了,还有一些东西,大家请看代码,里面都有我的注释,不懂的,请看注释:
//RBTree.h
enum Colour
{RED,BLACK
};// red black
template<class T>
struct RBTreeNode
{//这里的T就相当于是红黑树中的数据类型,即源码中对应的valueT _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){}
};
//这个地方,迭代器的结构体,只用于实现迭代器的一些内部的东西
//而对于像迭代器begin,end这些东西,不是我内部需要实现的
// 并且,这个是双向迭代器,所以只支持++,--,不支持+,-
// 这个东西,是谁用,谁就去定义就可以了
//这东西咱们之前在stl容器的实现中,也讲过
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;Node* _root;//`_root`成员变量用于在迭代过程中进行回溯RBTreeIterator(Node* node, Node* root):_node(node), _root(root){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_right){// 1、当前节点的右不为空// 下一个就是右子树中序第一个(最左节点)Node* minLeft = _node->_right;while (minLeft->_left){minLeft = minLeft->_left;}_node = minLeft;}else{// 2、当前节点的右为空// 下一个是当前节点的祖先,孩子是父亲左那个祖先Node* cur = _node;Node* parent = cur->_parent;// parent为空,cur是根,说明当前树走完了,// nullptr给_node,nullptr做end()//并且这个地方需要的是孩子是父亲的左,所以说,//如果遇到孩子是父亲的右的,进入循环,继续寻找while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}/*header 的作用与设计原理结构设计header 是一个辅助节点,不存储实际数据,其成员含义:parent:指向红黑树的根节点。left:指向树的最小节点(最左叶子)。right:指向树的最大节点(最右叶子)。根节点的 parent 也指向 header,形成闭环。边界处理:end() 迭代器:指向 header。begin() 迭代器:指向 header->left(最小节点)。关键行为:end()--(即 header--)返回 header->right(最大节点)。最大节点的 ++ 会走到 header(即 end())。最小节点的 -- 会走到 header,但通常不允许对 begin() 执行 --。*/Self& operator--(){//从根节点 _root 出发,不断向右子树遍历,直到找到最右侧节点(最大值节点)。//将 _node 设为该节点,完成从 end() 到最后一个元素的移动。if (_node == nullptr)//这个为什么要判断多余的一步呢?//其实也不是多余,因为你如果没有这一句判断//那么你的end()指向的是空,则_node也是空,// 访问 _node->_left 会导致 空指针解引用,引发未定义行为(如程序崩溃),//这不铁铁的坑了吗?{// --end(),到最右节点//这一句判断代码,才是从end()到最右节点的关键代码。Node* maxRight = _root;while (maxRight && maxRight->_right){maxRight = maxRight->_right;}_node = maxRight;}//在红黑树(或标准容器)中,end() 通常指向容器末尾的“超尾”位置(如 nullptr)// ,表示一个无效的哨兵位置。//当用户对 end() 迭代器执行 -- 操作时,预期是获取容器中的最后一个有效元素。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;}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* minLeft = _root;while (minLeft && minLeft->_left){minLeft = minLeft->_left;}//构造一个迭代器,其 _node 成员指向该最小节点,_root 成员保存树的根节点//咱们上面不是写了一个RBTreeIterator了嘛?这个Iterator就是他的typedef//不过没关系,你看准上面的RBTreeIterator的两个成员变量,一个是node,当前节点//一个是根节点,所以得跟上面的保持一致,才可以构造迭代器。return Iterator(minLeft, _root);}Iterator End(){//end() 通常指向最后一个元素的下一个位置(无效位置),用 nullptr 表示。//反向遍历支持:当对 end() 迭代器执行 -- 操作时,代码会根据 _root 找到最大节点// (见之前的 operator-- 实现)。return Iterator(nullptr, _root);}ConstIterator Begin() const{Node* minLeft = _root;while (minLeft && minLeft->_left){minLeft = minLeft->_left;}return ConstIterator(minLeft, _root);}ConstIterator End() const{return ConstIterator(nullptr, _root);}pair<Iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//注意看,插入是pair类型的,所以这里要用花括号,//多参数的隐式类型转换,要用花括号,前面的代表iterator//后面的代表bool类型//这里的iterator中两个都是_root,第二个都是_root,但是第一个表示当前节点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{//第二个都是_root,但是第一个表示当前节点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){Node* uncle = grandfather->_right;// 1、u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else // 2、u不存在或者u存在且为黑{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 // (parent == grandfather->_right){Node* uncle = grandfather->_left;// 1、u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续往上处理cur = grandfather;parent = cur->_parent;}else // 2、u不存在或者u存在且为黑{if (cur == parent->_right){// g// u p // cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;//第二个都是_root,但是第一个表示当前节点return { Iterator(newNode, _root), true };}void InOrder(){_InOrder(_root);cout << endl;}int Height(){return _Height(_root);}int Size(){return _Size(_root);}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{//第二个都是_root,但是第一个表示当前节点return Iterator(cur, _root);}}return End();//如果没找到,就返回end()}private:int _Size(Node* root){return root == nullptr ? 0 :_Size(root->_left) + _Size(root->_right) + 1;}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;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}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 (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}private:Node* _root = nullptr;
};
上面是RBTree.h中的代码,也是主要的底层的框架代码。
//mymap.h
template<class K, class V>
class map
{struct MapKeyOfT{//map 和 set 分别提供不同的仿函数(如 _KeyOfValue),用于从节点中提取键值://map 提取 pair::first。//set 直接返回节点值本身。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);}iterator find(const K& key){return _t.Find(key);}//operator[]的实现是关键,这里首先尝试插入一个键为key,值默认初始化的V类型的pair。//如果键存在,插入失败,但返回的迭代器指向已存在的元素;如果不存在,则插入新元素.// 插入新元素后,返回的迭代器也指向这个新插入的元素//然后返回该迭代器的second,也就是值的引用,返回新插入的节点的值。这符合标准map的operator[]的行为,//即允许通过[]访问并可能插入新元素。这里的V()是默认构造的,例如如果是int就是0,//如果是string就是空字符串。V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert({ key, V() });iterator it = ret.first;//it->second,而it的类型是RBTree的迭代器。当RBTree存储的是pair<const K, V>时,//迭代器的operator->应该返回指向该pair的指针,因此it->second应该是V& 类型,允许修改return it->second;}private:// pair可以修改,pair的K类型用const修饰,保证key不能被修改,但是value是可以被修改的RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
以上是mymap.h中的代码,即根据最底层的东西实现map。
template<class K>
class set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public://这里使用了typename关键字来告诉编译器RBTree<K, const K, SetKeyOfT>::Iterator是一个类型//,而不是静态成员变量或者其他。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{//这个地方代表_t这个成员变量的类型是const的,即const RBTree<K, const K, SetKeyOfT>//_t是const的,所以会调用const版本的Begin()//正好,RBTree中也有const版本的Begin()return _t.Begin();}const_iterator end() const{return _t.End();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}iterator find(const K& key){return _t.Find(key);}private:// 第一个模板参数带const,保证key不能被修改RBTree<K, const K, SetKeyOfT> _t;
};
这个是myset.h,是根据最底层的来实现set。
OK,这一章代码可能比较多,但我要说的话都结合着代码,因为这样效果更佳。
本篇完......................