学习->C++篇十五:红黑树和map,set

目录

一 . 红黑树的定义(规则):

二 . 红黑树是如何保持平衡的?

插入操作:

情况一:叔叔节点存在且为红色

情况二:叔叔节点不存在或存在且为黑色

三.map和set如何封装?


一 . 红黑树的定义(规则):

红黑树的规则如下:

1.每个节点的颜色不是红色就是黑色

2.根节点的颜色是黑色

3.红色节点的孩子是黑色        

4.每个节点到所有叶节点的简单路径上的黑色节点数目是相等的

5.空节点(NIL)认为是黑色的叶子节点

        由定义可知:红黑树的根节点到各个叶节点的简单路径上的黑色节点总数是相等的。所以当路径上全都是黑色节点时,该路径将是最短的,倘若是一黑一红则是最长的路径,显然最长路径是最短路径的两倍,这是由定义中的(3)和(4)决定的红黑树的性质,即最长路径不超过最短路径的两倍。

于是可以定义红黑树的节点如下:

enum Color{RED,BLACK};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _data;
	Color _col;

	RBTreeNode(const T& data = T())
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

        节点默认的构造函数给的节点颜色是红色,这是因为规则3的冲突比规则4的冲突更好解决,因为如果违反了规则3需要变色或旋转可以解决,而违反规则4则是增加了黑色节点数,影响了所有路径。

二 . 红黑树是如何保持平衡的?

        红黑树的规则让红黑树不像平衡二叉搜索树那样绝对平衡,而是近似的平衡,通过保持树中从根到叶最长路径不超过最短路径的两倍,这样搜索效率相对平衡二叉搜索树略微下降,而主要是减少了插入时的旋转次数,从而提高的插入效率,从而提高了红黑树整体的效率。

首先实现红黑树的基本结构如下:

        为了封装map和set,增加一个参数KeyOfValue传入仿函数获取存储的值的类型的key。同时封装上迭代器。

#pragma once
#include<map>
using std::pair;
enum Color{RED,BLACK};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _data;
	Color _col;

	RBTreeNode(const T& data = T())
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

template<class T,class Ptr,class Ref>
struct RBTreeIterator
{
	typedef RBTreeIterator<T, Ptr, Ref> Self;
	typedef RBTreeNode<T> Node;
	RBTreeIterator(Node* pNode)
		:_pNode(pNode)
	{}

	Ptr operator->() { return &_pNode->_data; }
	Ref operator*() { return _pNode->_data; }
	Self& operator++()
	{
		Increasement();
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(_pNode);
		Increasement();
		return tmp;
	}
	Self& operator--()
	{
		Decreasement();
		return *this;
	}
	Self& operator--(int)
	{
		Self tmp(_pNode);
		Decreasement();
		return tmp;
	}
	
	bool operator==(Self& p) { return _pNode == p._pNode; }
	bool operator!=(Self& p) { return _pNode != p._pNode; }
private:
	void Increasement()
    {
	}

	void Decreasement()
	{
	}
private:
	Node* _pNode;
};

template<class K,class T,class KeyOfValue>
class RBtree
{
	typedef RBTreeNode<T> Node;
public:
	RBtree()
		:_pHead(new Node) 
		//建一个虚拟的头结点,作为迭代器的end()位置,方便迭代器遍历
		//让头结点的左指针指向迭代器的起始位置(最左节点),右指针指向最右结点
	{
		_pHead->_left = _pHead;
		_pHead->_right = _pHead;
		_size = 0;
	}
	typedef RBTreeIterator<T, T*, T&> Iterator;
	Iterator Begin() { return Iterator(_pHead->_left); }
	Iterator End() { return Iterator(_pHead); }

	pair<Iterator, bool> Insert(const T& data)
	{
	}

	Node* Find(const T& data)
	{
	}

private:
	Node* LeftMost()//方便插入时更新_pHead的左边
	{
		Node* ret = _pHead->_parent;
		while (ret->_left)
			ret = ret->_left;
		return ret;
	}

	Node* RightMost()//方便插入时更新_pHead的右边
	{
		Node* ret = _pHead->_parent;
		while (ret->_right)
			ret = ret->_right;
		return ret;
	}
	Node*& GetRoot() { return _pHead->_pParent; }
private:
	Node* _pHead;
	size_t _size;
};

        要利用迭代器遍历树,进行二叉树的中序遍历,故迭代器的++是寻找二叉树中序遍历的下一个结点。当结点有右子树,直接寻找右子树的最左节点,否则,寻找第一个父节点的left为_pNode,父节点即为所求(也可画图理解)。

