C++:map和set的封装

✿༺小陈在拼命༻✿ 2024-08-23 17:35:02 阅读 53

      关于红黑树的模拟实现,大家不清楚的先去看看博主的博客再来看这篇文章,因为set和map的封装底层都是利用用的红黑树。所以这里不会过多介绍红黑树的相关内容,而更多的是去为了契合STL中的红黑树去进行改造,让封装的set和map能够去复用我们的这份代码

DS进阶:AVL树和红黑树-CSDN博客

      在模拟实现之前,我们肯定要尝试去看看源码是如何实现的!我们会发现其实map和set的底层都是用的红黑树去封装的

      但是你可能会有这样的疑惑,map是kv模型,set是k模型,那难道stl底层封装了两颗红黑树么??其实并不是的,创建stl的大佬们为了增加代码的复用性,想方设法地想让map和set同时复用一颗红黑树。而解决方法就是通过控制模版参数来区分map和set。

     既然底层是套的红黑树的壳子,我们就要来研究库里面的红黑树究竟通过了什么方法来让map和set都能够复用这份代码。

一、STL中的红黑树

1.1 利用模版参数控制和区分map和set

我们先来看看stl中的红黑树的模版参数,然后进行分析

   接下来我们来看看第三个模版参数的作用究竟是什么

总结:

第1个模版参数是为了帮助我们拿到Key的类型,因为find、erase的接口都是Key类型比较方便

第2个模版参数决定了红黑树节点中存的是key还是pair,以此来区分map和set

第3个模版参数是通过仿函数决定了是拿什么去进行比较,对set来说就是拿key,对pair来说就是拿他的first。

第4个模版参数是具体的比较逻辑,比如说我们传的是指针,但是我们并不想通过指针比而是通过指针解引用的类型比,就可以通过传这个仿函数去控制其比较的行为。

第5个是stl实现的一个堆内存管理器,是为了提高从堆区申请内存的效率,基本上所有的stl容器都会涉及到这个,所以目前暂时不需要太在意!

1.2 stl中的红黑树结构

在该图中,设置了一个哨兵节点,哨兵节点的左指向最小节点5,最大节点的右指向哨兵节点header, 为什么要这样设计呢??

      STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,

可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位

置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?

能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行--操作,必须要能找最

后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:

 

       但是这样虽然方便我们找到第一个节点和最后一个节点,但是每一次都要最最左端和最右端的节点进行和头节点之间的联系,其实比较麻烦,所以下面我们直接改造成不带哨兵节点的红黑树。去模拟实现迭代器。

1.3 改造并模拟实现红黑树的迭代器

但是最最关键的逻辑就是,实现++和--这样迭代器才能跑的起来,下面我们来进行分析

迭代器的封装

<code>template<class T,class Ref,class Ptr>

struct _RBTreeIterator

{

typedef RBTreeNode<T> Node;

typedef _RBTreeIterator<T, Ref, Ptr> Self; //返回一个自身的迭代器

typedef _RBTreeIterator<T, T&, T*> iterator; //用来转化成const迭代器

Node* _node;

_RBTreeIterator(Node* node) //利用节点去构造迭代器

:_node(node)

{}

// 1、typedef __RBTreeIterator<T, T&, T*> itertaor; 拷贝构造

// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;

// 支持普通迭代器构造const迭代器的构造函数

_RBTreeIterator(const iterator& it) //隐私类型转化

:_node(it._node)

{}

Ref operator*()

{

return _node->_data; //解引用拿到对应的东西 map拿到pair set拿到key

}

Ptr operator->() //返回对应的指针类型

{

return &operator*();

}

bool operator!=(const Self& s)

{

return _node != s._node;//判断两个迭代器是否相同

}

bool operator==(const Self& s)

{

return _node == s._node;//判断两个迭代器是否相同

}

Self& operator++() //实现迭代器的++

{

if (_node->_right)

{

//如有右不为空,那么就去找到 右子树的最左路节点

Node* subright = _node->_right;

while (subright->_left) subright = subright->_left; //找到最左路节点

_node = subright;

}

else

{

//右为空,沿着到根的路径,找孩子是父亲左的那个祖先

Node* cur = _node;

Node* parent = cur->_parent;

while (parent && parent->_right == cur)

{

cur = parent;

parent = parent->_parent;

}

_node = parent;

}

return *this;

}

Self operator++(int) //实现迭代器的后置++

{

Self temp = *this;

++*this;

return temp;

}

Self& operator--() //实现迭代器的-- 右 根 左

{

if (_node->_left)

{

//如有左不为空,那么就去找到 左子树的最右路节点

Node* subright = _node->_left;

while (subright->_right) subright = subright->_right; //找到最左路节点

_node = subright;

}

else

{

//左为空,沿着到根的路径,找孩子是父亲右的那个祖先

Node* cur = _node;

Node* parent = cur->_parent;

while (parent && parent->_left == cur)

{

cur = parent;

parent = parent->_parent;

}

_node = parent;

}

return *this;

}

Self operator--(int) //实现迭代器的后置--

{

Self temp = *this;

--*this;

return temp;

}

};

