目录

基本框架
红黑树是map和set的底层数据结构,红黑树的代码实现戳红黑树实现
现在要用红黑树封装出map和set两个容器,过程中需要对红黑树略加修改,读者可以打开红黑树代码一起看。
我们知道map中封装有两个变量:key和value,而set中则是以key同时作为键和值。
这里我们先看下map和set的STL源码如何对这两个变量进行封装:
- map
map源码中的键(key)值(value)类型:key —— Key
,value —— pair<const Key ,Value>
源码把value封装成了pair,first仍为Key,second为数据类型T。
- set
map源码中的键(key)值(value)类型:key —— Key
,value —— Key
显然这两个容器的key还保持模板参数Key的的类型,但是value的类型产生了区别,
红黑树模板参数Value接受的类型将分别是map的 pair<const Key,T>
和set的 Key
。
我们再查看STL中红黑树的部分源码:
可以看到源码中红黑树的结点模板参数Value决定了树结点中存放值的类型,因为map和set的value中都已经包含了键值key,在红黑树中是可以通过索引定位到具体结点的。
我们根据结点的类型先定下map和set的基本框架,
- map基本框架
namespace mystd
{
template<class K,class Value>
class map
{
public:
private:
RBTree<K, pair<const K, Value>> _t;//Value类型为pair
};
}
- set基本框架
namespace mystd
{
template<class K>
class set
{
public:
private:
RBTree<K,K> _t;//Value类型为K
};
}
- 红黑树结点代码,只接受Value类型即可:
//红黑树的树结点
template<class Value>
struct RBTreeNode
{
RBTreeNode<Value>* _left;
RBTreeNode<Value>* _right;
RBTreeNode<Value>* _parent;
Value _data;
Colour _colour;
RBTreeNode(const Value& data):
_left(nullptr),
_right(nullptr),
_parent(nullptr),
_data(data),
_colour(RED)
{}
};
- 红黑树的基本框架:
template<class K, class Value>
class RBTree
{
typedef RBTreeNode<Value> Node;//根据Value的类型判断map还是set,pair<const K,V>或者 K
private:
Node* _root;
};
红黑树仿函数 KeyofValue
我们观察红黑树源码的类模板的第三个参数:
这个类有什么用呢?
我们知道map和set的底层是一棵红黑树,那么红黑树在插入,删除和搜索时会将红黑树结点中的 _data
与函数中的索引参数 key
比较大小,从而满足二叉搜索树的属性(左小右大,或左大右小),但是树结点并不知道 _data
是什么类型?
如果我们正在使用map,那么_data的类型是pair< K , Value >,于是就要提取_data中的K类型值来和函数参数key作比较。
如果正在使用set,那么_data本身就是K类型,直接可以和key进行比较。
由于_data是什么类型是由map或set决定的,谁提供_data,谁就要提供解析_data中key值的方法,于是我们需要在map和set类中各自添加一个仿函数类,如下
- map中添加仿函数类
MapKOfValue
class map
{
public:
//内部类 仿函数 拿到Value中的key值
struct MapKOfValue
{
//map 传给红黑树的value类型是pair,为了比较大小,需要提取出key值
const K& operator()(const pair<const K, Value>& kv)
{
return kv.first;
}
};
private:
RBTree<K, pair<const K, Value>,MapKOfValue> _t;
};
- set 中添加仿函数类
SetKOfValue
class set
{
public:
struct SetKOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
private:
RBTree<K,K,SetKOfValue> _t;
};
- 红黑树类模板也迎来更新
template<class K, class Value,class KeyOfValue>//增加仿函数类KeyOfValue
class RBTree
{
typedef RBTreeNode<Value> Node;
private:
Node* _root;
};
红黑树迭代器
要实现map和set的迭代器,我们需要先为红黑树创建迭代器,随后封装在map和set中即可。
红黑树迭代器框架如下:
template <class Value,class Ref,class Ptr>
struct RBTreeIterator
{
typedef RBTreeNode<Value> Node;
typedef RBTreeIterator<Value,Ref,Ptr> Self;
//将提供给反向迭代器 作为返回类型
typedef Ref Reference;
typedef Ptr Pointer;
Node* _node;
RBTreeIterator(Node* node) :_node(node)
{}
Ref operator*()
{
//....
}
Ptr operator->()
{
//....
}
bool operator!=(const Self& s)const
{
//....
}
bool operator==(const Self& s)const
{
//....
}
Self operator++()
{
//....
}
Self operator--()
{
//.....
}
};
迭代器的成员变量是结点指针,所有的成员函数都
其中类模板参数:
Value
: 结点中的存放数据类型Ref
: 结点类型的引用Ptr
: 结点类型的指针
Ref和Ptr是为了方便建立const修饰的迭代器,在迭代器重载 operator&
和 operator->
时,不论外边传入的模板参数是什么,都可以返回Ref和Ptr,普通迭代器的 Ref
匹配 Value&
,Ptr匹配 Value*
,而const迭代器的 Ref
匹配 const Value&
,Ptr匹配 const Value*
。
可以看到这两个模板参数的引入使得代码更加灵活,体会泛型编程与类型的力量。
operator* 和 operator->
- 解引用返回结点存储的数据的引用
- 箭头返回数据的指针
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
注意:因为箭头的重载返回的是指针,真正实用的时候应该是it->->,但是编译器会优化,所以届时只使用一个箭头即可。
operator== 和 operator!=
bool operator!=(const Self& s)const
{
return _node!=s._node;
}
bool operator==(const Self& s)const
{
return _node==s._node;
}
operator++ 和 operator–
先看++操作,根据中序的规则,那么迭代器自增1后找到下一个结点的规则如下:
- 右树不空, 访问右树中的中序序列的第一个结点(即右树的最左结点)
- 右树为空,沿着父指针往上找,直到找到该路径上溯的孩子不是自身右孩子的祖先结点
Self& operator++()
{
if(_node->_right)
{
Node* cur=_node->_right;
while(cur->_left)
{
cur=cur->_left;
}
_node=cur;
}
else
{
Node* parent=_node->_parent;
Node* cur=_node;
while(parent && parent->_right==cur)
{
cur=parent;
parent=parent->_parent;
}
_node=parent;
}
return *this;
}
现在看–操作,可以理解为++操作的逆序(右根左),找到上一个结点的规则如下:
- 当前结点左树不空,找到左树的最右结点
- 左树为空,沿着父指针往上找,直到找到该路径上溯的孩子不是自身左孩子的祖先结点
Self& operator--()
{
if (_node ->_left)
{
Node* cur = _node->_left;
while (cur->_right)
{
cur = cur->_right;
}
_node = cur;
}
else
{
Node* cur = _node;
Node* parent = _node->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
反向迭代器
反向迭代器则是正向迭代器的容器适配器:
// 反向迭代器——使用正向迭代器封装
template <class Iterator>
struct RBTreeReverseIterator
{
public:
typedef typename Iterator::Reference Ref;
typedef typename Iterator::Pointer Ptr;
typedef RBTreeReverseIterator<Iterator> Self;
Iterator _it;//正向迭代器作为成员变量
RBTreeReverseIterator(Iterator it) :_it(it._node)
{}
Ref operator*()
{
return *_it;
}
Ptr operator->()
{
return _it.operator->();
}
bool operator!=(const Self& s)const
{
return _it != s._it;
}
bool operator==(const Self& s)const
{
return _it == s._it;
}
//自增自减与正向迭代器呈反向操作
Self& operator++()
{
--_it;
return *this;
}
Self& operator--()
{
++_it;
return *this;
}
};
封装成map和set的迭代器
首先将迭代器放入红黑树中
template<class K, class Value,class KeyOfValue>
class RBTree
{
public:
typedef RBTreeNode<Value> Node;
typedef RBTreeIterator<Value, Value&, Value*> iterator;
typedef RBTreeIterator<Value, const Value&,const Value*> const_iterator;
typedef RBTreeReverseIterator<iterator> reverse_iterator;
//....
};
由于该迭代器是需要给map和set封装访问的,所以typedef迭代器为public范围。
然后封装进map和set类:
🚩注意我们需要在迭代器前添加 typename
使用 typename 的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有 typename,编译器没有任何办法知道 RBTree<K, pair<const K, Value>::iterator
是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
直到确定了 RBTree
是什么东西,编译器才会知道 RBTree<K, pair<const K, Value>::iterator
是不是一个类型; 然而当模板被解析时,RBTree
还是不确定的。这时我们声明它为一个类型才能通过编译!
- map中添加
迭代器
class map
{
public:
//内部类 仿函数 拿到Value中的key值
struct MapKOfValue
{
//map 传给红黑树的value类型是pair,为了比较大小,需要提取出key值
const K& operator()(const pair<const K, Value>& kv)
{
return kv.first;
}
};
public:
typedef typename RBTree<K, pair<const K, Value>::iterator iterator;
typedef typename RBTree<K, pair<const K, Value>::const_iterator const_iterator;//typename 确保后面为类型名而非变量
typedef typename RBTree<K, pair<const K, Value>, MapKOfValue>::reverse_iterator reverse_iterator;
private:
RBTree<K, pair<const K, Value>,MapKOfValue> _t;
};
- set 中添加
迭代器
class set
{
public:
struct SetKOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
typedef typename RBTree<K, K, SetKOfValue>::iterator iterator;//typename 确保其为类型而非变量
typedef typename RBTree<K, K,SetKOfValue>::const_iterator const_iterator;
typedef typename RBTree<K, K, SetKOfValue>::reverse_iterator reverse_iterator;
private:
RBTree<K,K,SetKOfValue> _t;
};
完善类功能
插入结点——insert函数
STL中map和set返回的类型为:
所以需要对红黑树中insert函数的返回值稍作修改,注意在下面修改的insert函数时仿函数KeyOfValue的使用:
pair<iterator, bool> Insert(const Value& data)
{
KeyOfValue kot;//仿函数类实例化
if (_root == nullptr)
{
_root = new Node(data);
_root->_colour = BLACK;
return make_pair(iterator(_root), true);
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
parent = cur;
if (kot(cur->_data) > kot(data))//提取出Value类型中的key值,再进行比较
{
cur = cur->_left;
}
else if (kot(cur->_data) < kot(data))
{
cur = cur->_right;
}
else
{
return make_pair(iterator(cur), false);
}
}
Node* newnode = new Node(data);
newnode->_parent = parent;
if (kot(parent->_data) >kot(data))
{
parent->_left = newnode;
}
else
{
parent->_right = newnode;
}
cur = newnode;
//开始调整
while (parent && parent->_colour == RED)//最差情况,处理到根节点为止
{
Node* grandfather = parent->_parent;
Node* uncle = nullptr;
if (grandfather->_right == parent) //parent为grandfather右树
{
uncle = grandfather->_left;
if (uncle && uncle->_colour == RED)//情况1,仅需变色,且考虑往上追溯
{
parent->_colour = BLACK;
uncle->_colour = BLACK;
grandfather->_colour = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else //情况2+3 uncle为黑或者不存在,需要旋转+变色
{
//这里不会对uncle进行旋转或是变色处理,我们只关心是折线还是直线情况。
if (cur == parent->_left)//折线情况
{
//对parent做右单旋
RotateR(parent);
swap(cur, parent);
}
//统一处理为直线情况
RotateL(grandfather);
parent->_colour = BLACK;//parent(当前子树的根节点)变为黑色
grandfather->_colour = RED;//grandfather变为红色,以保证路径的黑色结点数量不变
//无需再往上调整了,下次的while会因为parent为黑直接结束循环,不用在此break
}
}
else //parent为grandfather左树
{
uncle = grandfather->_right;
if (uncle && uncle->_colour == RED)//情况1,仅需变色,且考虑往上追溯
{
parent->_colour = BLACK;
uncle->_colour = BLACK;
grandfather->_colour = RED;
//继续往上处理
cur = grandfather;
parent = cur->_parent;
}
else //情况2+3 uncle为黑或者不存在,需要旋转+变色
{
//这里不会对uncle进行旋转或是变色处理,我们只关心是折线还是直线情况。
if (cur == parent->_right)
{
//对parent做左单旋
RotateL(parent);
swap(cur, parent);
}
//统一处理为直线情况
RotateR(grandfather);
parent->_colour = BLACK;
grandfather->_colour = RED;
//无需再往上调整了,下次的while会因为parent为黑直接结束循环,不用在此break
}
}
}//while
_root->_colour = BLACK;//如果grandfather为根节点,那么让其变黑色
return make_pair(iterator(newnode), true);
}
- insert封装进map
public:
pair<iterator,bool> insert(const pair<const K,Value>& data)
{
return _t.Insert(data);
}
- insert封装进set
public:
pair<iterator,bool> insert(const K& data)
{
return _t.Insert(data);
}
下标访问——operator[]函数
- map
public:
Value& operator[](const K& key)
{
pair<iterator, bool> ret = _t.Insert(make_pair(key, Value()));
return ret.first->second;
}
删除结点——erase函数
红黑树的Erase函数很长就不放在这里,可以通过文末的链接查看红黑树类中的删除结点代码。
这里需要注意的是,删除结点是会遇到替换的情况的(删除结点的左右孩子都存在)。
set类可以直接交换待删除结点和替换结点的_data,但是map类无法交换,因为map类的_data为pair< const K,Value > 类型,const修饰无法更改,所以如果需要替换,将const去掉即可。
完整代码
- map
#include "RBTree.h"
namespace mystd
{
template<class K,class Value>
class map
{
public:
//内部类 仿函数 拿到Value中的key值
struct MapKOfValue
{
//map 传给红黑树的value类型是pair,为了比较大小,需要提取出key值
const K& operator()(const pair<const K, Value>& kv)
{
return kv.first;
}
};
public:
// typename 告诉编译器这只是一个名字,暂时不用对模板进行实例化
typedef typename RBTree<K, pair<const K, Value>, MapKOfValue>::iterator iterator;//typename 确保后面为类型名而非变量
typedef typename RBTree<K, pair<const K, Value>, MapKOfValue>::const_iterator const_iterator;
typedef typename RBTree<K, pair<const K, Value>, MapKOfValue>::reverse_iterator reverse_iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
reverse_iterator rbegin()
{
return _t.rbegin();
}
reverse_iterator rend()
{
return _t.rend();
}
pair<iterator,bool> insert(const pair<const K,Value>& data)
{
return _t.Insert(data);
}
Value& operator[](const K& key)
{
pair<iterator, bool> ret = _t.Insert(make_pair(key, Value()));
return ret.first->second;
}
bool erase(const K& key)
{
return _t.Erase(key);
}
iterator find(const K& key)
{
return _t.Find(key);
}
private:
RBTree<K, pair<const K, Value>,MapKOfValue> _t;
};
}
- set
#include "RBTree.h"
namespace mystd
{
template<class K>
class set
{
public:
struct SetKOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename RBTree<K, K, SetKOfValue>::iterator iterator;//typename 确保其为类型而非变量
typedef typename RBTree<K, K, SetKOfValue>::const_iterator const_iterator;
typedef typename RBTree<K, K, SetKOfValue>::reverse_iterator reverse_iterator;
pair<iterator,bool> insert(const K& data)
{
return _t.Insert(data);
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
reverse_iterator rbegin()
{
return _t.rbegin();
}
reverse_iterator rend()
{
return _t.rend();
}
bool erase(const K& key)
{
return _t.Erase(key);
}
iterator find(const K& key)
{
return _t.Find(key);
}
private:
RBTree<K,K,SetKOfValue> _t;
};
}
修改后的红黑树代码戳map与set实现
— end —
青山不改 绿水长流