【C++ STL】-- 用一棵红黑树的插入实现同时封装map与set

        用一棵红黑树同时封装map与set的意义:所谓的 “用一棵红黑树同时封装map与set” 只是在程序员的角度,通过一系列手段,以一个红黑树同时满足map与set。但是在编译器的角度,实际上并不是一颗树实现的,程序员所写的只是一份模板,map与set是需要各自根据模板实例化的,底层并不是一颗红黑树。(更加轻松实现,更加便于维护)

红黑树的源代码

(红黑树K模型模拟实现)

【C++ STL】-- 红黑树的插入实现_川入的博客-CSDN博客

(未经map与set改进的K模型红黑树的源代码)

#include<iostream>
#include<assert.h>
using namespace std;
// 节点颜色
enum Color{RED, BLACK};
 
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode {
	RBTreeNode<ValueType>* _right;  // 节点的右孩子
	RBTreeNode<ValueType>* _left;   // 节点的左孩子
	RBTreeNode<ValueType>* _parent; // 节点的双亲
 
	ValueType _data; // 节点的值
	Color _color;
 
	// 构造函数
	RBTreeNode(const ValueType& data = ValueType(), Color color = RED)
		:_right(nullptr), _left(nullptr), _parent(nullptr)
		,_data(data), _color(color)
	{}
};
 
template<class ValueType>
class RBTree {
	typedef RBTreeNode<ValueType> Node;
public:
	bool insert(const ValueType& data)
	{
		// 插入的位置是根节点
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK;
			return true;
		}
 
		Node* parent = nullptr;
		Node* cur = _root;
        // 查找cur插入的位置
		while (cur)
		{
			if (data > cur->_data)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (data < cur->_data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
				return false;
		}
 
		cur = new Node(data);
		cur->_color = RED;
 
		if (cur->_data > parent->_data)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;
 
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather);
			assert(grandfather->_color == BLACK);
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				
				//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;
					//继续向上
					cur = grandfather;
					parent = cur->_parent;
				}
				else// 情况二+三:uncle不存在 + 存在且为黑
				{
					//情况二:右旋 + p变黑,g变红
					//      g
					//   p     u
					// c
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					//情况三:左旋 + 情况二(右旋 + p变黑,g变红)
				    //     g
				    //  p     u
				    //    c
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
 
				//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;
					//继续向上
					cur = grandfather;
					parent = cur->_parent;
				}
				else// 情况二+三:uncle不存在 + 存在且为黑
				{
					//情况二:左旋 + p变黑,g变红
					//      g
					//   u     p
					//           c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					//情况三:右旋 + 情况二(左旋 + p变黑,g变红)
					//      g
					//   u     p
					//       c
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
		}
		_root->_color = BLACK;
		return true;
	}
 
	// 利用递归前序按升序打印红黑树
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
 
	// 检测是否符合红黑树
	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}
 
		// 判断根节点是否为黑色
		if (_root->_color == RED)
		{
			cout << "根节点不黑色" << endl;
			return false;
		}
 
		int benchMark = 0; //某路径的黑节点个数,作为基准值
		return PrevCheck(_root, 0, benchMark);
	}
 
private:
	// 利用深度优先
	bool PrevCheck(Node* root, int blackNum, int& benchMark) //利用引用保存基准值
	{
		if (root == nullptr)
		{
			if (benchMark == 0) //将第一个路径的黑节点个数给benchMark,作为基准值
				benchMark = blackNum;
			else if(blackNum != benchMark)
				return false;
			return true;
		}
 
		if (root->_color == BLACK)
			++blackNum;
 
		// 检查是否有连续的红色节点
		if (root->_color == RED && root->_parent->_color == RED)
		{
			cout << "存在连续的红节点" << endl;
				return false;
		}
 
		return PrevCheck(root->_left, blackNum, benchMark)
			&& PrevCheck(root->_right, blackNum, benchMark);
	}
 
	// 前序递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
 
		_InOrder(root->_left);
		cout << root->_data << " ";
		_InOrder(root->_right);
	}
 
	// 右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
 
		// 将subL连接到parent的左
		parent->_left = subLR;
		if (subLR) // 可能subL的右子树不存在
			subLR->_parent = parent;
 
		Node* pparent = parent->_parent;
 
		// 将parent连接到subL的右节点上,成为subL的右子树
		subL->_right = parent;
		parent->_parent = subL;
 
		// 将parent的父节点状态给予subL
		if (_root == parent) // parent是根
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else // parent不是根
		{
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
	}
 
	// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
 
		// 将subR连接到parent的右
		parent->_right = subRL;
		if (subRL) // 可能subR的左子树不存在
			subRL->_parent = parent;
 
		Node* pparent = parent->_parent;
 
		// 将parent连接到subR的左节点上,成为subR的左子树
		subR->_left = parent;
		parent->_parent = subR;
 
		// 将parent的父节点状态给予subR
		if (_root == parent) // parent是根
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else // parent不是根
		{
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}
	}
 
	Node* _root = nullptr;
};
 