1.4 红黑树实现的全部代码 

enum Colour

{

RED,

BLACK,

};

template<class T> //T表示传的是K还是pair

struct RBTreeNode

{

RBTreeNode<T>* _left;

RBTreeNode<T>* _right;

RBTreeNode<T>* _parent;

T _data;

Colour _col;

RBTreeNode(const T& data)

: _left(nullptr)

, _right(nullptr)

, _parent(nullptr)

, _data(data)

, _col(RED)

{}

};

template<class T,class Ref,class Ptr>

struct _RBTreeIterator

{

typedef RBTreeNode<T> Node;

typedef _RBTreeIterator<T, Ref, Ptr> Self; //返回一个自身的迭代器

typedef _RBTreeIterator<T, T&, T*> iterator; //用来转化成const迭代器

Node* _node;

_RBTreeIterator(Node* node) //利用节点去构造迭代器

:_node(node)

{}

// 1、typedef __RBTreeIterator<T, T&, T*> itertaor; 拷贝构造

// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;

// 支持普通迭代器构造const迭代器的构造函数 为的是隐式类型转化

_RBTreeIterator(const iterator& it) //隐私类型转化 为了set去服务的 因为set的普通迭代器也是const迭代器

:_node(it._node)

{}

Ref operator*()

{

return _node->_data; //解引用拿到对应的东西 map拿到pair set拿到key

}

Ptr operator->() //返回对应的指针类型

{

return &operator*();

}

bool operator!=(const Self& s)

{

return _node != s._node;//判断两个迭代器是否相同

}

bool operator==(const Self& s)

{

return _node == s._node;//判断两个迭代器是否相同

}

Self& operator++() //实现迭代器的++

{

if (_node->_right)

{

//如有右不为空,那么就去找到 右子树的最左路节点

Node* subright = _node->_right;

while (subright->_left) subright = subright->_left; //找到最左路节点

_node = subright;

}

else

{

//右为空,沿着到根的路径,找孩子是父亲左的那个祖先

Node* cur = _node;

Node* parent = cur->_parent;

while (parent && parent->_right == cur)

{

cur = parent;

parent = parent->_parent;

}

_node = parent;

}

return *this;

}

Self operator++(int) //实现迭代器的后置++

{

Self temp = *this;

++*this;

return temp;

}

Self& operator--() //实现迭代器的-- 右 根 左

{

if (_node->_left)

{

//如有左不为空,那么就去找到 左子树的最右路节点

Node* subright = _node->_left;

while (subright->_right) subright = subright->_right; //找到最左路节点

_node = subright;

}

else

{

//左为空,沿着到根的路径,找孩子是父亲右的那个祖先

Node* cur = _node;

Node* parent = cur->_parent;

while (parent && parent->_left == cur)

{

cur = parent;

parent = parent->_parent;

}

_node = parent;

}

return *this;

}

Self operator--(int) //实现迭代器的后置--

{

Self temp = *this;

--*this;

return temp;

}

};

//K是为了单独拿到key的类型 因为find erase 的接口都是key 而第二个模版参数T决定是这边传的是pair还是key

template<class K, class T,class KeyOfT> //KeyofT 取出来比较的是k 还是pair中的k

class RBTree

