本期我们来对map和set进行模拟实现,此处需要红黑树基础,没有看过红黑树的小伙伴建议先去看看红黑树,如果没了解过map和set的小伙伴也建议先去看一看,博客链接我都放在这里了
C++红黑树_KLZUQ的博客-CSDN博客
C++-map和set_KLZUQ的博客-CSDN博客
目录
源码剖析
代码实现
迭代器
全部代码
我们先来看看源码
这是set.h的头文件,其中stl_tree里实现的就是红黑树,里面还有set和multiset,我们先来看set
我们正常情况会认为,map和set的实现是两棵树,一棵树kv结构,一棵是k结构,但其实不是的,map和set使用的是一棵树
这里的代码进行了一些裁减,当然裁减的是一些无关紧要的东西,我们来看重点
我们可以看到上面的rb_tree是一个kv结构
但是我们从typedef来看,无论是key还是value,他们其实都是Key,下面我们再看看map
map也是kv结构,这里的key是Key,但是value是一个pair
我们可以看到,树的成员里有一个header,是link_type,而它的本质其实就是node* ,而树的节点存的是Value
但是这个value就是真的value吗?不是的,对于map而言,这里的value是pair,对于set而言,这里的value是Key,真正决定树里面存什么的是第二个模板参数传的是什么,我们继续看
这里先是有一个基类base,里面就是红黑树的正常结构,color,parent,left和right
然后用了一个子类去继承,这里才是node
Value是value_field,rb_tree到底是k结构还是kv结构,谁也不知道,这里是搞了一个泛型
另外,这里的node也不是必须用继承,只是库里面喜欢这样写而已,一会我们实现时可以不用继承
我们可以体会到,库的设计是非常强的,只用了一棵红黑树就解决了所有问题,只取决于传的第二个模板参数,这里的value到底是key还是pair都可以由我们选择
另外这里可能会有人提出一个疑问,我们可以不传第一个模板参数Key吗?答案是不行的,因为map和set只是封装而已,核心部分的接口是调用树里面的,而树里面会调用find和erase等等,他们都是需要Key来适配的,我们再想想,map和set用的也并不是同一棵树,而是同一个类模板,同一个类模板用不同的模板参数实现出不同的类,大家要仔细想想
我们先把之前写的红黑树拿过来(红黑树源码可以去本期博客开头部分的链接里去找)
template
struct RBTreeNode
{
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
T _data;
Colour _col;
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
,_col(RED)
{}
};
template
struct RBTree
{
typedef RBTreeNode Node;
public:
bool Insert(const T& data)
并且我们需要修改一下代码,这里我们把V改为了T,并且去掉了pair改为data
//MySet.h
#include"RBTree.h"
namespace bai
{
template
class set
{
private:
RBTree_t;
};
}
//MyMap.h
#include"RBTree.h"
namespace bai
{
template
class map
{
private:
RBTree>_t;
};
}
然后我们写一写框架
此时的调用关系就是这样的
下面我们还要继续修改红黑树里的代码,红黑树里有很多比较大小的语句,对于set没什么,但对于map,我们传的是pair,我们希望用first来比较,所以下面我们要进行多处修改,需要使用仿函数来进行控制
这个仿函数在库里面也有,意思是把value里的key取出来
namespace bai
{
template
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
private:
RBTree_t;
};
}
对于set,我们返回key即可
namespace bai
{
template
class map
{
struct MapKeyOfT
{
const K& operator()(const pair& kv)
{
return kv.first;
}
};
private:
RBTree, MapKeyOfT>_t;
};
}
对于map,我们取出pair的first返回
template
struct RBTree
{
typedef RBTreeNode Node;
public:
Node* Find(const K& key)
{
}
bool Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
KeyOfT kot;
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);
cur->_col = RED;
if (kot(parent->_data) < kot(data))
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
接着就是修改insert的部分代码,我们可以看到,比较大小时我们都使用的是kot,大家想想,如果不使用仿函数的话,我们该如何比较大小呢?对于set可以直接比较,但是map就不行了(其实这里map的pair也可以直接比较,库里面实现了,但是库里面的比较方法并不是我们需要的比较大小,所以这里需要我们自己写一下)
我们简单画一下调用关系就是这样 ,这里的比较并不是使用kot比较,而是kot会把T对象的值拿来进行比较,比如map,就会把pair中的first返回
我们看到库里面除了kot,还有一个compare,这个compare是用来比较T的大小的,也就是说,库用了一个仿函数来取T对象里的值,又用另一个仿函数比较T的大小
其实这个kot完全是为了map设计的,因为map的data比较不符合我们的需求,是一个pair,所以需要仿函数的帮助
大家仔细看,KeyOfT是在树这一层的,在map和set这一层是没有keyoft的
Node* Find(const K& key)
{
Node* cur = _root;
KeyOfT kot;
while (cur)
{
if (kot(cur->_data) < key)
{
cur = cur->_right;
}
else if(kot(cur->_data) > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
我们还要给红黑树写一个find
然后在set和map里再写一层insert,并且加上public
我们简单测试一下,没有问题
接下来我们来实现迭代器
这是stl库里set的迭代器,是rep_type
而它的本质就是rb_tree
另外我们还发现,无论是迭代器还是const迭代器,它其实都是const_iterator
我们继续追溯源头,这是stl_tree.h里的,它的迭代器是这样的
这是它的定义,这里和节点那里一样,都是写了一个基类base,然后继承
这是base的结构 ,里面有一个节点的指针node
然后operator*返回里面的value_field,这个value_field对于set而言就是key,对于map而言就是pair,当然这些其实都不是重点
重点是++和--,我们实现列表等等++和--其实没啥,但是这里是一棵树,它++怎么到下一个节点呢?
我们写代码一般是这样写的,begin是其实位置,而这棵树是二叉搜索树,所以应该是最左节点,也就是最小的值 ,我们看看库里面是不是也是这样的
库里面返回的是一个leftmost
库里面返回的是一个header->left,和我们要实现的有一些不一样,一会我们会讲解一下库里面是什么情况,下面我们来看++怎么走
这里我们需要不借助栈来完成中序的非递归
假设it在这个地方,++要找中序的下一个,也就是10,如果右树不为空,我们要访问右树的最左节点,对于所有节点,我们都可以这样做
我们再看右为空的情况, 我们需要访问它的祖先(注意,这里不一定是父亲),如果it是父亲的左,那我们访问父亲,如果it是父亲的右(也就是it在7的位置时),说明it访问完后父亲也完了,我们需要去祖先里父亲是左的那一个,也就是8的位置
总结一下:右不为空,访问右树最左节点,右为空,访问孩子是父亲左的那个祖先
template
struct __TreeIterator
{
typedef RBTreeNode Node;
typedef __TreeIterator Self;
Node* _node;
__TreeIterator(Node* node)
:_node(node)
{}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
Self& operator++()
{
}
};
我们先把框架写好
Self& operator++()
{
if (_node->_right)
{
//右树最左节点
Node* subLeft = _node->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
_node = subLeft;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
//找孩子是父亲左的祖先节点,即下一个要访问的节点
while (parent && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
接着我们来实现++,if里边的逻辑很好理解,就是右树不为空,else里就是右树为空,需要找孩子是父亲左的那个祖先,最后返回*this即可,这里建议画图理解
接着我们还要在树里面实现一下
我们还要在set里写一下,但是此时是不能运行的,因为类模板没有被实例化时是没有生成具体代码的,里面可能有一些语法问题等等,编译器在这里是不敢去类模板里找这个iterator的,iterator是属于RBTree这个类域的,找出来还设计没有实例化的具体参数,比如这里的K,这个K具体是int还是char,是不知道的,所以编译器在这里是认不出来的,而且,编译器在类里面去取,取到的可能是一个内嵌类型(一种是typedef,一种是内部类),也可能是静态成员变量,那iterator到底是内嵌类型还是静态成员变量,编译器也不知道
所以我们需要在这里加一个typename,告诉编译器等类模板实例化了再去找
这时候我们的迭代器就能跑起来了,是不是很有趣?
我们再把map的也补上
和set不一样的是这里出错了
我们实现的operator*返回的是data,对于set而言data是key,但是对于map而言,是一个pair
pair是不支持流插入的
所以这里我们可以使用->,另外大家还记得吗?这里是两个->,编译器优化了一个,这也是我们之前讲过的知识
此时我们还可以使用auto和范围for
虽然上面我们实现了这么多,但是还是有很多大坑的
比如我们的set是允许修改的,这可出现问题了,所以,任重道远啊
还记得我们上面说了库里面的无论iterator和const_iterator都是const吗?下面我们就要按照库的方法走
另外,map不能和set用一样的方法,对于map,key不允许修改,但是value是允许的
所以map的迭代器是正常的
也就是说,对于map来说,我们要不允许first,而允许second ,而且对于first这一行要在编译时就报错
库里面是这样做的,把key设为了const,下面我们来解决这些问题
我们给迭代器和树加上两个模板参数,如果是普通迭代器,我们传T*,T&,const就传const版本
再修改修改代码
typedef typename RBTree::const_iterator iterator;
typedef typename RBTree::const_iterator 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();
}
接着我们在set里写出const版本,并且我们把const和普通迭代器都改为const,和库里面是一样的
我们再看看库里面的,const版本普通迭代器,和我们的还是有点不一样的
此时我们运行,这里报错了
这里的原因是
t是一个普通对象
就会调用普通的begin,返回一个普通迭代器,这里就存在一个转换
大家要知道,这两个迭代器是同一个类模板,传不同的参数, 实例化出不同的类型
首先我们要提供const版本的begin和end
我们再次看看库里面的,这是stl_set.h 里的
我们和库里面改成一样的,把我们的const版本先屏蔽掉 ,结果成功了
我们来看,如果我们不加const,t就是普通的t,只能调用普通的begin,返回的是普通的iterator,这里的iterator我们看起来是iterator,其实不是的
它是我们typedef出来的const_iterator
它的本质是这样的,也就是说我们只提供这个就行
那为什么我们还要写普通的iterator呢?
因为我们使用的时候写的是普通的,这里set的问题就暂时解决了,下面我们来看map的
这里我们模仿库里面的写法,在k前加上const
另外别忘了修改成员里的
此时就可以只修改value,而key不能修改
我们还要提供一下const版本的,普通的迭代器key不可以修改,value可以,const迭代器是都不可以修改
比如这里,使用了const迭代器就都不可以修改了
我们再把operator==实现一下,然后给这些能加const的加上const,下面我们实现一下operator--
--就是反过来,++走的是中序,--就是右子树,根,左子树的顺序
Self& operator--()
{
if (_node->_left)
{
//找左树最右节点
Node* subRight = _node->_left;
while (subRight->_right)
{
subRight = subRight->_right;
}
_node = subRight;
}
else
{
//孩子是父亲右的那个节点
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
逻辑和++反一下即可,下面我们来看看库里面的结构
库和我们不同的是它增加了一个哨兵位的头节点,并且让它的左指向最左节点,右指向最右节点
这样的好处是可以快速找到最小节点和最大节点
所以库里面的是leftmost和rightmost
而它的end就直接是header,而我们的end是空,库里面的begin相对而言更高效一点,不够这是要付出代价的,比如在最左边插入一个节点,header的left就需要修改,另外,这里的header的parent是指向根的,然后root的parent指向header,删除,旋转等等都是需要维护header的
而且还要单独判断一下这个特殊情况,不然会死循环,这里cur等于parent的右,就会无限循环
我们看库里面的,这些我们了解即可
下面我们再实现一下operator[ ]
operator[ ] 的实现需要insert,我们看看库里面的insert,first是迭代器,second是bool
所以我们需要把树里面的insert改一下
修改返回值和返回类型
接着修改map和set的insert
然后我们编译,就出错了 ,set的insert是编不过的
这里的t是普通对象,调用insert,返回普通迭代器,也就是pair里的迭代器是普通迭代器
但是这里的迭代器是const_iterator
是这个样子,我们来看看库里面是怎么解决的
这是stl_set库,它用了pair的first去构造了一个
结合起来看一下
再看我们的,大家先想想,这里选中的iterator是const还是普通的 ?
是const,而后面的ret.first是普通迭代器,这里是不能用make_pair的一个原因,make_pair是自己推
这里的本质是调用了pair的构造函数
是这样子的,所以这里的本质是用普通迭代器构造const迭代器,我们以前实现的迭代器都是不支持的
库里面支持这样玩的原因是因为有这样一个函数 ,我们看最后一行,按理来说迭代器是不需要写拷贝构造的,但是这里并不是纯的拷贝构造,这里没有用self,大家仔细看self和iterator的区别
self就是这个迭代器,但iterator并不一定是
再结合这里来看
当这个类被实例化成const迭代器时,这个函数是一个构造函数,支持普通迭代器构造const迭代器
普通迭代器不受传的Ptr和Ref影响, const迭代器传const Ref和const Ptr时,普通迭代器还是value&和value*,始终是一个普通迭代器,这个函数就相当于一个普通的构造函数,它的参数是普通迭代器,用来构造const迭代器(链表那里也是这样做的)
当这个类被实例化为普通迭代器时,这个函数就是一个拷贝构造,普通迭代器构造普通迭代器,就是一个拷贝构造
所以我们也需要加上构造
V& operator[](const K& key)
{
pair ret = insert(make_pair(key, V()));
return ret.first->second;
}
此时我们终于可以完成operator[ ] 了,这里先调insert,然后调用make_pair,first是key,value是V的缺省值,如果没有,会先插入,如果有了就不插入,ret是pair,pair.first是一个迭代器,然后用->取second
我们简单测试一下,就和库里面差不多了
//RBTree.h
#include
using namespace std;
enum Colour
{
RED,
BLACK
};
template
struct RBTreeNode
{
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
T _data;
Colour _col;
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
,_col(RED)
{}
};
template
struct __TreeIterator
{
typedef RBTreeNode Node;
typedef __TreeIterator Self;
typedef __TreeIterator Iterator;
__TreeIterator(const Iterator& it)
:_node(it._node)
{}
Node* _node;
__TreeIterator(Node* node)
:_node(node)
{}
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;
}
Self& operator--()
{
if (_node->_left)
{
//找左树最右节点
Node* subRight = _node->_left;
while (subRight->_right)
{
subRight = subRight->_right;
}
_node = subRight;
}
else
{
//孩子是父亲右的那个节点
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Self& operator++()
{
if (_node->_right)
{
//右树最左节点
Node* subLeft = _node->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
_node = subLeft;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
//找孩子是父亲左的祖先节点,即下一个要访问的节点
while (parent && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
};
template
struct RBTree
{
typedef RBTreeNode Node;
public:
typedef __TreeIterator iterator;
typedef __TreeIterator const_iterator;
iterator begin()
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)//前面加条件是防止空树
{
leftMin = leftMin->_left;
}
return iterator(leftMin);
}
iterator end()
{
return iterator(nullptr);
}
const_iterator begin() const
{
Node* leftMin = _root;
while (leftMin && leftMin->_left)//前面加条件是防止空树
{
leftMin = leftMin->_left;
}
return const_iterator(leftMin);
}
const_iterator end() const
{
return const_iterator(nullptr);
}
Node* Find(const K& key)
{
Node* cur = _root;
KeyOfT kot;
while (cur)
{
if (kot(cur->_data) < key)
{
cur = cur->_right;
}
else if(kot(cur->_data) > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
pair Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(iterator(_root),true);
}
Node* cur = _root;
Node* parent = nullptr;
KeyOfT kot;
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 make_pair(iterator(cur), false);
}
}
cur = new Node(data);
cur->_col = RED;
Node* newnode = cur;
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)//parent在grandfather的左边
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)//叔叔存在且为红
{
//变色
uncle->_col = BLACK;
parent->_col = BLACK;
grandfather->_col = RED;
//继续向上处理
cur = grandfather;
parent = cur->_parent;
}
else//叔叔不存在/存在且为黑
{
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else//parent在grandfather的右边
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)//叔叔存在且为红
{
uncle->_col = BLACK;
parent->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//叔叔不存在/存在且为黑
{
if (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 make_pair(iterator(newnode), true);
}
void RotateL(Node* parent)//左单旋
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
parent->_right = curleft;
if (curleft)
{
curleft->_parent = parent;
}
cur->_left = parent;
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (parent == _root)//parent是根节点
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else
{
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
void RotateR(Node* parent)//右单旋
{
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = curright;
if (curright)
{
curright->_parent = parent;
}
Node* ppnode = parent->_parent;
cur->_right = parent;
parent->_parent = cur;
if (ppnode == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = cur;
}
else
{
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
bool IsBalance()
{
return IsBalance(_root);
}
bool CheckColour(Node* root,int blacknum,int benchmark)//检查节点颜色
{
if (root == nullptr)
{
if (benchmark != blacknum)//黑色节点数量不对
return false;
return true;
}
if (root->_col == BLACK)//检测黑色节点数量
{
++blacknum;
}
if (root->_col == RED && root->_parent && root->_parent->_col == RED)
{
cout << root->_kv.first << "连续红节点" << endl;
return false;
}
return CheckColour(root->_left,blacknum, benchmark)
&& CheckColour(root->_right, blacknum, benchmark);
}
bool IsBalance(Node* root)
{
if (root == nullptr)
return true;
if (root->_col != BLACK)
return false;
int benchmark = 0;//黑色节点基准值
Node* cur = _root;
while (cur)
{
if (cur->_col==BLACK)
++benchmark;
cur = cur->_left;
}
return CheckColour(root,0, benchmark);
}
private:
Node* _root = nullptr;
};
//MySet.h
#include"RBTree.h"
namespace bai
{
template
class set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename RBTree::const_iterator iterator;
typedef typename RBTree::const_iterator const_iterator;
const_iterator begin() const
{
return _t.begin();
}
const_iterator end() const
{
return _t.end();
}
/*const_iterator begin() const
{
return _t.begin();
}
const_iterator end() const
{
return _t.end();
}*/
pair insert(const K& key)
{
pair::iterator, bool> ret = _t.Insert(key);
return pair(ret.first,ret.second);
}
private:
RBTree_t;
};
}
//MyMap.h
#include"RBTree.h"
namespace bai
{
template
class map
{
struct MapKeyOfT
{
const K& operator()(const pair& kv)
{
return kv.first;
}
};
public:
typedef typename RBTree, MapKeyOfT>::iterator iterator;
typedef typename RBTree, MapKeyOfT>::const_iterator 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();
}
V& operator[](const K& key)
{
pair ret = insert(make_pair(key, V()));
return ret.first->second;
}
pair insert(const pair& kv)
{
return _t.Insert(kv);
}
private:
RBTree, MapKeyOfT>_t;
};
}
以上即为本期全部内容,希望大家可以有所收获
如有错误,还请指正