- 基本框架
- 红黑树仿函数 KeyofValue
- 红黑树迭代器
- operator* 和 operator->
- operator== 和 operator!=
- operator++ 和 operator--
- 反向迭代器
- 封装成map和set的迭代器
- 完善类功能
- 插入结点——insert函数
- 下标访问——operator[]函数
- 删除结点——erase函数
- 完整代码
基本框架
红黑树是map和set的底层数据结构,红黑树的代码实现戳红黑树实现
现在要用红黑树封装出map和set两个容器,过程中需要对红黑树略加修改,读者可以打开红黑树代码一起看。
我们知道map中封装有两个变量:key和value,而set中则是以key同时作为键和值。
这里我们先看下map和set的STL源码如何对这两个变量进行封装:
- map
map源码中的键(key)值(value)类型:key —— Key ,value —— pair
源码把value封装成了pair,first仍为Key,second为数据类型T。
- set
map源码中的键(key)值(value)类型:key —— Key ,value —— Key
显然这两个容器的key还保持模板参数Key的的类型,但是value的类型产生了区别,
红黑树模板参数Value接受的类型将分别是map的 pair
我们再查看STL中红黑树的部分源码:
可以看到源码中红黑树的结点模板参数Value决定了树结点中存放值的类型,因为map和set的value中都已经包含了键值key,在红黑树中是可以通过索引定位到具体结点的。
我们根据结点的类型先定下map和set的基本框架,
- map基本框架
namespace mystd
{
template
class map
{
public:
private:
RBTree> _t;//Value类型为pair
};
}
- set基本框架
namespace mystd
{
template
class set
{
public:
private:
RBTree _t;//Value类型为K
};
}
- 红黑树结点代码,只接受Value类型即可:
//红黑树的树结点 templatestruct RBTreeNode { RBTreeNode * _left; RBTreeNode * _right; RBTreeNode * _parent; Value _data; Colour _colour; RBTreeNode(const Value& data): _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _colour(RED) {} };
- 红黑树的基本框架:
template红黑树仿函数 KeyofValueclass RBTree { typedef RBTreeNode Node;//根据Value的类型判断map还是set,pair 或者 K private: Node* _root; };
我们观察红黑树源码的类模板的第三个参数:
这个类有什么用呢?
我们知道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& kv)
{
return kv.first;
}
};
private:
RBTree,MapKOfValue> _t;
};
- set 中添加仿函数类 SetKOfValue
class set
{
public:
struct SetKOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
private:
RBTree _t;
};
- 红黑树类模板也迎来更新
template红黑树迭代器//增加仿函数类KeyOfValue class RBTree { typedef RBTreeNode Node; private: Node* _root; };
要实现map和set的迭代器,我们需要先为红黑树创建迭代器,随后封装在map和set中即可。
红黑树迭代器框架如下:
templatestruct RBTreeIterator { typedef RBTreeNode Node; typedef RBTreeIterator 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封装成map和set的迭代器struct RBTreeReverseIterator { public: typedef typename Iterator::Reference Ref; typedef typename Iterator::Pointer Ptr; typedef RBTreeReverseIterator 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; } };
首先将迭代器放入红黑树中
templateclass RBTree { public: typedef RBTreeNode Node; typedef RBTreeIterator iterator; typedef RBTreeIterator const_iterator; typedef RBTreeReverseIterator reverse_iterator; //.... };
由于该迭代器是需要给map和set封装访问的,所以typedef迭代器为public范围。
然后封装进map和set类:
注意我们需要在迭代器前添加 typename
使用 typename 的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有 typename,编译器没有任何办法知道 RBTree
直到确定了 RBTree是什么东西,编译器才会知道 RBTree
- map中添加 迭代器
class map
{
public:
//内部类 仿函数 拿到Value中的key值
struct MapKOfValue
{
//map 传给红黑树的value类型是pair,为了比较大小,需要提取出key值
const K& operator()(const pair& kv)
{
return kv.first;
}
};
public:
typedef typename RBTree::iterator iterator;
typedef typename RBTree::const_iterator const_iterator;//typename 确保后面为类型名而非变量
typedef typename RBTree, MapKOfValue>::reverse_iterator reverse_iterator;
private:
RBTree,MapKOfValue> _t;
};
- set 中添加 迭代器
class set
{
public:
struct SetKOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
typedef typename RBTree::iterator iterator;//typename 确保其为类型而非变量
typedef typename RBTree::const_iterator const_iterator;
typedef typename RBTree::reverse_iterator reverse_iterator;
private:
RBTree _t;
};
完善类功能
插入结点——insert函数
STL中map和set返回的类型为:
所以需要对红黑树中insert函数的返回值稍作修改,注意在下面修改的insert函数时仿函数KeyOfValue的使用:
pairInsert(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 insert(const pair& data)
{
return _t.Insert(data);
}
- insert封装进set
public:
pair insert(const K& data)
{
return _t.Insert(data);
}
下标访问——operator[]函数
- map
public:
Value& operator[](const K& key)
{
pair 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 map
{
public:
//内部类 仿函数 拿到Value中的key值
struct MapKOfValue
{
//map 传给红黑树的value类型是pair,为了比较大小,需要提取出key值
const K& operator()(const pair& kv)
{
return kv.first;
}
};
public:
// typename 告诉编译器这只是一个名字,暂时不用对模板进行实例化
typedef typename RBTree, MapKOfValue>::iterator iterator;//typename 确保后面为类型名而非变量
typedef typename RBTree, MapKOfValue>::const_iterator const_iterator;
typedef typename RBTree, 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 insert(const pair& data)
{
return _t.Insert(data);
}
Value& operator[](const K& key)
{
pair 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,MapKOfValue> _t;
};
}
- set
#include "RBTree.h"
namespace mystd
{
template
class set
{
public:
struct SetKOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename RBTree::iterator iterator;//typename 确保其为类型而非变量
typedef typename RBTree::const_iterator const_iterator;
typedef typename RBTree::reverse_iterator reverse_iterator;
pair 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 _t;
};
}
修改后的红黑树代码戳map与set实现
— end —
青山不改 绿水长流