{

typedef RBTreeNode<T> Node;

public:

typedef _RBTreeIterator<T, T&, T*> iterator;

typedef _RBTreeIterator<T, const T&, const T*> const_iterator;

iterator begin()

{

Node* cur = _root;

while (cur && cur->_left) cur = cur->_left;

//找到最左路的节点

return iterator(cur);

}

iterator end()

{

return iterator(nullptr);

}

const_iterator begin() const

{

Node* cur = _root;

while (cur && cur->_left) cur = cur->_left;

//找到最左路的节点

return const_iterator(cur);

}

const_iterator end() const

{

return const_iterator(nullptr);

}

~RBTree()

{

_Destroy(_root);

_root = nullptr;

}

iterator Find(const K& key) const

{

Node* cur = _root;

KeyOfT kot;//控制 是在pair中拿key还是直接拿key

while (cur)

{

if (kot(cur->_data) < key) cur = cur->_right; //我比你小,你往右找

else if (kot(cur->_data) > key) cur = cur->_left; //我比你大,你往左找

else return iterator(cur);

}

return iterator(nullptr);//说明找不到

}

//先用搜索树的逻辑插入节点,然后再去更新平衡因子。

pair<iterator,bool> Insert(const T& data)

{

//如果为空树,新节点就是根

if (_root == nullptr)

{

_root = new Node(data);

_root->_col = BLACK;

return make_pair(iterator(_root),true);

}

KeyOfT kot;//控制 是在pair中拿key还是直接拿key

//如果不为空树

Node* parent = nullptr;

Node* cur = _root;

while (cur)

{

if (kot(cur->_data) > kot(data)) //如果我比你大,到左子树去

{

parent = cur;

cur = cur->_left;

}

else if (kot(cur->_data) < kot(data)) //比你小,你去右子树

{

parent = cur;

cur = cur->_right;

}

else return make_pair(iterator(cur), false);//相等

}

//此时肯定是对应地接在parent的后面

cur = new Node(data);

Node* newnode = cur;//记住新加入的节点

if (kot(parent->_data)> kot(data)) parent->_left = cur; //比父亲小连左边

else parent->_right = cur; //比父亲大连右边

//别忘了父亲指针

cur->_parent = parent;

while (parent && parent->_col == RED)

{

Node* grandfather = parent->_parent;

//情况1,如果u为存在且为红

if (grandfather->_left == parent)//如果p是g的左边,u就在右边

{

Node* uncle = grandfather->_right;

//情况1,如果u为存在且为红 p u变黑,g变红 向上调整

if (uncle && uncle->_col == RED)

{

parent->_col = BLACK;

uncle->_col = BLACK;

grandfather->_col = RED;

//继续向上调整

cur = grandfather;

parent = cur->_parent;

}

else //情况2或者情况3, u为黑或者不存在 旋转+变色

{

if (cur == parent->_left) //情况2 右单旋+p变黑 g变红

{

// g

// p u

// c

RotateR(grandfather);

parent->_col = BLACK;

grandfather->_col = RED;

}

else //情况3 右左双旋 c变黑 g变红

{

// g

// p u

// c

RotateL(parent);

RotateR(grandfather);

cur->_col = BLACK;

grandfather->_col = RED;

}

break;//情况2和情况3都要跳出循环

}

}

else//if (grandfather->_right == parent)//如果p是g的右边,u就在左边 几乎一样,就是旋转的逻辑不同

{

Node* uncle = grandfather->_left;

//情况1,如果u为存在且为红 p u变黑,g变红 向上调整

if (uncle && uncle->_col == RED)

{

parent->_col = BLACK;

uncle->_col = BLACK;

grandfather->_col = RED;

//继续向上调整

cur = grandfather;

parent = cur->_parent;

}

else//情况2或者情况3, u为黑或者不存在 旋转+变色

{

if (cur == parent->_right) //情况2 左单旋+p变黑 g变红

{

// g

// p u

// c

RotateL(grandfather);

parent->_col = BLACK;

grandfather->_col = RED;

}

else //情况3 左右双旋 c变黑 g变红

{

// g

// p u

// c

RotateR(parent);

RotateL(grandfather);

cur->_col = BLACK;

grandfather->_col = RED;

}

break;//情况2和情况3都要跳出循环

}

}

}

_root->_col = BLACK; //预防情况1出现 parent就是根的情况 此时无论如何_root变成黑,总没错

return make_pair(iterator(newnode), true);

}

void InOrder()

{

_InOrder(_root);

cout << endl;

}

bool IsBalance()

{

if (_root && _root->_col == RED)

{

cout << "根节点颜色是红色" << endl;

return false;

}

int benchmark = 0;//找到一条路径作为基准值 然后看看其他路径是否相等

Node* cur = _root;

while (cur)

{

if (cur->_col == BLACK)

++benchmark;

cur = cur->_left;

}

// 连续红色节点

return _Check(_root, 0, benchmark);

}

int Height()

{

return _Height(_root);

}

private:

void _Destroy(Node* root)

{

if (root == nullptr) return;

//后序遍历销毁

_Destroy(root->_left);

_Destroy(root->_right);

delete root;

}

int _Height(Node* root)

{

if (root == nullptr)

return 0;

int leftH = _Height(root->_left);

int rightH = _Height(root->_right);

return leftH > rightH ? leftH + 1 : rightH + 1;

}

bool _Check(Node* root, int blackNum, int benchmark)

{

if (root == nullptr)

{

if (benchmark != blackNum)

{

cout << "某条路径黑色节点的数量不相等" << endl;

return false;

}

return true;

}

if (root->_col == BLACK)

{

++blackNum;

}

if (root->_col == RED

&& root->_parent

&& root->_parent->_col == RED)

{

cout << "存在连续的红色节点" << endl;

return false;

}

return _Check(root->_left, blackNum, benchmark)

&& _Check(root->_right, blackNum, benchmark);

}

void _InOrder(Node* root)

{

if (root == nullptr)

{

return;

}

_InOrder(root->_left);

cout << root->_kv.first << " ";

_InOrder(root->_right);

}

//旋转代码和AVL树是一样的,只不过不需要搞平衡因子

void RotateL(Node* parent)

{

//旋转前,先记录对应的节点

Node* subR = parent->_right;

Node* subRL = subR->_left;

Node* ppnode = parent->_parent;//子树的前驱节点

//先让b变成30的边

parent->_right = subRL;

if (subRL) subRL->_parent = parent;

//让30变成60的左边

subR->_left = parent;

parent->_parent = subR;

//此时与前驱节点连接起来 如果前驱节点为空,直接改变根

if (ppnode == nullptr)

{

_root = subR;

_root->_parent = nullptr;

}

//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边

else

{

if (ppnode->_left == parent) ppnode->_left = subR;

else ppnode->_right = subR;

//向上连接

subR->_parent = ppnode;

}

}

void RotateR(Node* parent)

{

//旋转前,先记录对应的节点

Node* subL = parent->_left;

Node* subLR = subL->_right;

Node* ppnode = parent->_parent;//子树的前驱节点

//先让b变成60的左边

parent->_left = subLR;

if (subLR) subLR->_parent = parent;

//让60变成30的右边

subL->_right = parent;

parent->_parent = subL;

//此时与前驱节点连接起来 如果前驱节点为空,直接改变根

if (ppnode == nullptr)

{

_root = subL;

_root->_parent = nullptr;

}

//如果前驱节点不为空,此时要根据之前paernt的情况决定插在哪边

else

{

if (ppnode->_left == parent) ppnode->_left = subL;

else ppnode->_right = subL;

//向上连接

subL->_parent = ppnode;

}

}

Node* _root = nullptr;

};