实现如下:

    void Increasement()
	{
		assert(_pNode);//有虚拟头结点,理论上不为nullptr
		if (_pNode->_right)
		{
			_pNode = _pNode->_right;
			while (_pNode->_left)
				_pNode = _pNode->_left;
		}
		else
		{
			Node* parent = _pNode->_parent;
			//寻找到第一个父节点,其左节点是_pNode,父节点即为所求
			while (parent->_right == _pNode)
			{
				_pNode = parent;
				parent = parent->_parent;
			}
			//当 _root 是最右节点,++得虚拟头结点,即end,不需再赋值一次
			if (parent->_right != _pNode)
				_pNode = parent;
		}
	}

实现迭代器的--逻辑与上面的++相反:

    void Decreasement()
	{
		assert(_pNode);//有虚拟头结点,理论上不为nullptr
		if (_pNode->_col == RED &&
			_pNode->_parent->_parent = _pNode)//当前迭代器是end访问的结点
			_pNode = _pNode->_right;
		else if (_pNode->_left)//有左节点,左子树的最右节点为所求
		{
			_pNode = _pNode->_left;
			while (_pNode->_right)
				_pNode = _pNode->_right;
		}
		else//寻找第一个父节点,其右节点为pNode
		{
			Node* parent = _pNode->_parent;
			while (parent->_left == _pNode)
			{
				_pNode = parent;
				parent = parent->_parent;
			}
			_pNode = parent;//倘若是begin,-- 得虚拟头结点,即end
		}
	}

接下来实现查找,同之前的二叉搜索树,直接从根开始迭代往下找即可。

	Node* Find(const T& data)
	{
		KeyOfValue kot;//用仿函数实现map和set
		if (_size == 0)
			return nullptr;
		Node* cur = _pHead->_parent;
		while (cur)
		{
			if (kot(cur->_data) == kof(data))
				break;
			else if (kot(cur->_data) > kof(data))
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		return cur;
	}

插入操作:

        首先按二叉搜索树的规则在指定位置插入新节点(这里实现的map,不允许key重复),红黑树默认插入红色节点,倘若新增节点的父节点为黑色,则不与红黑树的规则发生冲突,倘若新增节点的父亲节点的颜色为红色,会发生两种冲突规则的情况:

情况一:叔叔节点存在且为红色

        这时违法规则,可以变色处理,将parent和uncle的颜色变成黑色,从而不与cur的颜色冲突,然后将grandparent的颜色变成红色,从而让parent和uncle的两条路径上的黑色节点数保持不变,这样就不违法红黑树的规则了,因为grandparent可能是作为红黑树的子树,grandparent的变色可能会导致其父节点颜色与它冲突,故还需往上冲突,如果grandparent就是根节点,直接将根节点的颜色变成黑色就可以了,根节点颜色的影响是整棵树,所以仍遵守红黑树的规则。

子树的情况图(通用):(这里新增节点有左右子树,因为考虑到该情况可能由之前的节点往上更新导致的,abcde为空时就是新增的情况)

情况二:叔叔节点不存在或存在且为黑色

叔叔节点不存在或存在且为黑色时,这时单纯地变色无法达到遵守红黑树规则的目的,需要先进行旋转,根据cur节点相对parent和grandparent的位置,进行旋转(与平衡二叉搜索树相同),然后再进行变色,从而继续保持遵守红黑树的规则。

由前篇平衡二叉搜索树可知,旋转时有四种情况:

如图:

 以两种情况为例,其他情况同理:

1.当叔叔节点不存在时,且旋转情况为情况一,如图:

 2.当叔叔节点存在且为黑,且旋转情况为情况三,如图:

        其他的情况都是同理的,通过旋转让三者保持一个左根右三元组的状态,然后让根变为黑色,左右的节点变成红色即可。

总结情况一,二,可以实现插入的代码如下:(这里的左旋和右旋直接复用平衡树的即可)

	pair<Iterator, bool> Insert(const T& data)
	{
		if (!_pHead->_parent)
		{
			Node* newnode = new Node(data);
			_pHead->_parent = newnode;
			_pHead->_left = _pHead->_right = _pHead->_parent;
			newnode->_parent = _pHead;
			++_size;
			return pair<Iterator, bool>(Iterator(_pHead->_parent), true);
		}
		Node* cur = Find(data);
		if (cur)
			return pair<Iterator, bool>(Iterator(cur), false);
		KeyOfValue kot;
		cur = _pHead->_parent;
		Node* parent = cur->_parent;
		while (cur)
		{
			parent = cur;
			if (kot(cur->_data) < kot(data))
				cur = cur->_right;
			else if (kot(cur->_data) > kot(data))
				cur = cur->_left;
			else
				assert(false);
		}
		Node* newnode = new Node(data);//用newnode记录新增节点方便返回
		cur = newnode;
		cur->_parent = parent;
		if (kot(parent->_data) < kot(data))
			parent->_right = cur;
		else
			parent->_left = cur;
		//更新到根节点为止,当父亲节点为红才需要更新
		while (parent != _pHead && parent->_col == RED)
		{
			Node* gparent = parent->_parent;
			if (gparent->_left == parent)
			{
				Node* uncle = gparent->_right;
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = parent->_col = BLACK;
					gparent->_col = RED;
					cur = gparent;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)
					{
						RotateR(gparent);
						cur->_col = gparent->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateL(parent);
						RotateR(gparent);
						cur->_col = BLACK;
						gparent->_col = parent->_col = RED;
					}
					break;
				}
			}
			else
			{
				Node* uncle = gparent->_left;
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = parent->_col = BLACK;
					gparent->_col = RED;
					cur = gparent;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						RotateL(gparent);
						cur->_col = gparent->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateR(parent);
						RotateL(gparent);
						cur->_col = BLACK;
						gparent->_col = parent->_col = RED;
					}
					break;
				}
			}
		}		
		GetRoot()->_col = BLACK;
		_pHead->_left = LeftMost();
		_pHead->_right = RightMost();
		++_size;
		return pair<Iterator, bool>(Iterator(newnode), true);
	}

