总言
主要介绍map、set的封装框架。
1、基本框架说明
1)、问题说明与stl底层查看
问题:map和set底层都是通过红黑树实现,但我们知道二者功能各不相同,比如map为Key-Value模型,而set为Key模型。如果通过红黑树实现,是否意味着我们需要分别实现两个功能类似的红黑树?
我们来看看库里是如何实现的:
#ifndef __SGI_STL_SET_H
#define __SGI_STL_SET_H
#include <tree.h>
#include <stl_set.h>
#ifdef __STL_USE_NAMESPACES
using __STD::set;
#endif /* __STL_USE_NAMESPACES */
#endif /* __SGI_STL_SET_H */
#ifndef __SGI_STL_MAP_H
#define __SGI_STL_MAP_H
#include <tree.h>
#include <stl_map.h>
#ifdef __STL_USE_NAMESPACES
using __STD::map;
#endif /* __STL_USE_NAMESPACES */
#endif /* __SGI_STL_MAP_H */
相关分析:
2)、map、set中框架搭建
根据上述分析,我们来对原先的红黑树做修改,相关框架:set和map(二):AVL树和红黑树
template<class T>
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)
{ }
};
//此处我们实现红黑树结点只需要第二参数T,那么第一参数K有何用?
//回答:比如find,我们要根据第一参数Key的值,找到对应的结点。
template<class K,class T>
struct RBTree //红黑树
{
typedef RBTreeNode<T> Node;
//……
private:
Node* _root = nullptr;
};
namespace my_set
{
template<class K>
class set
{
public:
//……
//其它实现
private:
RBTree<K, K> _t;
};
}
namespace my_map
{
template<class K, class V>
class map
{
public:
//……
//其它实现
private:
RBTree<K, pair<K, V>> _t;
};
}
2、map、set封装Ⅰ:用于比较的仿函数
1)、问题引入
上述修改后有一个问题: 节点的比较。在原先红黑树中,我们实现了Insert,其判断结点位置时,直接通过运算符进行比较查询合适结点位置。
if (cur->_data.first < data.first)
{
//……
}
else if (cur->_data.first > data.first)
{
//……
}
但这里RBTree是用于实现set、map。对于T _data;
,二者一个结点类型为K
,一个为pair<K,V>
,但都要同时做到使用K(Key)值比较判断。如何做到?
2)、相关实现
方法说明:这里我们为set、map分别实现一个仿函数,根据其各自需求,返回我们需要的用于比较的值:
在set中:
template<class K>
class set
{
public:
struct SetKeyofT
{
const K& operator()(const K& key)
{
return key;
}
};
private:
RBTree<K, K, SetKeyofT> _t;
};
}
在map中:
template<class K, class V>
class map
{
public:
struct MapKeyofT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
private:
RBTree<K, pair<K, V>, MapKeyofT> _t;
};
RBTree中,相关查找修改如下:
KeyOfT kot;
if (kot(cur->_data) < kot(data))//若当前结点值比待插入结点值小,说明待插入结点值在其右子树
{
//……
}
else if (kot(cur->_data) > kot(data))
{
//……
}
框架展示:我们增添了一个class KeyOfT
,根据map、set传入的不同仿函数,获取不同结果。
template<class K, class T, class KeyOfT>
struct RBTree
{
typedef RBTreeNode<T> Node;
public:
bool Insert(const T& data)
{
KeyOfT kot;
//step1:按照二叉搜索树的方式插入新节点
Ⅰ、寻找位置
if (_root == nullptr)//单独处理:根节点为空时
{
_root = new Node(data);
_root->_col = BLACK;//根节点默认为黑色
return true;
}
//根节点不为空:
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
//比较当前结点值和待插入值
if (kot(cur->_data) < kot(data))//若当前结点值比待插入结点值小,说明待插入结点值在其右子树
{
parent = cur;
cur = cur->_right;
}
else if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else//key值已存在
{
return false;
}
}
Ⅱ、插入值,调整链接关系
cur = new Node(data);
cur->_parent = parent;
if (kot(parent->_data) < kot(data))//cur插入在parent的右孩子处
parent->_right = cur;
else
parent->_left = cur;//cur插入在parent的左孩子处
cur->_col = RED;//将插入的结点设置为红色
//step2:检查新增结点后,是否还满足红黑树性质
//……
}
Node* _root = nullptr;
};
3、map、set封装Ⅱ:迭代器实现
3.1、基本说明
1)、基本说明
通常情况下,begin()
与end()
代表的是一段前闭后开的区间。反映到红黑树中,begin()
表示中序遍历下第一个结点(即最左侧结点/最小结点);end()
表示最后一个结点的下一个位置(即最右侧结点/最大结点)。
一种实现方案:通常情况下会给出一个header头结点,类似于链表中的哨兵位头,其left、right分别指向最左结点和最右结点,即我们需要的begin()、end()
。
下述实现中我们并没有借鉴哨兵位的头。
2)、迭代器框架
说明:set、map
的迭代器,实则使用的是红黑树的迭代器,因此实现时,我们需要先实现红黑树中的迭代器,对set、map
而言,则通过封装达成相关需求。
红黑树中,迭代器基本框架如下:类比于List迭代器实现。
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, Ref, Ptr> iterator;
Node* _node;//成员变量:指向结点的指针
__RBTreeIterator(Node* node)//构造函数
:_node(node)
{}
//实现++、--、*、&等
//……
};
3.2、begin()、end()、operator*、operator&、operator==、operator!=
3.2.1、begin()、end()
1)、在红黑树中
template<class K, class T, class KeyOfT>
struct RBTree
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, T&, T*> iterator;
typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
public:
iterator begin()
{ //begin()在红黑树中取最左结点
Node* cur = _root;
while (cur && cur->_left)
{
cur = cur->_left;
}
return iterator(cur);//匿名构造
}
iterator end()
{
return iterator(nullptr);
}
//……
}
2)、在map、set中
map、set中迭代器只需要调用红黑树的迭代器即可。注意此处typename
的使用说明。
typedef typename RBTree<K, K, SetKeyofT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
typedef typename RBTree<K, pair<K, V>, MapKeyofT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
3.2.2、operator*、operator&、operator==、operator!=
在红黑树的迭代器中:(struct __RBTreeIterator)
Ref operator* ()
{
return _node->_data;
}
Ptr operator&()
{
return &(_node->_data);
}
bool operator==(const iterator& it)const
{
return _node == it._node;
}
bool operator!=(const iterator& it)const
{
return _node != it._node;
}
3.3、operator++、operator- -
3.3.1、operator++
红黑树中,operator++
要满足中序遍历,访问当前结点的下一个位置,此时会存在两种情况:
说明:关于此处迭代器的++
、--
,关键需要理解为什么要这样做。本质是因为在此过程中,对左子树、根节点、右子树,根节点会被访问到两次,需要将其跳过。
2)、相关实现
iterator& operator++()
{
if (_node->_right)//当前节点右子树不为空:访问其右子树最左节点
{
Node* cur = _node->_right;
while (cur->_left)
cur = cur->_left;
_node = cur;//让迭代器来到当前结点处
}
else//当前节点右子树为空:沿祖先链迭代,寻找首个孩子非右孩子的父节点
{
Node* cur = _node;
Node* parent = _node->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;//此处迭代器来到的是parent位置处
}
return *this;//最后返回迭代器本身
}
3.3.2、operator- -
反方向进行即可。
iterator operator--()
{
if (_node->_left)//若当前节点左子树存在
{
Node* cur = _node->_left;//--的下一个节点是该左子树的最右值
while (cur->_right)
cur = cur->_right;
_node = cur;
}
else//当前节点左子树不存在:说明--依次访问了右子树、当前节点、左子树,要返回当前节点的上一层
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
3.4、Insert满足pair<iterator,bool>返回值、operator[]
3.4.1、Insert
在之前学习map、set时,对insert的返回值我们有简单介绍:
这里我们同样做出修改:
pair<iterator,bool> Insert(const T& data)
{
KeyOfT kot;//用于辅佐完成set和map因类型不同而无法统一判断
//step1:按照二叉搜索树的方式插入新节点
Ⅰ、寻找位置
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return make_pair(iterator(_root), true);//返回新增节点处的迭代器及bool值
}
//根节点不为空:
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
//比较当前结点值和待插入值
if (kot(cur->_data) < kot(data))
{
parent = cur;
cur = cur->_right;
}
else if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else
{
return make_pair(iterator(cur),false);//key值已存在:返回原先存在的节点的迭代器
}
}
Ⅱ、插入值,调整链接关系
cur = new Node(data);
Node* newnode = cur;//用于返回
cur->_parent = parent;
if (kot(parent->_data) < kot(data))
parent->_right = cur;
else
parent->_left = cur;
cur->_col = RED;
//step2:检查新增结点后,是否还满足红黑树性质
//此处省略
_root->_col = BLACK;
return make_pair(iterator(newnode), true);//返回新增节点处的迭代器及bool值
}
3.4.2、operator[]
有了上述对于insert的改造,map中operator[]
就能够实现(此处的相关解释见前文:set和map(一))
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;//rer.first获取iterator,iterator调用opeator->
}
测试代码如下:
#include"map.h"
void testmap()
{
//次数统计
string arr[] = { "晴","多云","晴","阴","小雨","多云","多云","阴","晴","小雨","大雨","阴","多云","晴" };
my_map::map<string, int> countMap;
for (auto& str : arr)//直接借助范围for遍历
{
countMap[str]++;
}
//范围for
for (const auto& kv : countMap)
{
cout << kv.first << ": " << kv.second << endl;
}
}
4、总览
4.1、set
#pragma once
#include"RedBlackTree.h"
namespace my_set
{
template<class K>
class set
{
struct SetKeyofT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename RBTree<K, K, SetKeyofT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator,bool> insert(const K& val)
{
return _t.Insert(val);
}
private:
RBTree<K, K, SetKeyofT> _t;
};
}
4.2、map
#pragma once
#include"RedBlackTree.h"
namespace my_map
{
template<class K, class V>
class map
{
public:
struct MapKeyofT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
typedef typename RBTree<K, pair<K, V>, MapKeyofT>::iterator iterator;
iterator begin()
{
return _t.begin();
}
iterator end()
{
return _t.end();
}
pair<iterator, bool> insert(const pair<K,V>& val)
{
return _t.Insert(val);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;//rer.first获取iterator,iterator调用opeator->
}
private:
RBTree<K, pair<K, V>, MapKeyofT> _t;
};
}
4.3、RedBlackTree
#pragma once
#include<assert.h>
#include<iostream>
#include<utility>
using namespace std;
enum Colour
{
RED,
BLACK
};
template<class T>
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> iterator;
Node* _node;//成员变量:指向结点的指针
__RBTreeIterator(Node* node)//构造函数
:_node(node)
{}
//实现++、--、*、->
Ref operator* ()
{
return _node->_data;
}
Ptr operator->()
{
return &(_node->_data);
}
bool operator==(const iterator& it)const
{
return _node == it._node;
}
bool operator!=(const iterator& it)const
{
return _node != it._node;
}
iterator& operator++()
{
if (_node->_right)//当前节点右子树不为空:访问其右子树最左节点
{
Node* cur = _node->_right;
while (cur->_left)
cur = cur->_left;
_node = cur;//让迭代器来到当前结点处
}
else//当前节点右子树为空:沿祖先链迭代,寻找首个孩子非右孩子的父节点
{
Node* cur = _node;
Node* parent = _node->_parent;
while (parent && cur == parent->_right)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;//此处迭代器来到的是parent位置处
}
return *this;//最后返回迭代器本身
}
iterator operator--()
{
if (_node->_left)//若当前节点左子树存在
{
Node* cur = _node->_left;//--的下一个节点是该左子树的最右值
while (cur->_right)
cur = cur->_right;
_node = cur;
}
else//当前节点左子树不存在:说明--依次访问了右子树、当前节点、左子树,要返回当前节点的上一层
{
Node* cur = _node;
Node* parent = cur->_parent;
while (parent && cur == parent->_left)
{
cur = parent;
parent = cur->_parent;
}
_node = parent;
}
return *this;
}
};
template<class K, class T, class KeyOfT>
struct RBTree
{
typedef RBTreeNode<T> Node;
typedef __RBTreeIterator<T, T&, T*> iterator;
typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
public:
iterator begin()
{ //begin()在红黑树中取最左结点
Node* cur = _root;
while (cur && cur->_left)
{
cur = cur->_left;
}
return iterator(cur);//匿名构造
}
iterator end()
{
return iterator(nullptr);
}
pair<iterator,bool> Insert(const T& data)
{
KeyOfT kot;
//step1:按照二叉搜索树的方式插入新节点
Ⅰ、寻找位置
if (_root == nullptr)//单独处理:根节点为空时
{
_root = new Node(data);
_root->_col = BLACK;//根节点默认为黑色
return make_pair(iterator(_root), true);
}
//根节点不为空:
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
//比较当前结点值和待插入值
if (kot(cur->_data) < kot(data))//若当前结点值比待插入结点值小,说明待插入结点值在其右子树
{
parent = cur;
cur = cur->_right;
}
else if (kot(cur->_data) > kot(data))
{
parent = cur;
cur = cur->_left;
}
else//key值已存在
{
return make_pair(iterator(cur),false);
}
}
Ⅱ、插入值,调整链接关系
cur = new Node(data);
Node* newnode = cur;//用于返回
cur->_parent = parent;
if (kot(parent->_data) < kot(data))//cur插入在parent的右孩子处
parent->_right = cur;
else
parent->_left = cur;//cur插入在parent的左孩子处
cur->_col = RED;//将插入的结点设置为红色
//step2:检查新增结点后,是否还满足红黑树性质
while (parent && parent->_col == RED)//新结点后,无论是cur本身,还是迭代后变色,如果parent、cur颜色皆红,则违反性质3:不能有连续的红结点,因此需要调整
{
//固定:
Node* grandparent = parent->_parent;
assert(grandparent && grandparent->_col == BLACK);//grandparent存在且为黑
//关键看叔叔,此处对uncle和parent在不同位置分别处理
if (parent == grandparent->_left)
{
Node* uncle = grandparent->_right;
//情况一
if (uncle&& uncle->_col == RED)//叔叔存在且为红
{
//处理方法:变色
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
//继续向上调整
cur = grandparent;
parent = cur->_parent;
}
else //叔叔不存在或叔叔存在且为黑:情况二+情况三
{
//处理方法:旋转+变色(旋转有四类)
//Ⅰ:情况二:p在g左,c在p左,成直线。右单旋+g变色为红,p变色为黑
if (cur == parent->_left)
{
RotateR(grandparent);
grandparent->_col = RED;
parent->_col = BLACK;
}
else//Ⅱ:情况三:p在g左,c在p右,成折线。左右双旋+g变色为红,c变色为黑
{
RotateL(parent);
RotateR(grandparent);
grandparent->_col = RED;
cur->_col = BLACK;
}
break;//情况二、情况三完成调整后,满足红黑树规则,不必继续向上调整
}
}
else//parent == grandparent->_right
{
Node* uncle = grandparent->_left;
//情况一
if (uncle&& uncle->_col == RED)//叔叔存在且为红
{
//处理方法:变色
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
//继续向上调整
cur = grandparent;
parent = cur->_parent;
}
else //叔叔不存在或叔叔存在且为黑:情况二+情况三
{
//处理方法:旋转+变色(旋转有四类)
//Ⅰ:情况二:p在g右,c在p右,成直线。左单旋+g变色为红,p变色为黑
if (cur == parent->_right)
{
RotateL(grandparent);
grandparent->_col = RED;
parent->_col = BLACK;
}
else//Ⅱ:情况三:p在g右,c在p左,成折线。右左双旋+g变色为红,c变色为黑
{
RotateR(parent);
RotateL(grandparent);
grandparent->_col = RED;
cur->_col = BLACK;
}
break;//情况二、情况三完成调整后,满足红黑树规则,不必继续向上调整
}
}
}
_root->_col = BLACK;
return make_pair(iterator(newnode), true);
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
{
cout << "根节点不满足黑色" << endl;
return false;
}
int BasicalNum = 0;//用于确定基准值:前序遍历时的第一条路径
int blackNum = 0;//用于统计树中黑色结点数
return PrevCheck(_root, blackNum, BasicalNum);
}
private:
bool PrevCheck(Node* root, int blackNum, int& basicalNum)
{
if (root == nullptr)
{
if (basicalNum == 0)
{
basicalNum == blackNum;
return true;
}
else
{
if (blackNum == basicalNum)
return true;
else
{
cout << "某条路径黑色结点数不匹配" << endl;
return false;
}
}
}
if (root->_col == BLACK)
++blackNum;
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "存在连续的红色结点" << endl;
return false;
}
return PrevCheck(root->_left, blackNum, basicalNum)
&& PrevCheck(root->_right, blackNum, basicalNum);
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
//cout << root->_kv.first << "," << root->_kv.second << endl;
_InOrder(root->_right);
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRtoL = subR->_left;
Node* grendparent = parent->_parent;
//修改链接关系以达成旋转
parent->_right = subRtoL;
parent->_parent = subR;
if (subRtoL)//h>=0,subRtoL可能不存在
subRtoL->_parent = parent;
subR->_left = parent;
if (_root == parent)//parent为原先AVL树的根节点
{
subR->_parent = nullptr;
_root = subR;
}
else//parent为原先AVL树的分支节点
{
subR->_parent = grendparent;
if (grendparent->_left == parent)
grendparent->_left = subR;
else
grendparent->_right = subR;
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLtoR = subL->_right;
Node* grendparent = parent->_parent;
//处理链接关系
parent->_left = subLtoR;
parent->_parent = subL;
if (subLtoR)
subLtoR->_parent = parent;
subL->_right = parent;
if (_root == parent)
{
subL->_parent = nullptr;
_root = subL;
}
else
{
subL->_parent = grendparent;
if (grendparent->_left == parent)
grendparent->_left = subL;
else
grendparent->_right = subL;
}
}
Node* _root = nullptr;
};