二、set的模拟实现

前面我们已经将架子搭好了,这个时候就可以直接开始用了!!

namespace cyx

{

template<class K>

class set

{

struct SetKeyofT

{

const K& operator()(const K& key) //为了跟map保持一致

{

return key;

}

};

public:

typedef typename RBTree< K,K,SetKeyofT>::const_iterator iterator;//在没有实例化的时候 编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题

typedef typename RBTree< K, K, SetKeyofT>::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();

}

pair<iterator, bool> insert(const K&key)

{

return _t.Insert(key);

}

private:

RBTree<K, K, SetKeyofT> _t;

};

void test_set1()

{

int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };

set<int> s;

for (auto e : a)

{

s.insert(e);

}

set<int>::iterator it = s.begin();

while (it != s.end())

{

cout << *it << " ";

//*it = 1;

++it;

}

cout << endl;

for (auto e : s)

{

cout << e << " ";

}

cout << endl;

}

}

注意:

1、在没有实例化的时候 ,编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题

 2、对于insert返回值的改造,本质上是为了map去服务的,set只是配合而已。

三、map的模拟实现

3.1 insert的改装

在stl中 insert的返回值是pair<iterator,bool> 一开始我不太能理解为什么要这么设计。后来我明白了其实本质上为了后面重载[ ]的实现做铺垫。我们可以通过返回值去拿到iterator,并对对应节点的value进行直接修改!!

<code>//先用搜索树的逻辑插入节点,然后再去更新平衡因子。

pair<iterator,bool> Insert(const T& data)