void TestRBTree()
{
	size_t N = 100;
	srand(time(0));
	RBTree<int> t;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand();
		t.insert(x);
	}
 
	t.InOrder();
 
	if (t.IsBalance())
		cout << "是红黑树" << endl;
	else
		cout << "不是红黑树" << endl;
}
 
int main()
{
	TestRBTree();
	return 0;
}

模板参数的改进

        我们知道set是经典的k模型,map是经典的kv模型。那么是不是代表,对于set与map的实现需要分别利用k模型的红黑树和kv模型的红黑树,需要两颗红黑树?

        但是STL其实是非常注意复用性的(如:迭代器的复用 - 普通迭代器与const迭代器、栈队列复用之前的容器、优先级队列复用之前的容器) ,并不会让两份代码,大结构和逻辑几乎都是类似的,只有细节的些许差距,以此写两份,导致代码冗余。

让我们看看源代码对齐的处理:

我们简单的实现一下:

模板参数的再次改进

        在原来的基础上:对于插入点的查找,插入点的插入都是需要利用data数据与比较路径上节点的_data成员的比较:

  • 对于set是没有问题的,set 以RBTree<K, K> _t,将key传给了模板参数T,所以data是key类型的,对于比较是没有问题的。
  • 对于map是有问题的,map 以PBTree<K, pair<K, V>>,将pair<K, V>传给了模板参数T,所以data是pair<K, V>类型的:而pair对于比较的operator重载是:

pair比较重载的相关文档

以 operator> 举例:

        其是 “data1.frist > data2.frist” 或者 “data1.frist < data2.frist 的情况下 data1.second > data2..second” 都是属于data1 > data2。 所以pair的比较重载是不可用的,而由于pair已经提供了比较重载,我们也就没法自己写比较重载。

        基于此基础上,我们就需要增加一个模板参数,一个仿函数。

map与set:

        即:根据仿函数比较的地方我们也需要进行改进(如:insert的插入位置查找):

正向迭代器

        红黑树的正向迭代器,实际上是对结点指针进行了封装,模板化了T,T&,T*,分别用于结点指针,*与->的operator的重载函数的返回值。

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node; //对节点指针进行封装

    //构造函数
	__RBTreeIterator(Node* node)
		:_node(node)
	{}

    // ……
}

template<class K, class T, class KeyOfT>
class RBTree {
public://typedef会受访问限定符干扰	
    typedef __RBTreeIterator<T, T&, T*> iterator;
    // ……
}

当正向迭代器调用*是,重载operator*返回对应数据的引用即可:

Ref operator*() //Pef:T&
{
	return _node->_data;
}

当正向迭代器调用->是,重载operator->返回对应数据的地址即可:

Ptr operator->()
{
	return &_node->_data;
}

对于==!=的运算符重载,只需要判断正向迭代器中封装的,对应节点地址是否相等即可:

bool operator!=(const Self& s)const
{
	return _node != s._node;
}

bool operator==(const Self& s)const
{
	return _node == s._node;
}

        对于正向迭代器的++与--运算符的重载函数,最佳的实现的方式是非递归,而由于此时三叉链的节点(即,同时有:parent,left, right的指针)无需利用栈就可以更加直接的实现:

operator前置++

逻辑如下:

    因为,遍历红黑树的核心是:左子树 根 右子树

  • 右子树不为空,++就是找右子树的中序第一个节点(最左节点):

  • 右子树为空,++就是找孩子不是父亲右的那个祖先父亲节点:

         同时实现了,正向迭代器的结尾是end()时为:nullptr。

Self& operator++()
{
	// 下一个就是:右子树的最左节点
	if (_node->_right)
	{
		Node* left = _node->_right;
		while (left->_left)
			left = left->_left;
		_node = left;
	}
	else
	{
		// 下一个就是:找孩子不是父亲右的那个祖先父亲节点
		Node* root = _node;
		Node* parent = _node->_parent;
		while (parent && root == parent->_right)
		{
			root = root->_parent;
			parent = root->_parent;
		}
		_node = parent;
	}
	return *this;
}

