Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!
我的博客: <但凡.
我的专栏: 《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》
这期我们模拟实现一下基于红黑树的map和set。
目录
1、封装set和map,解决KeyOfT
首先我们先把set和map的大类实现一下:
map:
cpp
#pragma once
#include"RBTree.h"
template<class K,class V>
class map
{
public:
private:
RBTree<K, pair<K, V>> _t;
};
set:
cpp
#pragma once
#include"RBTree.h"
template<class K>
class set
{
public:
private:
RBTree<K, K> _t;
};
对于传入红黑树的两个参数,第一个我们传入关键值key,第二个传入我们存储的实际类型,如果是key就再传入一次,如果是key_value就传入pair类型。
我们需要一个KeyOfT,将key_value里面的key值取出来,因为我们不知道存储的值到底是key还是key_value类型,所以对于map和set我们都需要一个对应的KeyOfT。
map:
cpp
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
set:
cpp
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
这样的话,我们在传入红黑树时需要往模板中传入三个参数,所以我们对红黑树进行改造:
cpp
template<class K,class T,class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
pair<Iterator,bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;//根节点必须是黑
return { Iterator(_root,_root),true };
}
KeyOfT kot;
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
//这访函数返回T值
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->_col = RED;//新增的必须是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;
if (uncle && uncle->_col == RED)//仅调色
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent=cur->_parent;
}
else
{
//u不存在或u存在且为黑
//单边高
if (cur == parent->_left)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//对应的是AVL树左边右边高的情况
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else
{
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 { Iterator(newnode,_root),false };
}
......
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
{
return Iterator(cur,_root);
}
}
return End();
}
private:
......
private:
Node* _root = nullptr;
};
为了节省篇幅我把所有不涉及改动的函数都先去掉了。另外insert里面涉及的迭代器我们之后再说。
2、iterator
**接着我们实现map和set的迭代器。**首先我们在红黑树所在的头文件中把迭代器定义出来:
cpp
template<class T,class ptr,class ref>
class RBTreeIterator
{
public:
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T,ptr,ref> Self;
RBTreeIterator(Node* node, Node* root)
:_node(node),
_root(root)
{}
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->_right)
{
//节点右不为空,下一个节点就是右子树中序第一个(最左节点
Node* minleft = _node->_right;
while (minleft->_left)
{
minleft = minleft->_left;
}
_node = minleft;
}
else
{
//节点右为空,找找祖先,并且该祖先是其父亲的左
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right)//parent是空也结束
{
cur = parent;
parent = parent->_parent;
}
//如果parent为nullptr,nullptr为end()
_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;
}
private:
Node* _node;
Node* _root;
};
对于迭代器这个类,++,--两个操作需要特殊说明一下。
对于++操作,首先,因为我们map和set++是有序数的下一个,对应的就是红黑树的中序遍历的下一个节点。那么我们不可能因为一个++就遍历整个红黑树去找下一个节点吧?所以我们分情况讨论一下,看看红黑树的下一个节点理应出现在哪。**如果当前节点右子树不为空,下一个节点就应该是右子树的最左节点。如果当前节点的右子树为空,下一个节点就应该是父节点,并且该父节点一定是他的父节点(父节点的父节点)的左子树。**如果一直查找直到nullptr,就说明++之后应该是end,那就返回nullptr就可以了。
对于--操作,首先我们要对当前节点是end()的情况做特殊处理,这个节点的上一个节点应该是整棵树的最右节点。第二种情况就是左子树不为空,如果左子树不为空,说明他的上一个节点为左子树的最右节点。第三种情况就是左子树为空,此时他的上一个节点应该是父节点,并且此父节点一定是他的父节点(父节点的父节点)的右子节点。
接下来我们对红黑树进行改造,新增迭代器的部分:
cpp
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;
}
return Iterator(minleft,_root);
}
Iterator End()
{
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);
}
接下来我们对insert进行改造,使他的返回值和源码一样:
cpp
pair<Iterator,bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;//根节点必须是黑
return { Iterator(_root,_root),true };
}
KeyOfT kot;
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
//这访函数返回T值
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->_col = RED;//新增的必须是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;
if (uncle && uncle->_col == RED)//仅调色
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;//继续向上调整
parent=cur->_parent;
}
else
{
//单边高
if (cur == parent->_left)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
else
{
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 { Iterator(newnode,_root),false };
}
接下来我们再去map和set的头文件中,对迭代器进行进一步的封装:
map:
cpp
typedef typename RBTree < K, pair<K, V>, MapKeyOfT>::Iterator iterator;
typedef typename RBTree < K, pair<K, V>, MapKeyOfT>::ConstIterator const_iterator;
pair<iterator,bool> insert(const pair<K, V>& kv)
{
return _t.Insert(kv);
}
iterator begin()
{
return _t.Begin();
}
iterator end()
{
return _t.End();
}
const_iterator begin() const
{
return _t.Begin();
}
const_iterator end() const
{
return _t.End();
}
iterator find(const K& key)
{
return _t.Find(key);
}
set:
cpp
typedef typename RBTree < K, K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree < K, K, SetKeyOfT>::ConstIterator const_iterator;
pair<iterator, bool> insert(const K& key)
{
return _t.Insert(key);
}
iterator begin()
{
return _t.Begin();
}
iterator end()
{
return _t.End();
}
const_iterator begin() const
{
return _t.Begin();
}
const_iterator end() const
{
return _t.End();
}
iterator find(const K& key)
{
return _t.Find(key);
}
注意这里我们typedef的迭代器一定要是在红黑树的大类中我们已经封装好的迭代器,不然没有对应的Begin,End的操作。并且我们要用typename标记,不然编译器不知道iterator到底是一个类还是一个变量。
3、key不支持修改的问题
这个问题就很好解决了,对于set,我们只需要在第二个参数加上const 就行了:
cpp
RBTree<K,const K, SetKeyOfT> _t;
对于map,我们在第二个参数pair中的key值之前加上const:
cpp
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
4、operator[]
**这里的[]本质还是对insert的复用,因为[]本身也可以插入,如果插入成功,就返回新插入的值对应的value值,如果插入失败,就返回原有的该值的value值(具体可以看map和set的使用这一篇)。**那这个操作完全可以通过复用insert来实现:
map:
cpp
V& operator[](const K& key)
{
pair<iterator, bool> t = insert({ key ,V()});//int的缺省值是0
auto it = t.first;
return it->second;//这里会省略一个->
}
set:
set不支持operator[]。
好了,到这里我们就基本实现完了map和set,现在解决大部分读者在学习这里的时候会遇到的一个疑问。为啥模板类中要传入一个K再传入一个T呢?假设在set中,我们存储的值类型为int,那么传两个int进来不是妥妥的重复吗?
**首先,我们模拟实现要和源码尽可能保持一致,源码是这么写的,所以我们也这么写。**那么源码为什么要这么写呢?
确实,对于set来说就是重复传了。但是对于map来说,第一个K的作用是指示key值的类型。因为find和erase操作的函数参数都是K类型的。也就是说**我们得用一个类型来指示我们形参是什么类型的。**假设map中我们只传一个pair进来,我们根本无法确定key值的类型是什么。
并且,本着能用一套逻辑就用一套逻辑的原则,通过传一个key的类型来解决这个问题,总比写两套逻辑,一个是key类型红黑树,一个是key_value类型红黑树要方便得多。
完整代码放在我的gitee了:
好了,今天的内容就分享到这,我们下期再见!