{

//如果为空树,新节点就是根

if (_root == nullptr)

{

_root = new Node(data);

_root->_col = BLACK;

return make_pair(iterator(_root),true);

}

KeyOfT kot;//控制 是在pair中拿key还是直接拿key

//如果不为空树

Node* parent = nullptr;

Node* cur = _root;

while (cur)

{

if (kot(cur->_data) > kot(data)) //如果我比你大,到左子树去

{

parent = cur;

cur = cur->_left;

}

else if (kot(cur->_data) < kot(data)) //比你小,你去右子树

{

parent = cur;

cur = cur->_right;

}

else return make_pair(iterator(cur), false);//相等

}

//此时肯定是对应地接在parent的后面

cur = new Node(data);

Node* newnode = cur;//记住新加入的节点

if (kot(parent->_data)> kot(data)) parent->_left = cur; //比父亲小连左边

else parent->_right = cur; //比父亲大连右边

//别忘了父亲指针

cur->_parent = parent;

while (parent && parent->_col == RED)

{

Node* grandfather = parent->_parent;

//情况1,如果u为存在且为红

if (grandfather->_left == parent)//如果p是g的左边,u就在右边

{

Node* uncle = grandfather->_right;

//情况1,如果u为存在且为红 p u变黑,g变红 向上调整

if (uncle && uncle->_col == RED)

{

parent->_col = BLACK;

uncle->_col = BLACK;

grandfather->_col = RED;

//继续向上调整

cur = grandfather;

parent = cur->_parent;

}

else //情况2或者情况3, u为黑或者不存在 旋转+变色

{

if (cur == parent->_left) //情况2 右单旋+p变黑 g变红

{

// g

// p u

// c

RotateR(grandfather);

parent->_col = BLACK;

grandfather->_col = RED;

}

else //情况3 右左双旋 c变黑 g变红

{

// g

// p u

// c

RotateL(parent);

RotateR(grandfather);

cur->_col = BLACK;

grandfather->_col = RED;

}

break;//情况2和情况3都要跳出循环

}

}

else//if (grandfather->_right == parent)//如果p是g的右边,u就在左边 几乎一样,就是旋转的逻辑不同

{

Node* uncle = grandfather->_left;

//情况1,如果u为存在且为红 p u变黑,g变红 向上调整

if (uncle && uncle->_col == RED)

{

parent->_col = BLACK;

uncle->_col = BLACK;

grandfather->_col = RED;

//继续向上调整

cur = grandfather;

parent = cur->_parent;

}

else//情况2或者情况3, u为黑或者不存在 旋转+变色

{

if (cur == parent->_right) //情况2 左单旋+p变黑 g变红

{

// g

// p u

// c

RotateL(grandfather);

parent->_col = BLACK;

grandfather->_col = RED;

}

else //情况3 左右双旋 c变黑 g变红

{

// g

// p u

// c

RotateR(parent);

RotateL(grandfather);

cur->_col = BLACK;

grandfather->_col = RED;

}

break;//情况2和情况3都要跳出循环

}

}

}

_root->_col = BLACK; //预防情况1出现 parent就是根的情况 此时无论如何_root变成黑,总没错

return make_pair(iterator(newnode), true);

}

3.2 重载[ ]的实现

V& operator[](const K& key)

{

pair<iterator, bool> ret = _t.Insert(make_pair(key, V())); //默认构造

return ret.first->second;

}

通过insert拿到对应位置的迭代器,然后指向其second 这样就可以直接进行修改了。 

3.3 模拟实现的代码

namespace cyx

{

template<class K, class V>

class map

{

struct MapKeyofT

{

const K& operator()(const pair<const K, V>& kv) //为了跟map保持一致

{

return kv.first;

}

};

public:

//模版类型的内嵌类型 加typename

typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::iterator iterator;//在没有实例化的时候 编译器并不知道这是一个成员还是一个类型 typename可以帮助我们解决这个问题

typedef typename RBTree<K, pair<const K, V>, MapKeyofT>::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 = _t.Insert(make_pair(key, V())); //默认构造

return ret.first->second;

}

pair<iterator, bool> insert(const pair<const K, V>& kv)

{

return _t.Insert(kv);

}

private:

RBTree<K, pair<const K,V>, MapKeyofT> _t;

};

void test_map()

{

map<string, string> dict;

dict.insert(make_pair("sort", "排序"));

dict.insert(make_pair("left", "左边"));

dict.insert(make_pair("right", "右边"));

dict.insert(make_pair("hello", "你好"));

map<string, string>::iterator it = dict.begin();

cout << it->first<<endl;

it++;

cout << it->first << endl;

it--;

cout << it->first << endl;

while (it != dict.end())

{

cout << it->first << ":" << it->second << endl;

++it;

}

//for(auto&kv:dict) cout << kv.first << ":" << kv.second << endl;

}

void test_map2()

{

string arr[] = { "西瓜", "梨子", "香蕉", "香蕉", "苹果", "西瓜", "梨子", "香蕉", "西瓜", "西瓜", "苹果", "火龙果", "橙子", "梨子"};

map<string, int> countMap;

for (auto& e : arr)

{

++countMap[e];

}

for (auto& kv : countMap)

{

//kv.second = 2; 控制first不能被修改 或者是key不能被修改

cout << kv.first << ":" << kv.second << endl;

}

}

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。