使用红黑树封装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的主要内容,如果未能解决你的问题,请参考以下文章