最后再写一个判断是否为红黑树的函数,测试一下:

bool isValidRBtree() {
		//直接求其中一条路径中的黑色节点数,用于与其他路径比较
		Node* cur = _pHead->_parent;
		int BlackCount = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
				++BlackCount;
			cur = cur->_left;
		}
		return _isValidRBtree(_pHead->_parent,0,BlackCount);
	}

private:
	bool _isValidRBtree(Node*cur,int curCount,int BlackCount)
	{
		if (cur == nullptr)
			return curCount == BlackCount;
		if (cur->_col == RED && cur->_parent->_col == RED)
			return false;
		if (cur->_col == BLACK)
			++curCount;
		return _isValidRBtree(cur->_left, curCount, BlackCount)
			&& _isValidRBtree(cur->_right, curCount, BlackCount);
	}

测试代码:

#include"rbtree.h"
struct SetKeyOfT
{
	const int& operator()(const int& d) { return d; }
};
int main() 
{
	srand(time(NULL));
	
	RBtree<int, int, SetKeyOfT>rbt1;
	for (int i = 0; i < 100000; i++)
	{
		rbt1.Insert(rand()%10000);
		if (!rbt1.isValidRBtree())
		{
			printf("error while i == %d", i);
			break;
		}
	}
	return 0;
}

测试结果:正常

三.map和set如何封装?

        注意点:

        1.回顾一下节点的定义,存储的T,在set中是K,而在map中是pair<K,V>。

        2.这里的仿函数为同时用封装set和map做的准备,map中插入的是pair,用仿函数取其中的first即K,进行数据的比较逻辑,而set的是K直接返回K自身就可以了。

        3.对于map还要实现operator[],与stl中相同实现,先进行插入逻辑,注意插入的pair的second用的是V()即其默认构造函数,像内置类型也认为是有的,比如int()默认构造的是0。

具体实现:

set:

#pragma once
#include"rbtree.h"

template<class K>
class Set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& d) { return d; }
	};
public:
	typedef typename RBtree<K,K,SetKeyOfT>::Iterator iterator;
	iterator begin() { return _rbtree.Begin(); }
	iterator end() { return _rbtree.End(); }
	pair<iterator, bool>insert(const K& data) { return _rbtree.Insert(data); }
	iterator find(const K& data) { return _rbtree.Find(data); }
private:
	RBtree<K, K, SetKeyOfT>_rbtree;
};

map:

#pragma once
#include"rbtree.h"

template<class K,class V>
class Map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& p) { return p.first; }
	};
public:
	typedef typename RBtree<K, pair<K, V>, MapKeyOfT>::Iterator iterator;
	iterator begin() { return _rbtree.Begin(); }
	iterator end() { return _rbtree.End(); }
	pair<iterator, bool>insert(const pair<K,V>& p) { return _rbtree.Insert(p); }
	iterator find(const K& data) { return _rbtree.Find(data); }
	V& operator[](const K&k)
	{
		pair<iterator,bool> ret = _rbtree.Insert(pair<K, V>(k, V()));
		return ret.first->second;//节点存储的是pair<K,V>p,Find返回的是iterator
	}
private:
	RBtree<K, pair<K,V>, MapKeyOfT>_rbtree;
};

至此map和set实现核心大致如上。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值