目录
一、红黑树的改造
节点结构的定义
迭代器类的实现
红黑树中提供迭代器
红黑树的主要代码
二、set的实现
三、map的实现
四、测试代码
map与set的底层都是红黑树,所以本篇文章就分享如何用同一颗红黑树封装出map与set
所以大家可以先去看一下我的讲解红黑树的博客:实现红黑树-CSDN博客
一、红黑树的改造
节点结构的定义
//节点结构定义
template<class T>
struct RBTreeNode
{
//三叉链
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data; //由于要用红黑树封装出map与set, 因此不知道_data是什么类型,有可能是单纯的值,也有可能是pair键值对!
//颜色变量
Color _col;
//节点的构造函数
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
迭代器类的实现
与模拟实现list一样,由于迭代器的统一操作是it++, 而在链表/树中,直接++是无法到下一个节点的,因此需要把节点指针封装成一个类,从而提供迭代器
//map/set的迭代器也是对节点指针封装成的类
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
typedef RBTreeNode<T> Node;
Node* _node;
typedef __TreeIterator<T, Ref, Ptr> self;
//构造函数
__TreeIterator(Node* node)
:_node(node)
{}
//*it
Ref operator*()
{
return _node->_data;
}
//it->
Ptr operator->()
{
return &_node->_data;
}
//求二叉树中序遍历过程中的下一个节点(左子树,根,右子树)
//1.当前节点的右子树存在,下一个节点就是右子树的最左节点
//2.当前节点的右子树不存在,说明以当前节点为根的树已经访问完毕,向上找到孩子是父亲左的那个祖先
self& operator++()
{
if (_node->_right)
{
Node* cur = _node->_right;
while (cur->_left)
{
cur = cur->_left;
}
_node = cur;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right) //也有可能找到了根,此时也就结束了!
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
//求二叉树中序遍历过程中的上一个节点(左子树,根,右子树)
//1.it的左子树存在,找左子树的最右节点
//2.it的左子树不存在, 向上找孩子是父亲右的那个祖先
self& operator--()
{
if(_node->_left)
{
Node* cur = _node->_left;
while (cur)
{
cur = cur->_right;
}
_node = cur;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
//迭代器的比较本质是节点指针的比较
bool operator==(const self& s)
{
return _node == s._node;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
};
上述迭代器的实现很多与list模拟实现是一样的: list使用与模拟实现-CSDN博客
关键的地方在于迭代器++与迭代器--如何实现,由于map/set的遍历是中序遍历,因此迭代器++的本质就是要找二叉树中序遍历过程中当前节点的下一个节点, 而中序遍历的顺序是: 左子树 根 右子树, 因此如果当前树的右子树存在,那么下一个节点就是右子树的最左节点;
如果右子树不存在,说明以当前节点为根的树已经访问完了,如果当前节点的父亲依旧是右孩子,说明以当前节点父亲为根的树也访问完了,依次类推~因此我们需要向上找孩子是父亲左的那个祖先, 才是下一个要访问的节点
找中序遍历的上一个节点也是同样的道理, 此处就不赘述了~
红黑树中提供迭代器
template<class K, class T, class KeyOfT>
class RBTree
{
public:
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T, T&, T*> iterator; //普通迭代器的定义
typedef __TreeIterator<T, const T&, const T*> const_iterator; //const迭代器的定义
public:
//begin()返回中序遍历的起始节点指针构造的迭代器
//起始位置是左子树的最左节点
iterator begin()
{
Node* cur = _root; //_root可能为空,因此下面while加了cur不为空的判断
while (cur && cur->_left)
{
cur = cur->_left;
}
return iterator(cur);
}
const_iterator begin() const
{
Node* cur = _root; //_root可能为空,因此下面while加了cur不为空的判断
while (cur && cur->_left)
{
cur = cur->_left;
}
return const_iterator(cur);
}
//end()返回空指针
iterator end()
{
return iterator(nullptr);
}
const_iterator end() const
{
return const_iterator(nullptr);
}
};
红黑树的主要代码
模板参数
1.需要三个模板参数,第二个模板参数就是 T, 实现set时,T就是key, 实现map时,T就是pair<key, value>;
2.插入节点时需要比较,如果是set, 就直接比较key, 如果是map, 比较的是pair中的key, 因此比较规则是不一样的,所以第三个模板参数要传仿函数, 在比大小之前先获取key
3.在find函数中,参数直接要用到key, 而pair中的key只能取到类型,因此第一个模板参数还需要把key传进来
除了仿函数的使用与insert的返回值,代码中的其他地方与 实现AVL树-CSDN博客 基本一样的
//必须要有第一个参数,因为find函数要根据key类型的参数去查找,而我们无法直接从T中拿到key类型
template<class K, class T, class KeyOfT> //第三个参数是仿函数,方便我们根据传递的类确定比较规则
class RBTree
{
public:
typedef RBTreeNode<T> Node;
typedef __TreeIterator<T, T&, T*> iterator; //迭代器的定义
typedef __TreeIterator<T, const T&, const T*> const_iterator; //迭代器的定义
public:
//迭代器
//begin()返回中序遍历的起始节点指针构造的迭代器
iterator begin()
{
Node* cur = _root; //_root可能为空,因此下面while加了cur不为空的判断
while (cur && cur->_left)
{
cur = cur->_left;
}
return iterator(cur);
}
const_iterator begin() const
{
Node* cur = _root; //_root可能为空,因此下面while加了cur不为空的判断
while (cur && cur->_left)
{
cur = cur->_left;
}
return const_iterator(cur);
}
//end()返回空指针
iterator end()
{
return iterator(nullptr);
}
const_iterator end() const
{
return const_iterator(nullptr);
}
//pair<iterator, bool> Insert(const T& data) err, iterator无法转化为set中的const_iterator
//set中的iterator本质是const_iterator, 本质是__TreeIterator<T, const T&, const T*>类型
//而此处返回的iterator,类型是__TreeIterator<T, T&, T*>
//这两种类型是完全不同的,因此会报错,而当我们使用Node*就可以解决问题了,因为pair类的构造函数/拷贝构造函数设计的很好~
pair<Node*, bool> Insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(_root, true);
}
Node* parent = nullptr;
Node* cur = _root;
KeyOfT kot;
while (cur)
{
//库中pair的比较规则:
//first小就小,first一样则second小就小
//而我们期望比较规则: 只按照first比较!!!
if (kot(cur->_data) < kot(data)) //如果data是pair类型,而库中pair类型比较规则不符合我们的期望, 因此要传仿函数指定比较规则
{
parent = cur;
cur = cur->_right;
}
else if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else
{
return make_pair(cur, false);
}
}
cur = new Node(data);
Node* newNode = cur;
if (kot(parent->_data) < kot(data))
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
while (parent && parent->_col == RED) //父亲不存在或者父亲为黑色就不需要继续调整了!
{
Node* grandfather = parent->_parent;
if (grandfather->_left == parent) //父亲是爷爷的左孩子
{
Node* uncle = grandfather->_right;
//1.插入节点的叔叔存在且为红色 --- 父亲和叔叔变黑,爷爷变红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//调整完之后,更新cur, 判断是否还需要调整
cur = grandfather;
parent = cur->_parent;
}
//2.uncle不存在 / uncle为黑色 --- 旋转 + 变色
else
{
if (cur == parent->_left) //单纯左边高
{
RotateR(grandfather); //右单旋
//变色
parent->_col = BLACK;
grandfather->_col = RED;
}
else //cur是parent的右边
{
RotateLR(grandfather); //左右双旋
//变色
cur->_col = BLACK;
grandfather->_col = RED;
}
break; //旋转+变色完之后, 该子树的根变成了黑色,无需继续调整
}
}
else //父亲是爷爷的右孩子
{
Node* uncle = grandfather->_left;
//1.插入节点的叔叔存在且为红色 --- 父亲和叔叔变黑,爷爷变红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//调整完之后,更新cur, 判断是否还需要调整
cur = grandfather;
parent = cur->_parent;
}
//2.uncle不存在 / uncle为黑色 --- 旋转 + 变色
else
{
if (cur == parent->_left) //cur是parent的左边,进行右单旋
{
RotateRL(grandfather); //右左单旋
//变色
cur->_col = BLACK;
grandfather->_col = RED;
}
else //cur是parent的右边
{
RotateL(grandfather); //左单旋
//变色
grandfather->_col = RED;
parent->_col = BLACK;
}
break; //旋转+变色完之后, 该子树的根变成了黑色,无需继续调整
}
}
}
_root->_col = BLACK;
return make_pair(cur, true);
}
iterator find(const K& key)
{
Node* cur = _root;
KeyOfT kot;
if (key > kot(_root->_data))
{
cur = cur->_right;
}
else if (key < kot(_root->_data))
{
cur = cur->_left;
}
else
{
return iterator(cur);
}
return iterator(end());
}
//左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
subR->_left = parent;
Node* parentParent = parent->_parent;
parent->_parent = subR;
if (subRL)
subRL->_parent = parent;
if (parentParent == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
}
//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* parentParent = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parentParent == nullptr)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
}
//左右双旋
void RotateLR(Node* parent)
{
RotateL(parent->_left);
RotateR(parent);
}
//右左双旋
void RotateRL(Node* parent)
{
RotateR(parent->_right);
RotateL(parent);
}
//判断是否是红黑树
bool IsRBTree()
{
if (_root == nullptr) return true;
if (_root->_col == RED)
{
cout << "根节点为红色" << endl;
return false;
}
//以最左路径的黑节点的个数作为参考值
Node* cur = _root;
int BlackCount = 0;
while (cur)
{
if (cur->_col == BLACK)
BlackCount++;
cur = cur->_left;
}
//调用子函数判断红黑树
int count = 0;
return _IsRBTree(_root, count, BlackCount);
}
private:
bool _IsRBTree(Node* root, int count, int BlackCount)
{
if (root == nullptr)
{
if (count != BlackCount)
{
cout << "黑色节点的个数不相等" << endl;
return false;
}
return true;
}
//判断节点的孩子节点颜色不好判断, 转化成判断父亲节点颜色
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "存在连续的红节点" << endl;
return false;
}
if (root->_col == BLACK)
count++;
//递归子问题
return _IsRBTree(root->_left, count, BlackCount)
&& _IsRBTree(root->_right, count, BlackCount);
}
private:
Node* _root = nullptr;
};
二、set的实现
由于set中的值是不能被修改的,因此无论是set提供的普通迭代器还是const迭代器,都不能去修改元素的值,因此两类迭代器的底层都是红黑树的const迭代器
namespace dck
{
template <class K>
class set
{
public:
struct SetKeyOfT
{
const K& operator()(const K& key) //()运算符重载
{
return key; //set传仿函数只是为了配合map!!!
}
};
//非const迭代器和const迭代器本质都是const迭代器,这样的话无论用哪个set都不能被修改了!!
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
iterator begin() const
{
return _t.begin();
}
iterator end() const
{
return _t.end();
}
pair<iterator, bool> insert(const K& key)
{
return _t.Insert(key);
}
iterator find(const K& key)
{
return _t.find(key);
}
private:
//第二个模板参数决定了set中存储什么
RBTree<K, K, SetKeyOfT> _t;
};
}
三、map的实现
map中的元素是pair<key, value>, 但key不能修改,value可以修改, 另外,map中的[ ]重载是借助insert实现的,这点我们在讲set与map使用-CSDN博客使用时已经提到过
同时为了解决普通对象(非const对象)的pair类型的first不能修改,second可以修改,我们可以把pair类型的first 传成 const K
namespace dck
{
template <class K, class V>
class map
{
public:
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv) //()运算符重载
{
return kv.first; //map传仿函数就是为了获取kv中的key, 从而比较能够按照key比较
}
};
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, 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<iterator, bool> ret = insert(make_pair(Key, V()));
return ret.first->second;
}
pair<iterator, bool> insert(const pair<const K, V>& kv)
{
return _t.Insert(kv);
}
iterator find(const K& key)
{
return _t.find(key);
}
private:
//第二个模板参数决定了map中存储什么
RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
}
四、测试代码
#include "MySet.h"
#include "MyMap.h"
#include <set>
void test_MySet()
{
dck::set<int> s1;
s1.insert(6);
s1.insert(2);
s1.insert(3);
s1.insert(4);
dck::set<int>::iterator it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
for (auto& e : s1)
{
cout << e << " ";
}
cout << endl;
}
void test_MyMap1()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
dck::map<string, int> countMap;
dck::map<string, int>::iterator it = countMap.begin();
for (auto& e : arr)
{
countMap[e]++;
}
for (auto& e : countMap)
{
//e.first = "haha"; //不能修改
//e.second = 2; //可以修改
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
void test_MyMap2()
{
dck::map<string, string> countMap;
countMap.insert(make_pair("left", "左边"));
countMap.insert(make_pair("right", "右边"));
countMap.insert(make_pair("insert", "插入"));
dck::map<string, string>::iterator it = countMap.find("left");
if (it != countMap.end())
it->second = "剩余";
for (auto& e : countMap)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
int main()
{
test_MySet();
//test_MyMap1();
//test_MyMap2();
return 0;
}