使用红黑树封装map和set

Posted Moua

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用红黑树封装map和set相关的知识,希望对你有一定的参考价值。

目录

一、对红黑树进行调整并增加迭代器

1、分析STL中红黑树、set、map源码

2、红黑树迭代器实现

3、模拟实现红黑树(带迭代器)

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

STL详解—— 用一棵红黑树同时封装出map和set

C++红黑树详解并封装实现map和set

C++红黑树详解并封装实现map和set

红黑树来实现map&set

C++map与set模拟实现

C++map与set模拟实现