operator前置--

    因为,到过来遍历红黑树的核心是:右子树 根 左子树,所以与前置++的实现原理一样:

  • 左子树不为空,- - 就是找左子树的中序最后一个节点(最右节点)
  • 左子树为空,- - 就是找孩子不是父亲左的那个祖先父亲节点
Self& operator--()
{
	// 上一个就是:左子树的最右节点
	if (_node->_left)
	{
		Node* right = _node->_left;
		while (right->_right)
		{
			right = right->_right;
		}
		_node = right;
	}
	else
	{
		// 上一个就是:找孩子不是父亲左的那个祖先父亲节点
		Node* root = _node; v
			Node* parent = root->_parent;
		while (parent && root == parent->_left)
		{
			root = root->_parent;
			parent = root->_parent;
		}
		_node = parent;
	}
	return *this;
}

insert

        由于我们需要实现operator[],所以insert的返回值需要是pair<iterator, bool>,如果没有就插入,如果已经有返回那个已经在的节点的迭代器:

map.h

pair<iterator, bool>  insert(const pair<K, V>& kv)
{
	return _t.insert(kv);
}

set.h

pair<iterator, bool> insert(const K& key)
{
	return _t.insert(key);
}

RBTree.h

pair<iterator, bool> insert(const T& data)
{
	KeyOfT kot;

	// 插入的位置是根节点
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_color = BLACK;
		return make_pair(iterator(_root), true);
	}

	Node* parent = nullptr;
	Node* cur = _root;
	// 查找cur插入的位置
	while (cur)
	{
		if (kot(data) > kot(cur->_data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kot(data) < kot(cur->_data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else
			return make_pair(iterator(cur), false);
	}

	cur = new Node(data);
	Node* newnode = cur; //用于返回iterator封装新增节点的地址
	cur->_color = RED;

	if (kot(cur->_data) > kot(parent->_data))
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;

	while (parent && parent->_color == RED)
	{
		Node* grandfather = parent->_parent;
		assert(grandfather);
		assert(grandfather->_color == BLACK);
		if (grandfather->_left == parent)
		{
			Node* uncle = grandfather->_right;

			//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
			if (uncle && uncle->_color == RED)
			{
				parent->_color = uncle->_color = BLACK;
				grandfather->_color = RED;
				//继续向上
				cur = grandfather;
				parent = cur->_parent;
			}
			else// 情况二+三:uncle不存在 + 存在且为黑
			{
				//情况二:右旋 + p变黑,g变红
				//      g
				//   p     u
				// c
				if (cur == parent->_left)
				{
					RotateR(grandfather);
					parent->_color = BLACK;
					grandfather->_color = RED;
				}
				//情况三:左旋 + 情况二(右旋 + p变黑,g变红)
				//     g
				//  p     u
				//    c
				else
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_color = BLACK;
					grandfather->_color = RED;
				}
				break;
			}
		}
		else
		{
			Node* uncle = grandfather->_left;

			//情况一:uncle存在且为红,p、u变黑,g变红。继续向上
			if (uncle && uncle->_color == RED)
			{
				parent->_color = uncle->_color = BLACK;
				grandfather->_color = RED;
				//继续向上
				cur = grandfather;
				parent = cur->_parent;
			}
			else// 情况二+三:uncle不存在 + 存在且为黑
			{
				//情况二:左旋 + p变黑,g变红
				//      g
				//   u     p
				//           c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_color = BLACK;
					grandfather->_color = RED;
				}
				//情况三:右旋 + 情况二(左旋 + p变黑,g变红)
				//      g
				//   u     p
				//       c
				else
				{
					RotateR(parent);
					RotateL(grandfather);
					cur->_color = BLACK;
					grandfather->_color = RED;
				}
				break;
			}
		}
	}
	_root->_color = BLACK;
	return make_pair(iterator(newnode), true);
}

map的operator[]

        operator[]就不是在红黑树那层套了,因为只有kv才有方括号,即set没有方括号。红黑树又没有办法确定是k模型,还是kv模型。即map自己套就行了。

V& operator[](const K& key)
{
	pair<iterator, bool> ret = insert(make_pair(key, V())); // V():int就是0、指针就是nullptr、自定义就是默认构造的值
	// 有即返回有的
	// 没有即返回刚插入的
	return ret.first->second;
	//ret->first == pair<iterator, bool>里面第一个元素,迭代器
}

        本质上map与set可以说是什么都没有写,都是下层的封装。map与set空有其表,靠底层的红黑树。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川入

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值