标题:[C++] map、set 的 封装 (二)
@水墨不写bug
前言
在正式深入进map、set封装之前,我有一些话想说,map和set的封装在初次理解时可能会比较困难,仅仅是模板,仿函数引起的回调就会把你拌入无底深渊。但是,在我看来,map、set的封装本身就是一个双向奔赴的过程,map、set想要复用红黑树,红黑树也为复用进一步调整,使复用更加简单方便。这是两个齿轮不断校准的过程,直到两侧的齿轮紧紧的咬合在一起。所以,既要讲map、set也要讲红黑树。
此外,这里的封装技巧也是无数先辈经验和智慧的总结,这当然需要我们花一部分时间来理解和吸收。
所以,不着急,路要一步一步的走。
目录
前言
一、红黑树的迭代器实现
1.重载 operator++()、 operator--():
2.const迭代器实现
3.迭代器模板参数传入的细节
二、红黑树的成员函数实现细节
1.begin,end
2.find
3.insert
三、map、set层面做出的努力
正文开始:
一、红黑树的迭代器实现
红黑树的迭代器是我们封装的一个类,在类中,我们可以对运算符进行重载。重载运算符的类实例化出的对象会根据我们重载时的规则来表现出我们想要的行为。
比如:一个整形指针,++代表向后移动一个整形长度(4字节),但是对于关联性容器红黑树,其迭代器++代表移动到中序的下一个位置,这就需要我们自己通过重载来实现。
迭代器设计出来,是为了方便对容器进行操作。红黑树迭代器中,我们主要讲解实现++和--的重载:
1.重载 operator++()、 operator--():
operator++:
中序遍历为:左子树,根,右子树。
在类中隐含有this,代表我们想要让this 以中序 后移一位。我们要暂时用局部的眼光来看待这个问题:
当this的右子树非空,由于this访问过,接下来要访问右子树的最左节点;
当右子树为空,则this这棵树访问完了,这就需要向上一级的子树移动,继续判断,这是一个循环的过程:只要当前节点是父节点的右子树根节点,则表示以父为根的子树访问完了,需要向上一级子树移动。
operator--:
逆中序遍历为:右子树,根,左子树。
类似的:
当this的左子树非空,由于this被访问过,接下来要访问左子树的最右节点;
当左子树为空,则this这棵树访问完了,这就需要向上一级的子树移动,继续判断,这是一个循环的过程:只要当前节点是父节点的左子树根节点,则表示以父为根的子树访问完了,需要向上一级子树移动。
与++不同的是:我们实现的红黑树没有设置头节点(哨兵),而是如下设计:
begin():最左节点;
end():nullptr
所以当我们想要用end()的返回值用迭代器反向遍历时,会出现对nullptr解引用导致崩溃的问题。
解决方案如下:
在重载operator--时,进行特殊判断,如果是对end()的返回值(nullptr)解引用,则找到最右节点返回即可。
2.const迭代器实现
与实现链表的迭代器的时候类似,我们通过传入特殊类型,与普通模板参数区分开:
普通迭代器传入 <V,V&,V*>;
const迭代器传入 <V,const V&,const V*>
同时多设置两个模板参数额外用于接受引用与指针类型,分别用于operator*,operator->的返回值。
3.迭代器模板参数传入的细节
在传入迭代器模板参数时,需要注意:
map的key不能修改;
set数据本身就不能修改。
红黑树是插入的时候根据key的大小来构建的。为了防止我们通过迭代器修改key,导致红黑树失效,由于我们修改的是数值域,所以我们把set迭代器的第二个参数(数值域)设为const;把map的pair内的K设置为const即可:
红黑树迭代器设计参考:
//迭代器类似于指针,内置类型的指针的++,*,==,!= 运算默认成立
//红黑树迭代器类目的在于 重载++,*,==,!=运算符,红黑树迭代器按照我们想要的方式进行运算
template<class V,class Ref,class Ptr>
struct RBTreeIterator
{
typedef RBTreeIterator<V,Ref,Ptr> Self;
typedef RBTreeNode<V> Node;
Node* _node;
Node* _root;
RBTreeIterator(Node* node,Node* root)
:_node(node)
,_root(root)
{}
//中序遍历,左 根 右
Self& operator++()
{
//右子树不为空,找右子树的最左节点
if(_node->_right)
{
Node* leftMost = _node->_right;
while (leftMost->_left)
{
leftMost = leftMost->_left;
}
_node = leftMost;
}
else
//右子树为空,表示此树访问完了
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
//反向的中序 右 根 左
//由于end是nullptr,需要特殊处理
Self& operator--()
{
if (_node == nullptr)
//找最右节点
{
Node* rightMost = _root;
while (rightMost->_right)
{
rightMost = rightMost->_right;
}
_node = rightMost;
}
else if (_node->_left)
//左子树的最右节点
{
Node* rightMost = _node->_left;
while (rightMost->_right)
{
rightMost = rightMost->_right;
}
_node = rightMost;
}
else
{
Node* cur = _node;
Node* parent = cur->_parent;
while (cur == parent->_left)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
};
二、红黑树的成员函数实现细节
1.begin,end
对于begin、end不再赘述,本别返回最左节点、nullptr。
2.find
根据key找到对应的节点,返回这个节点的迭代器,如果此节点不存在,则返回nullptr构造的迭代器。
3.insert
是红黑树的重点逻辑。在之前的实现逻辑上,我们主要更改了insert的返回值类型,从单一的bool型改为了含有迭代器和bool的pair型:pair<iterator,bool>;与之前的实现基本相同。
Iterator Begin()
{
Node* leftMost = _root;
while (leftMost && leftMost->_left)
{
leftMost = leftMost->_left;
}
return Iterator(leftMost,_root);
}
//nullptr 暂时代表end
Iterator End()
{
return Iterator(nullptr,_root);
}
Iterator Find(const K& key)
{
KeyOfV kov;
Node* cur = _root;
while (cur)
{
if (kov(cur->_data) < key)
{
cur = cur->_right;
}
else if (kov(cur->_data) > key)
{
cur = cur->_left;
}
else
{
return Iterator(cur, _root);
}
}
return End();
}
pair<Iterator,bool> insert(const V& data)
{
//对于空的特殊处理
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
//插入成功
return { Iterator(_root,_root),true };
}
//找到插入位置
Node* cur = _root, * parent = nullptr;
KeyOfV keyofv;
while (cur)
{
if (keyofv(cur->_data) < keyofv(data))
{
parent = cur;
cur = cur->_right;
}
else if (keyofv(cur->_data) > keyofv(data))
{
parent = cur;
cur = cur->_left;
}
else
{//插入失败,有相同值
return { Iterator(cur,_root),false };
}
}
//new并连接
cur = new Node(data);
Node* newnode = cur;
if (keyofv(parent->_data) < keyofv(data))
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
//降低平衡
//二叉树逻辑结束,红黑树开始
//cur为红,p为红,g为黑,
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)
{
//uncle在右侧
// g
// p u
//
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)//u存在且为红
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//继续向上处理
cur = grandfather;
parent = cur->_parent;
}
else//u不存在或者u存在且为黑
//旋转
{
if (cur == parent->_left)
{
RotateR(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
RotateL(parent);
RotateR(grandfather);
grandfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
else
{
//uncle在左侧s
// g
// u p
//
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)//uncle存在且为红
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = grandfather->_parent;
}
else//uncle不存在或者存在且为黑
//旋转
{
if (cur == parent->_right)
{
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
RotateR(parent);
RotateL(grandfather);
grandfather->_col = RED;
cur->_col = BLACK;
}
break;
}
}
}
_root->_col = BLACK;
return { Iterator(newnode,_root),true };
}
三、map、set层面做出的努力
其实,在底层红黑树已经实现出来的情况下,map、set层面并不复杂。
需要做的工作是为map、set设计一个表层调用的壳。函数的实现大部分是通过红黑树对象调用红黑树成员函数。
实现如下:
my_set.h:
#pragma once
#include"RBTree.h"
namespace ddsm
{
template<class K>
class set
{
struct SetKeyOfV
{
const K& operator()(const K& key)
{
return key;
}
};
public:
//封装红黑树的Iterator,在本封装层套一层壳
typedef typename RBTree<K,const K, SetKeyOfV>::Iterator iterator;
typedef typename RBTree<K,const K, SetKeyOfV>::ConstIterator const_iterator;
iterator begin()
{
return _rbtree.Begin();
}
iterator end()
{
return _rbtree.End();
}
const_iterator begin() const
{
return _rbtree.Begin();
}
const_iterator end() const
{
return _rbtree.End();
}
pair<iterator,bool> insert(const K& data)
{
return _rbtree.insert(data);
}
iterator find(const K& key)
{
return _rbtree.Find(key);
}
private:
bool isvaildset()
{
return _rbtree.IsValidRBTree();
}
RBTree<K,const K, SetKeyOfV> _rbtree;
};
}
my_map.h:
#pragma once
#include"RBTree.h"
namespace ddsm
{
template<class K,class V>
class map
{
struct MapKeyOfV
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
//封装红黑树的Iterator,在本封装层套一层壳
typedef typename RBTree<K, pair<const K, V>, MapKeyOfV>::Iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfV>::ConstIterator const_iterator;
iterator begin()
{
return _rbtree.Begin();
}
iterator end()
{
return _rbtree.End();
}
const_iterator begin() const
{
return _rbtree.Begin();
}
const_iterator end() const
{
return _rbtree.End();
}
pair<iterator,bool> insert(const pair<K,V>& data)
{
return _rbtree.insert(data);
}
iterator find(const K& key)
{
return _rbtree.Find(key);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert({key,V()});
return ret.first->second;
}
private:
bool isvaildmap()
{
return _rbtree.IsValidRBTree();
}
RBTree<K, pair<const K, V>, MapKeyOfV> _rbtree;
};
}
完~
未经作者同意禁止转载