用一棵红黑树同时封装map与set的意义:所谓的 “用一棵红黑树同时封装map与set” 只是在程序员的角度,通过一系列手段,以一个红黑树同时满足map与set。但是在编译器的角度,实际上并不是一颗树实现的,程序员所写的只是一份模板,map与set是需要各自根据模板实例化的,底层并不是一颗红黑树。(更加轻松实现,更加便于维护)
红黑树的源代码
(红黑树K模型模拟实现)
【C++ STL】-- 红黑树的插入实现_川入的博客-CSDN博客
(未经map与set改进的K模型红黑树的源代码)
#include<iostream>
#include<assert.h>
using namespace std;
// 节点颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode {
RBTreeNode<ValueType>* _right; // 节点的右孩子
RBTreeNode<ValueType>* _left; // 节点的左孩子
RBTreeNode<ValueType>* _parent; // 节点的双亲
ValueType _data; // 节点的值
Color _color;
// 构造函数
RBTreeNode(const ValueType& data = ValueType(), Color color = RED)
:_right(nullptr), _left(nullptr), _parent(nullptr)
,_data(data), _color(color)
{}
};
template<class ValueType>
class RBTree {
typedef RBTreeNode<ValueType> Node;
public:
bool insert(const ValueType& data)
{
// 插入的位置是根节点
if (_root == nullptr)
{
_root = new Node(data);
_root->_color = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
// 查找cur插入的位置
while (cur)
{
if (data > cur->_data)
{
parent = cur;
cur = cur->_right;
}
else if (data < cur->_data)
{
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new Node(data);
cur->_color = RED;
if (cur->_data > parent->_data)
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
while (parent && parent->_color == RED)
{
Node* grandfather = parent->_parent;
assert(grandfather);
assert(grandfather->_color == BLACK);
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续向上
cur = grandfather;
parent = cur->_parent;
}
else// 情况二+三:uncle不存在 + 存在且为黑
{
//情况二:右旋 + p变黑,g变红
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
//情况三:左旋 + 情况二(右旋 + p变黑,g变红)
// g
// p u
// c
else
{
RotateL(parent);
RotateR(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
else
{
Node* uncle = grandfather->_left;
//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续向上
cur = grandfather;
parent = cur->_parent;
}
else// 情况二+三:uncle不存在 + 存在且为黑
{
//情况二:左旋 + p变黑,g变红
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
//情况三:右旋 + 情况二(左旋 + p变黑,g变红)
// g
// u p
// c
else
{
RotateR(parent);
RotateL(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
}
_root->_color = BLACK;
return true;
}
// 利用递归前序按升序打印红黑树
void InOrder()
{
_InOrder(_root);
cout << endl;
}
// 检测是否符合红黑树
bool IsBalance()
{
if (_root == nullptr)
{
return true;
}
// 判断根节点是否为黑色
if (_root->_color == RED)
{
cout << "根节点不黑色" << endl;
return false;
}
int benchMark = 0; //某路径的黑节点个数,作为基准值
return PrevCheck(_root, 0, benchMark);
}
private:
// 利用深度优先
bool PrevCheck(Node* root, int blackNum, int& benchMark) //利用引用保存基准值
{
if (root == nullptr)
{
if (benchMark == 0) //将第一个路径的黑节点个数给benchMark,作为基准值
benchMark = blackNum;
else if(blackNum != benchMark)
return false;
return true;
}
if (root->_color == BLACK)
++blackNum;
// 检查是否有连续的红色节点
if (root->_color == RED && root->_parent->_color == RED)
{
cout << "存在连续的红节点" << endl;
return false;
}
return PrevCheck(root->_left, blackNum, benchMark)
&& PrevCheck(root->_right, blackNum, benchMark);
}
// 前序递归
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
// 右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 将subL连接到parent的左
parent->_left = subLR;
if (subLR) // 可能subL的右子树不存在
subLR->_parent = parent;
Node* pparent = parent->_parent;
// 将parent连接到subL的右节点上,成为subL的右子树
subL->_right = parent;
parent->_parent = subL;
// 将parent的父节点状态给予subL
if (_root == parent) // parent是根
{
_root = subL;
subL->_parent = nullptr;
}
else // parent不是根
{
if (pparent->_left == parent)
pparent->_left = subL;
else
pparent->_right = subL;
subL->_parent = pparent;
}
}
// 左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
// 将subR连接到parent的右
parent->_right = subRL;
if (subRL) // 可能subR的左子树不存在
subRL->_parent = parent;
Node* pparent = parent->_parent;
// 将parent连接到subR的左节点上,成为subR的左子树
subR->_left = parent;
parent->_parent = subR;
// 将parent的父节点状态给予subR
if (_root == parent) // parent是根
{
_root = subR;
subR->_parent = nullptr;
}
else // parent不是根
{
if (pparent->_left == parent)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
}
Node* _root = nullptr;
};
void TestRBTree()
{
size_t N = 100;
srand(time(0));
RBTree<int> t;
for (size_t i = 0; i < N; ++i)
{
int x = rand();
t.insert(x);
}
t.InOrder();
if (t.IsBalance())
cout << "是红黑树" << endl;
else
cout << "不是红黑树" << endl;
}
int main()
{
TestRBTree();
return 0;
}
模板参数的改进
我们知道set是经典的k模型,map是经典的kv模型。那么是不是代表,对于set与map的实现需要分别利用k模型的红黑树和kv模型的红黑树,需要两颗红黑树?
但是STL其实是非常注意复用性的(如:迭代器的复用 - 普通迭代器与const迭代器、栈队列复用之前的容器、优先级队列复用之前的容器) ,并不会让两份代码,大结构和逻辑几乎都是类似的,只有细节的些许差距,以此写两份,导致代码冗余。
让我们看看源代码对齐的处理:
我们简单的实现一下:
模板参数的再次改进
在原来的基础上:对于插入点的查找,插入点的插入都是需要利用data数据与比较路径上节点的_data成员的比较:
- 对于set是没有问题的,set 以RBTree<K, K> _t,将key传给了模板参数T,所以data是key类型的,对于比较是没有问题的。
- 对于map是有问题的,map 以PBTree<K, pair<K, V>>,将pair<K, V>传给了模板参数T,所以data是pair<K, V>类型的:而pair对于比较的operator重载是:
pair比较重载的相关文档
以 operator> 举例:
其是 “data1.frist > data2.frist” 或者 “data1.frist < data2.frist 的情况下 data1.second > data2..second” 都是属于data1 > data2。 所以pair的比较重载是不可用的,而由于pair已经提供了比较重载,我们也就没法自己写比较重载。
基于此基础上,我们就需要增加一个模板参数,一个仿函数。
map与set:
即:根据仿函数比较的地方我们也需要进行改进(如:insert的插入位置查找):
正向迭代器
红黑树的正向迭代器,实际上是对结点指针进行了封装,模板化了T,T&,T*,分别用于结点指针,*与->的operator的重载函数的返回值。
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, Ref, Ptr> Self;
Node* _node; //对节点指针进行封装
//构造函数
__RBTreeIterator(Node* node)
:_node(node)
{}
// ……
}
template<class K, class T, class KeyOfT>
class RBTree {
public://typedef会受访问限定符干扰
typedef __RBTreeIterator<T, T&, T*> iterator;
// ……
}
当正向迭代器调用*是,重载operator*返回对应数据的引用即可:
Ref operator*() //Pef:T&
{
return _node->_data;
}
当正向迭代器调用->是,重载operator->返回对应数据的地址即可:
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;
}
对于正向迭代器的++与--运算符的重载函数,最佳的实现的方式是非递归,而由于此时三叉链的节点(即,同时有:parent,left, right的指针)无需利用栈就可以更加直接的实现:
operator前置++
逻辑如下:
因为,遍历红黑树的核心是:左子树 根 右子树
- 右子树不为空,++就是找右子树的中序第一个节点(最左节点):
- 右子树为空,++就是找孩子不是父亲右的那个祖先父亲节点:
同时实现了,正向迭代器的结尾是end()时为:nullptr。
Self& operator++()
{
// 下一个就是:右子树的最左节点
if (_node->_right)
{
Node* left = _node->_right;
while (left->_left)
left = left->_left;
_node = left;
}
else
{
// 下一个就是:找孩子不是父亲右的那个祖先父亲节点
Node* root = _node;
Node* parent = _node->_parent;
while (parent && root == parent->_right)
{
root = root->_parent;
parent = root->_parent;
}
_node = parent;
}
return *this;
}
operator前置--
因为,到过来遍历红黑树的核心是:右子树 根 左子树,所以与前置++的实现原理一样:
- 左子树不为空,- - 就是找左子树的中序最后一个节点(最右节点)
- 左子树为空,- - 就是找孩子不是父亲左的那个祖先父亲节点
Self& operator--()
{
// 上一个就是:左子树的最右节点
if (_node->_left)
{
Node* right = _node->_left;
while (right->_right)
{
right = right->_right;
}
_node = right;
}
else
{
// 上一个就是:找孩子不是父亲左的那个祖先父亲节点
Node* root = _node; v
Node* parent = root->_parent;
while (parent && root == parent->_left)
{
root = root->_parent;
parent = root->_parent;
}
_node = parent;
}
return *this;
}
insert
由于我们需要实现operator[],所以insert的返回值需要是pair<iterator, bool>,如果没有就插入,如果已经有返回那个已经在的节点的迭代器:
map.h
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _t.insert(kv);
}
set.h
pair<iterator, bool> insert(const K& key)
{
return _t.insert(key);
}
RBTree.h
pair<iterator, bool> insert(const T& data)
{
KeyOfT kot;
// 插入的位置是根节点
if (_root == nullptr)
{
_root = new Node(data);
_root->_color = BLACK;
return make_pair(iterator(_root), true);
}
Node* parent = nullptr;
Node* cur = _root;
// 查找cur插入的位置
while (cur)
{
if (kot(data) > kot(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else
return make_pair(iterator(cur), false);
}
cur = new Node(data);
Node* newnode = cur; //用于返回iterator封装新增节点的地址
cur->_color = RED;
if (kot(cur->_data) > kot(parent->_data))
parent->_right = cur;
else
parent->_left = cur;
cur->_parent = parent;
while (parent && parent->_color == RED)
{
Node* grandfather = parent->_parent;
assert(grandfather);
assert(grandfather->_color == BLACK);
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续向上
cur = grandfather;
parent = cur->_parent;
}
else// 情况二+三:uncle不存在 + 存在且为黑
{
//情况二:右旋 + p变黑,g变红
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
//情况三:左旋 + 情况二(右旋 + p变黑,g变红)
// g
// p u
// c
else
{
RotateL(parent);
RotateR(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
else
{
Node* uncle = grandfather->_left;
//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
if (uncle && uncle->_color == RED)
{
parent->_color = uncle->_color = BLACK;
grandfather->_color = RED;
//继续向上
cur = grandfather;
parent = cur->_parent;
}
else// 情况二+三:uncle不存在 + 存在且为黑
{
//情况二:左旋 + p变黑,g变红
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_color = BLACK;
grandfather->_color = RED;
}
//情况三:右旋 + 情况二(左旋 + p变黑,g变红)
// g
// u p
// c
else
{
RotateR(parent);
RotateL(grandfather);
cur->_color = BLACK;
grandfather->_color = RED;
}
break;
}
}
}
_root->_color = BLACK;
return make_pair(iterator(newnode), true);
}
map的operator[]
operator[]就不是在红黑树那层套了,因为只有kv才有方括号,即set没有方括号。红黑树又没有办法确定是k模型,还是kv模型。即map自己套就行了。
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V())); // V():int就是0、指针就是nullptr、自定义就是默认构造的值
// 有即返回有的
// 没有即返回刚插入的
return ret.first->second;
//ret->first == pair<iterator, bool>里面第一个元素,迭代器
}
本质上map与set可以说是什么都没有写,都是下层的封装。map与set空有其表,靠底层的红黑树。