使用红黑树封装map和set
Posted Moua
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用红黑树封装map和set相关的知识,希望对你有一定的参考价值。
目录
红黑树和AVL树都是二叉搜索树,但是从效率以及实现方式等方面综合来看,红黑树比AVL树更优。也就是说,红黑树是一种更好的,适合搜索的数据结构。同时,红黑树的使用方面非常广泛,Java库、linux内核、c++STL中都有用到红黑树这种数据结构。在STL中,map和set就是基于红黑树来实现。
一、对红黑树进行调整并增加迭代器
1、分析STL中红黑树、set、map源码
map和set两个关联式容器,底层都是对红黑树进行了封装,并且使用的是一棵红黑树来实现的。
通过前面对set和map的了解,它们都属于key/value模型,准确来说set是key/key类型的,而map是key/value类型的。这也就是说,stl中的set和map要使用用一颗红黑树来封装,按理来讲这颗红黑树的值域就必须是pair<key,value>。但是对于set来说,如果也是键值对,如果key类型是字符串或者其他较大类型,存储两份实在是太浪费空间了。
实际上,在stl原码红黑树的节点是一个模板参数Value类型,如果是set它的value就是key类型的,如果是map它的类型就是pair<key,value>。
//stl原码中红黑树节点的定义(删减)
typedef bool __rb_tree_color_type;
struct __rb_tree_node_base
typedef __rb_tree_color_type color_type;//实际上就是bool类型
typedef __rb_tree_node_base* base_ptr;
color_type color;
base_ptr parent;
base_ptr left;
base_ptr right;
;
template <class Value>//模板参数只有value
struct __rb_tree_node : public __rb_tree_node_base
Value value_field;
;
接下来在看一下map和set是如何对模板参数进行传参的:
//STL中map源码(删减)
template <class Key, class T, class Compare, class Alloc = alloc>
class map
public:
typedef Key key_type;
typedef T data_type;
typedef T mapped_type;
typedef pair<const Key, T> value_type;
private:
typedef rb_tree<key_type, value_type,
select1st<value_type>, key_compare, Alloc> rep_type;
;
/*这里我们可以看出,模板参数一共有四个,后两个暂时不关心
*第一个参数是key类型,第二个是pair<key,T>
*通过刚才对红黑树节点的分析,可以知道第二个参数pair<key,T>是用来构造节点的
*第一个参数多余吗?不多余,因为map和set底层都是同一颗红黑树,如果不传第一个参数
*那么如果是map使用红黑树,红黑树中如何知道key是什么类型?
*/
//STL中set源码(删减)
template <class Key, class Compare, class Alloc = alloc>
class set
public:
typedef Key key_type;
typedef Key value_type;
typedef Compare key_compare;
typedef Compare value_compare;
private:
typedef rb_tree<key_type, value_type,
identity<value_type>, key_compare, Alloc> rep_type;
;
/*set中只存储一个key类型的值作为节点的值域
*它在进行模板参数传递时,一个是key_type,一个是value_type,但是实际上都是key类型的
*之所以这么做就是为了和map使用同一颗红黑树,但是还不能将key在节点中存两份
*同样,它的第一个参数也仅仅只是为了说明key的类型
*/
看完这些,红黑树、map和set的大体框架已经有了,最后一个问题就是模板参数中的compare是用来干啥的?
因为set和map的Value值不同(set是key,map是键值对),在红黑树中存在很多比较的地方,到底该采用那种方式进行比较呢?因此,这里的compare实际上是一个仿函数,这个仿函数的功能就是返回Value中的key类型的值。当set调用时,返回的就是key类型的节点,当map调用时返回的就是键值对的第一个值。
2、红黑树迭代器实现
map和set都没有自己的迭代器,它们都是对红黑树进行封装,使用红黑树的迭代器。红黑树的迭代器相比于前边的序列式容器的迭代器略有不同。首先红黑树迭代器的++、--就比较特殊。
1)前置++
情况21:当前节点的右孩子节点不为空,++指向的应该是当前孩子节点的最左孩子节点。
情况2:当前节点是父节点的左孩子,且当前节点的右孩子节点为空。++指向其父节点。
情况3:当前节点是父节点的右孩子,且当前节点的右孩子节点为空。++后指向其祖先节点中是父节点的左孩子节点的父节点。
代码实现:
//前置++
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 && parent->_right == cur)
cur = parent;
parent = parent->_parent;
_node = parent;
return *this;
2)前置--
情况1:左子树存在,在左子树中找最右节点。
情况2:左子树不存在
- 如果当前节点是父节点的左孩子节点,则--后为其祖先节点中第一个不是其父节点的左孩子节点的父节点
- 如果当前节点是父节点的右孩子节点,则--后为该节点的父节点
代码实现:
Self operator--()
//左子树存在
if (_node->_left)
//找左子树的最右节点
Node* cur = _node->_left;
while (cur->_right)
cur = cur->_right;
else
//左子树不存在,向上找
Node* cur = _node;
Node* parent = cur->_parent;
//找不是父节点的左孩子节点的节点
while (parent && parent->_left == cur)
cur = parent;
parent = parent->_parent;
_node = parent;
注意:这里红黑树没有头结点,end()认为是nullptr,如果有head节点则end()应该是head。如果堆head--应该找最大,这里简单实现就暂不考虑。
3、模拟实现红黑树(带迭代器)
namespace Moua
//定义节点
template<class Value_Type>
struct RBTreeNode
typedef RBTreeNode<Value_Type> Node;
Color _col;
Node* _parent;
Node* _left;
Node* _right;
Value_Type _val;
//构造函数
RBTreeNode(const Value_Type& val)
:_col(RED),
_val(val),
_parent(nullptr), _left(nullptr), _right(nullptr)
;
//迭代器定义
//template<class T,class T&,class T*>
template<class T, class Ref, class Ptr>
struct RBTreeIterator
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T, Ref, Ptr> Self;
Node* _node;
//构造函数
RBTreeIterator(Node* nd)
:_node(nd)
Ref operator*()
return _node->_val;
Ptr operator->()
return &(_node->_val);
//前置++
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 && parent->_right == cur)
cur = parent;
parent = parent->_parent;
_node = parent;
return *this;
//后置++
Self operator++(int)
Self ret = *this;
//调用前置++
++(*this);
return ret;
//前置--
Self operator--()
//左子树存在
if (_node->_left)
//找左子树的最右节点
Node* cur = _node->_left;
while (cur->_right)
cur = cur->_right;
else
//左子树不存在,向上找
Node* cur = _node;
Node* parent = cur->_parent;
//找不是父节点的左孩子节点的节点
while (parent && parent->_left == cur)
cur = parent;
parent = parent->_parent;
_node = parent;
//后置--
Self operator--(int)
Self cur = this;
--(*this);
return cur;
//不等于
bool operator!=(const Self s) const
return _node != s._node;
;
//红黑树
template<class key,class T,class KeyOfT>
class RBTree
//注意:节点模板参数传T即可
typedef RBTreeNode<T> Node;
private:
Node* _root = nullptr;//根节点
public:
typedef RBTreeIterator<T, T&, T*> iterator;
typedef RBTreeIterator<T, const T&, const T*> ConstIterator;
iterator begin()
Node* cur = _root;
//返回数的最左孩子节点
while (cur && cur->_left)
cur = cur->_left;
return iterator(cur);
iterator end()
//直接返回一个空节点即可
return iterator(nullptr);
//插入---插入的节点的值应该是T类型的
pair<Node*, bool> insert(const T& val)
KeyOfT compare;//对仿函数进行实例化
//申请节点
Node* newNode = new Node(val);
//如果根节点为空,直接插入为根节点
if (_root == nullptr)
_root = newNode;
_root->_col = BLACK;
return make_pair(_root, true);
//找插入位置进行插入
Node* cur = _root;
Node* parent = nullptr;
while (cur)
//这里的比较直接使用仿函数进行比较(set和map的比较方式不同)
if (compare(cur->_val) > compare(newNode->_val))
//插入的节点比cur小,左插
parent = cur;
cur = cur->_left;
else if (compare(cur->_val) < compare(newNode->_val))
//插入的节点比cur大,右插
parent = cur;
cur = cur->_right;
else
//插入的节点存在,直接返回该节点的val
return make_pair(cur, false);
//找到了插入位置,进行插入
if (compare(parent->_val) > compare(newNode->_val))
//插入到parent的左边
parent->_left = newNode;
newNode->_parent = parent;
else
//插入到parent的右边
parent->_right = newNode;
newNode->_parent = parent;
//插入成功,对树进行调整
cur = newNode;
parent = cur->_parent;
//新插入节点的父节点是红色的才需要调整---走到这里,新插入的节点父节点肯定存在
while (parent && parent->_col == RED)
//走到这里grandParent节点必然是黑色的
Node* grandParent = parent->_parent;
Node* uncle = nullptr;
if (grandParent->_left == parent)
uncle = grandParent->_right;
else
uncle = grandParent->_left;
//情况1:新插入节点的叔叔节点存在且为红
if (uncle && uncle->_col == RED)
//将父节点和叔叔节点变成黑色,爷爷节点变成红色
uncle->_col = BLACK;
parent->_col = BLACK;
grandParent->_col = RED;
//继续迭代
cur = grandParent;
parent = cur->_parent;
else
//新插入节点的叔叔节点不存在或者新插入节点的叔叔节点为黑色
if (grandParent->_left == parent)
if (parent->_left == cur)
//右单旋
RotateR(grandParent);
//调整颜色
parent->_col = BLACK;
grandParent->_col = RED;
else
//左右双旋
RotateL(parent);
RotateR(grandParent);
//调整颜色
cur->_col = BLACK;
grandParent->_col = RED;
else
if (parent->_right == cur)
//左单旋
RotateL(grandParent);
parent->_col = BLACK;
grandParent->_col = RED;
else
//右左双旋
RotateR(parent);
RotateL(grandParent);
//调整颜色
cur->_col = BLACK;
grandParent->_col = RED;
break;
//在调整过程中,有可能将根节点变成了红色节点,因此需要将根节点调整成黑色的
_root->_col = BLACK;
return make_pair(newNode, true);
//右单旋
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 (parent == _root)
_root = subL;
_root->_parent = nullptr;
else
if (parentParent->_left == parent)
parentParent->_left = subL;
else
parentParent->_right = subL;
subL->_parent = parentParent;
//左单旋
void RotateL(Node* parent)
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* parentParent = parent->_parent;
parent->_parent = subR;
if (_root == parent)
_root = subR;
else
if (parentParent->_left == parent)
parentParent->_left = subR;
else
parentParent->_right = subR;
subR->_parent = parentParent;
static void _inOrder(Node* root)
KeyOfT compare;//对仿函数进行实例化
if (root == nullptr)
return;
_inOrder(root->_left);
std::cout << compare(root->_val) << " ";
_inOrder(root->_right);
//中序遍历
void inOrder()
_inOrder(_root);
std::cout << endl;
bool RedNode(Node* root)
if (root == nullptr)
return true;
if (root->_col == RED)
//判断父节点是否为红色
if (root->_parent && root->_parent->_col == RED)
return false;
//判断左右子树
return RedNode(root->_left) && RedNode(root->_right);
bool BlackNodeNum(Node* root, int blackNum, int num)
//检查是否每条路径上的黑色节点的个数都相同
if (root == nullptr)
return blackNum == num;
if (root->_col == BLACK)
blackNum++;
return BlackNodeNum(root->_left, blackNum, num) && BlackNodeNum(root->_right, blackNum, num);
//检查红黑树
bool check()
if (_root && _root->_col == RED)
return false;
//求出一条路径上黑色节点的个数
int num = 0;
Node* cur = _root;
while (cur)
if (cur->_col == BLACK)
num++;
cur = cur->_left;
return RedNode(_root) && BlackNodeNum(_root, 0, num);
;
二、使用红黑树模拟实现map
namespace MouMap
template<class K,class V>
class map
//仿函数
struct MapKOfT
const K& operator()(const pair<const K, V>& kv)
return kv.first;
;
private:
RBTree<K, pair<const K, V>, MapKOfT> _t;
public:
//构造函数
pair<RBTreeNode<pair<const K, V>>*, bool> insert(const pair<const K, V>& kv)
return _t.Insert(kv);
;
三、使用红黑树模拟实现set
namespace MouSet
template<class K>
class set
struct SetKOfT
const K& operator()(const K& k)
return k;
;
public:
typedef typename RBTree<K, K, SetKOfT>::Iterator iterator;
iterator begin()
return _t.Begin();
iterator end()
return _t.End();
pair<RBTreeNode<K>*, bool> insert(const K& k)
return _t.Insert(k);
private:
RBTree<K, K, SetKOfT> _t;
;
以上是关于使用红黑树封装map和set的主要内容,如果未能解决你的问题,请参考以下文章