目录
前言
一,定义
二,完善set&map比较
三,迭代器实现
operator++
operator--
operator[]
四,发现问题
解决
修改一: set封装层面
修改二:正向迭代器修改
下期预告: 哈希!!
结语
前言
我们在上篇文章红黑树——插入底层实现【C++】面试重灾区!!-CSDN博客
在上篇文章我们实现了红黑树的插入实现,同时也知道map与set底层一般是红黑树实现,那么本篇问文章我们来尝试封装map&set。
注意:建议完整学完本节,代码是逐步完善的。
首先我们从STL源码中寻找线索
还是比较明显的,红黑树的底层更加适合Map,但STL想让set复用map的红黑树,所以让set做出了一些 “ 妥协 ”:
参数传递方向:
一,定义
根据STL源代码的提示,我们能初步形成框架
namespace my_map
{
template <class K, class V>
class map
{
RB_Tree<K, pair<const K, V>> _t;
public:
bool insert(const pair<const K, V>& p)
{
return _t.insert(p);
}
};
}
namespace my_set
{
template <class K>
class set
{
RB_Tree<K, K> _t;
public:
bool insert(const K& p)
{
return _t.insert(p);
}
};
}
同时我们要对红黑树的定义进行修改:
template <class T>
struct RBT_Data
{
T _data;
RBT_Data<T>* left = nullptr;
RBT_Data<T>* right = nullptr;
RBT_Data<T>* parent = nullptr;
color _col; // 颜色
RBT_Data(const T& p)
:_data(p)
,_col(RED) //与其修改黑色路径数量,不如违反红子孩子都为黑的原则来的轻松。
{}
};
template <class K, class V>
class RB_Tree
{
typedef RBT_Data<V> RBT_Data;
RBT_Data* root = nullptr;
....
二,完善set&map比较
从结果上来看,set&map 竟然运行成功。这是为什么呢?? 没有了固定的 cur->_kv.first map应该是无法访问pair里面的数据,那又是如何进行比较大小,形成排序的呢??
答案在pair里面
pair类里面重载了比较大小的符号,但都是first,second之间进行比较,我们的排序需要依照Key值进行判断。那如何解决??
通过构建仿函数
set与map形成各自的仿函数,这样set就能适配map。
三,迭代器实现
基础框架,基本符号" * ", " -> " , " != "的重载。
这里以set封装为例
// 在set封装上的处理
namespace my_set
{
template <class K>
class set
{
typedef RB_Tree_Iterator<K, K&, K*> iterator;
typedef RB_Tree_Iterator<K, const K&, const K*> const_iterator;
struct KeyofT
{
const K& operator()(const K& data)
{
return data;
}
};
RB_Tree<K, K, KeyofT> _t;
public:
bool insert(const K& p)
{
return _t.insert(p);
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
};
}
// 迭代器类处理
template <class T, class Ref, class Ptr>
class RB_Tree_Iterator
{
typedef RBT_Data<T> RBT_Data;
typedef RB_Tree_Iterator<T, Ref, Ptr> Seft;
RBT_Data* it_root ;
public:
RB_Tree_Iterator( RBT_Data* _node = nullptr)
:it_root(_node)
{}
Ref operator*()
{
return it_root->_data;
}
Ptr operator->()
{
return &it_root->_data;
}
};
// 在RB_Tree上的处理
template <class K, class V, class KeyofT>
class RB_Tree
{
typedef RB_Tree_Iterator<V, V&, V*> iterator;
typedef RB_Tree_Iterator<V, const V&, const V*> const_iterator;
.......
iterator begin()
{
RBT_Data* node = root;
while (node && node->left)
{
node = node->left;
}
return iterator(node);
}
// end跟以往的不同,end表示最后一个数据的下一个数据,也就是nullptr,实际距离是根的父节点。
iterator end()
{
return iterator(nullptr);
}
const const_iterator begin() const
{
RBT_Data* node = root;
while (node && node->left)
{
node = node->left;
}
return const_iterator(node);
}
const const_iterator end() const
{
return const_iterator(nullptr);
}
....
但我们还差一个极其重要的字符 “ ++ ”的实现,由于这个比较特殊我们分开单独讲。
operator++
思想:中序遍历来访问各个数据。比较难的是如何利用红黑树的三叉链,来实现中序遍历。
Seft& operator++()
{
if (it_root && it_root->right)
{
// 假设右孩子存在
RBT_Data* subleft = it_root->right;
while (subleft->left)
{
subleft = subleft->left;
}
it_root = subleft;
}
else //如果没有右孩子,就向上回溯
{
RBT_Data* cur = it_root;
RBT_Data* par = it_root->parent;
while (par && par->right == cur)
{
cur = par;
par = par->parent;
}
it_root = par;
}
return *this;
}
operator--
" -- "与 “ ++ ”两符号重载,本质上没多大改变,就是从原来的 左 -> 根 -> 右 => 右 -> 根 -> 左。
Seft& operator--()
{
if (it_root && it_root->right)
{
// 假设左侧存在
RBT_Data* subright = it_root->left;
while (subright->right)
{
subright = subright->right;
}
it_root = subright;
}
else //如果左侧没有
{
RBT_Data* cur = it_root;
RBT_Data* par = it_root->parent;
while (par && par->left == cur)
{
cur = par;
par = par->parent;
}
it_root = par;
}
return *this;
}
operator[]
这是STL对operator[] 的使用, 这需要我们对insert()进行返回值的修改,我们需要返回一个pair类型,里面放着pair<iterator, bool>,方便我们修改value的值。
这里我给一个案例进行修改:insert返回value的引用
后面的返回值处理成pair返回类似。
然后就是map与set的封装层,我们要如何使用呢??代码如下:
// set
K& operator[](const K& key)
{
pair<iterator, bool> ret = _t.insert(key);
if (ret.second)
return ret.first.it_root->_data;
cout << "插入失败" << endl;
}
// map
V& operator[](const K& key)
{
pair<iterator, bool> ret = _t.insert(make_pair(key, V()));
if (ret.second)
return ret.first->second;
cout << "插入失败" << endl;
}
四,发现问题
我们运行这段测试代码,我们会发现,set里面的数据被修改,但这是不被允许的。
my_set::set<int> st;
st[1];
st[2];
st[3];
st[4];
for (auto& e : st)
{
cout << e << " ";
}
for (auto& e : st)
{
e = 1;
cout << e << " ";
}
原因分析:下图是普通对象set获取迭代器参数的传递
解决
对于set的普通对象,如果是上面的代码,我们无法在适配map的前提下,保证set的value不可修改。解决思路:我们让set的普通迭代器类型底层是const迭代器。但其中的细节还是非常巧妙,我们通过下图讲解特殊之初吧。
修改一: set封装层面
修改二:正向迭代器修改
好啦,map与set封装精华就到这里了。文章来源:https://www.toymoban.com/news/detail-740342.html
map_set.h
namespace my_set
{
template <class K>
class set
{
struct KeyofT
{
const K& operator()(const K& data)
{
return data;
}
};
typedef typename RB_Tree<K, K, KeyofT>::const_iterator iterator; //直接用,const对象的迭代器充当普通迭代器
typedef typename RB_Tree<K, K, KeyofT>::const_iterator const_iterator;
RB_Tree<K, K, KeyofT> _t;
public:
pair<iterator, bool> insert(const K& p)
{
return _t.insert(p);
}
K& operator[](const K& key)
{
pair<iterator, bool> ret = _t.insert(key);
if (ret.second)
return ret.first.it_root->_data;
cout << "插入失败" << endl;
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
const_iterator begin() const
{
return _t.begin();
}
const_iterator end() const
{
return _t.end();
}
};
}
namespace my_map
{
template <class K, class V>
class map
{
typedef RB_Tree_Iterator<pair<const K, V>, pair<const K, V>&, pair<const K, V>*> iterator;
typedef RB_Tree_Iterator<pair<const K, V>, const pair<const K, V>&, const pair<const K, V>*> const_iterator;
struct KeyofT
{
const K& operator()(const pair<const K, V>& data)
{
return data.first;
}
};
RB_Tree<K, pair<const K, V>, KeyofT> _t;
public:
pair<iterator, bool> insert(const pair<const K, V>& p)
{
return _t.insert(p);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _t.insert(make_pair(key, V()));
if (ret.second)
return ret.first->second;
cout << "插入失败" << endl;
}
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
const_iterator begin() const
{
return _t.begin();
}
const_iterator end() const
{
return _t.end();
}
};
}
RB_Tree.h
#include <iostream>
#include<string>
#include <assert.h>
#include <utility>
using namespace std;
enum color
{
RED,
BLACK
};
template <class T>
struct RBT_Data
{
T _data;
RBT_Data<T>* left = nullptr;
RBT_Data<T>* right = nullptr;
RBT_Data<T>* parent = nullptr;
color _col; // 颜色
RBT_Data(const T& p)
:_data(p)
, _col(RED) //与其修改黑色路径数量,不如违反红子孩子都为黑的原则来的轻松。
{}
};
template <class T, class Ref, class Ptr>
class RB_Tree_Iterator
{
typedef RBT_Data<T> RBT_Data;
typedef RB_Tree_Iterator<T, Ref, Ptr> Self;
typedef RB_Tree_Iterator<T, T&, T*> iterator; // 跟上面的不同,这个iterator只以T为准,这样在set const的对象时,不会出现
// 两个const 的情况
public:
RBT_Data* it_root ;
RB_Tree_Iterator(RBT_Data* _node = nullptr)
:it_root(_node)
{}
RB_Tree_Iterator(const iterator& it)
:it_root(it.it_root)
{}
Ref operator*()
{
return it_root->_data;
}
Ptr operator->()
{
return &it_root->_data;
}
// node* 地址也算不同罢了
bool operator!= (const Self& node)
{
return node.it_root != it_root;
}
Self& operator++()
{
if (it_root && it_root->right)
{
// 假设右侧存在
RBT_Data* subleft = it_root->right;
while (subleft->left)
{
subleft = subleft->left;
}
it_root = subleft;
}
else //如果右侧没有
{
RBT_Data* cur = it_root;
RBT_Data* par = it_root->parent;
while (par && par->right == cur)
{
cur = par;
par = par->parent;
}
it_root = par;
}
return *this;
}
Self& operator--()
{
if (it_root && it_root->right)
{
// 假设左侧存在
RBT_Data* subright = it_root->left;
while (subright->right)
{
subright = subright->right;
}
it_root = subright;
}
else //如果左侧没有
{
RBT_Data* cur = it_root;
RBT_Data* par = it_root->parent;
while (par && par->left == cur)
{
cur = par;
par = par->parent;
}
it_root = par;
}
return *this;
}
};
template <class K, class V, class KeyofT>
class RB_Tree
{
public:
typedef RBT_Data<V> RBT_Data;
typedef RB_Tree_Iterator<V, V&, V*> iterator;
typedef RB_Tree_Iterator<V, const V&, const V*> const_iterator;
// 这个也是按照后序遍历方式进行析构
void Destroy(RBT_Data* node)
{
if (node == nullptr)
return;
Destroy(node->left);
Destroy(node->right);
delete(node);
}
~RB_Tree()
{
if (root)
{
Destroy(root);
root = nullptr;
}
}
iterator begin()
{
RBT_Data* node = root;
while (node && node->left)
{
node = node->left;
}
return iterator(node);
}
// end跟以往的不同,end表示最后一个数据的下一个数据,也就是nullptr
iterator end()
{
return iterator(nullptr);
}
const_iterator begin() const
{
RBT_Data* node = root;
while (node && node->left)
{
node = node->left;
}
return const_iterator(node);
}
const_iterator end() const
{
return const_iterator(nullptr);
}
void Print()
{
_Print(root);
}
bool IsBalance()
{
if (root && root->_col == RED)
{
return false;
}
int BlackNum = 0;
// 还差一个最长路径
int standard = 0;
RBT_Data* cur = root;
while (cur)
{
if (cur->_col == BLACK)
standard++;
cur = cur->left;
}
return _IsBalance(root->left, BlackNum, standard) && _IsBalance(root->right, BlackNum, standard);
}
pair<iterator, bool> insert(const V& p)
{
RBT_Data* new_a_d = new RBT_Data(p);
if (!root)
{
root = new_a_d;
root->_col = BLACK;
return make_pair(iterator(new_a_d), true);
}
else
{
RBT_Data* cur = root;
RBT_Data* parent = nullptr;
KeyofT Kt;
while (cur)
{
if (Kt(p) < Kt(cur->_data))
{
parent = cur;
cur = cur->left;
}
else if (Kt(p) > Kt(cur->_data))
{
parent = cur;
cur = cur->right;
}
else
{
delete(new_a_d); // 插入失败,删除新建结点
return make_pair(iterator(root), false);
}
}
if (p < parent->_data)
{
parent->left = new_a_d;
}
else
{
parent->right = new_a_d;
}
new_a_d->parent = parent;
// 调整颜色
cur = new_a_d;
RBT_Data* par = cur->parent;
if (cur == root)
{
cur->_col = BLACK;
}
while (par && par->_col == RED)
{
RBT_Data* gf = par->parent;
RBT_Data* uncle = nullptr;
if (gf && par == gf->right)
{
uncle = gf->left;
}
else if (gf && par == gf->left)
{
uncle = gf->right;
}
else
{
assert(false);
}
if (uncle && uncle->_col == RED)// 有u且为红
{
gf->_col = RED;
uncle->_col = BLACK;
par->_col = BLACK;
cur = gf; // 切换为祖先,进入循环向上
par = cur->parent;
}
else if (!uncle ||
(uncle && uncle->_col == BLACK))
{ // 情况2 + 3,判断,是否是折线还是直线
if (gf->left == par && par->left == cur)
{ // 右单选
RotateR(gf);
}
else if (gf->right == par && par->right == cur)
{ // 左单旋
RotateL(gf);
}
else if (gf->left == par && par->right == cur)
{ // 需要左双旋
RotateLR(gf);
}
else if (gf->right == par && par->left == cur)
{ // 需要右双旋
RotateRL(gf);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
if (root->_col == RED)
{
root->_col = BLACK;
}
return make_pair(iterator(new_a_d), true);
}
}
private:
RBT_Data* root = nullptr;
void RotateL(RBT_Data* parent)
{
assert(parent->right);
RBT_Data* par = parent;
RBT_Data* par_R = par->right;
RBT_Data* par_RL = par->right->left;
RBT_Data* ppnode = par->parent;
par->right = par_RL;
if (par_RL)
par_RL->parent = par;
par_R->left = par;
par->parent = par_R;
par_R->parent = ppnode;
if (!ppnode)
{
root = par_R;
}
else if (ppnode->left == par)
{
ppnode->left = par_R;
}
else
{
ppnode->right = par_R;
}
par->_col = RED;
par_R->_col = BLACK;
}
void RotateR(RBT_Data* parent)
{
assert(parent->left);
RBT_Data* par = parent;
RBT_Data* par_L = par->left;
RBT_Data* par_LR = par->left->right;
RBT_Data* ppnode = par->parent;
par->left = par_LR;
if (par_LR)
par_LR->parent = par;
par_L->right = par;
par->parent = par_L;
par_L->parent = ppnode;
if (!ppnode)
{
root = par_L;
}
else
{
if (ppnode->left == par)
{
ppnode->left = par_L;
}
else
{
ppnode->right = par_L;
}
}
par->_col = RED;
par_L->_col = BLACK;
}
void RotateLR(RBT_Data* parent)
{
assert(parent->left);
RBT_Data* par = parent;
RBT_Data* par_L = par->left;
RBT_Data* par_LR = par->left->right;
RotateL(par_L);
RotateR(par);
par_LR->_col = BLACK;
par_L->_col = BLACK;
}
void RotateRL(RBT_Data* parent)
{
assert(parent->right);
RBT_Data* par = parent;
RBT_Data* par_R = par->right;
RBT_Data* par_RL = par->right->left;
RotateR(par_R);
RotateL(par);
par_RL->_col = BLACK;
par_R->_col = BLACK;
}
void _Print(const RBT_Data* root)
{
if (root == nullptr)
{
// cout << "[]";
return;
}
_Print(root->left);
cout << KeyofT(root->_data) << " ";
_Print(root->right);
}
bool _IsBalance(const RBT_Data* cur, int BlackNum, int standard)
{
if (cur == nullptr)
{
return true;
}
if (cur->_col == BLACK)
BlackNum++;
if (cur->_col == RED && cur->_col == cur->parent->_col)
{
return false;
}
return _IsBalance(cur->left, BlackNum, standard) && _IsBalance(cur->right, BlackNum, standard);
}
};
下期预告: 哈希!!
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。文章来源地址https://www.toymoban.com/news/detail-740342.html
到了这里,关于用红黑树封装map&set【C++】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!