【C++】基于红黑树封装set和map

头像
🚀个人主页:@小羊
🚀所属专栏:C++
很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~

动图描述

目录

  • 前言
    • 一、更高维度的泛型
    • 二、模版参数
    • 三、比较逻辑的重写
    • 四、迭代器
      • 4.1 const迭代器
      • 4.2 ++重载
      • 4.3 - -重载
    • 五、完整代码


前言

前面我们介绍了set和map的底层细节,在上篇文章中也简单实现了红黑树,而我们知道set和map的底层就是依靠红黑树实现的,那么在本文我们将学习如何基于红黑树来封装出set和map。
本篇文章会带你深入理解C++的三大特性之一——封装。

封装屏蔽底层细节,提供统一访问方式。


一、更高维度的泛型

| 红黑树部分代码:

#pragma once

#include <iostream>
using namespace std;

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv, Colour col = RED)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(col)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree() = default;

	RBTree(const RBTree<K, V>& t)
	{
		_root = _copy(t._root);
	}

	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	RBTree<K, V>& operator=(RBTree<K, V> t)
	{
		swap(_root, t._root);
		return *this;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool Find(const pair<K, V>& kv)
	{
		Node* pcur = _root;
		while (pcur)
		{
			if (kv.first < pcur->_kv.first)
			{
				pcur = pcur->_left;
			}
			else if (kv.first > pcur->_kv.first)
			{
				pcur = pcur->_right;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

	bool Insert(const pair<K, V>& kv)
	{
		Node* pcur = _root;
		Node* parent = nullptr;
		if (pcur == nullptr)//当前还没有节点
		{
			_root = new Node(kv);
			_root->_col = BLACK;//根节点必须是黑色的
			return true;
		}
		while (pcur)
		{
			if (kv.first < pcur->_kv.first)
			{
				parent = pcur;
				pcur = pcur->_left;
			}
			else if (kv.first > pcur->_kv.first)
			{
				parent = pcur;
				pcur = pcur->_right;
			}
			else
			{
				return false;
			}
		}
		pcur = new Node(kv);

		if (kv.first < parent->_kv.first)
		{
			parent->_left = pcur;
		}
		else
		{
			parent->_right = pcur;
		}
		pcur->_parent = parent;

		Node* grandfather = parent->_parent;
		Node* uncle = nullptr;
		//当父节点存在,且颜色为红,需要往上更新
		while (parent && parent->_col == RED)
		{
			if (parent == grandfather->_left)
			{
				uncle = grandfather->_right;
			}
			else
			{
				uncle = grandfather->_left;
			}
			if (uncle && uncle->_col == RED)//情况一:u存在且为红
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;
				if (grandfather->_parent == nullptr)
				{
					grandfather->_col = BLACK;
					return true;
				}
				pcur = grandfather;
				parent = pcur->_parent;
				grandfather = parent->_parent;
			}
			else //情况二:u存在且为黑 / 情况三:u不存在
			{
				if (parent == grandfather->_left 
				&& pcur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else if (parent == grandfather->_left 
				&& pcur == parent->_right)
				{
					RotateL(parent);
					RotateR(grandfather);
					pcur->_col = BLACK;
					grandfather->_col = RED;
				}
				else if (parent == grandfather->_right 
				&& pcur == parent->_left)
				{
					RotateR(parent);
					RotateL(grandfather);
					pcur->_col = BLACK;
					grandfather->_col = RED;
				}
				else if (parent == grandfather->_right 
				&& pcur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		return true;
	}
private:
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		subL->_right = parent;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		Node* parentparent = parent->_parent;
		parent->_parent = subL;
		if (parentparent == nullptr)
		{
			_root = subL;
		}
		else
		{
			if (parent == parentparent->_left)
			{
				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 (parentparent == nullptr)
		{
			_root = subR;
		}
		else
		{
			if (parent == parentparent->_left)
			{
				parentparent->_left = subR;
			}
			else
			{
				parentparent->_right = subR;
			}
		}
		subR->_parent = parentparent;
	}

	Node* _copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* newnode = new Node(root->_kv);
		newnode->_left = copy(root->_left);
		newnode->_right = copy(root->_right);
		return newnode;
	}

	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)//递归一定要有结束条件
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

前面我们实现的红黑树存储的数据是键值对pair<K, V>,现在又需要基于红黑树封装出set和map,而我们知道set中存的是K,map中存的是pair<K, V>,它们数据类型不一样,那我们要拷贝两份红黑树的代码来分别封装set和map吗,如果真是这样那也太挫了。

我们可以想办法只用一个红黑树封装set和map,像这种使用不同类型在同一套代码中操作的需求,我们很容易就想到了模版。值得一说的是,我们实现的红黑树已经是一个类模版,不过这个类模版还不是太灵活,因为它存储类型是K还是pair<K, V>是写死的,并不能够做到完全泛型。

说到这里相信我们都想到了一个解决办法:把用于封装set和map的红黑树存储数据的类型也设计成一个模版参数,在实例化具体的类时根据传过去的值来确定是存储K还是pair<K, V>,而站在set和map层面它们肯定知道自己的数据类型。

RBTree.h:

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

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

template<class K, class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	RBTree() = default;

	RBTree(const RBTree<K, T>& t)
	{
		_root = _copy(t._root);
	}

	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	RBTree<K, T>& operator=(RBTree<K, T> t)
	{
		swap(_root, t._root);
		return *this;
	}
	
	//...

MySet.h:

namespace yjz
{
	template<class K>
	class set
	{
	public:

	private:
		RBTree<K, const K> _t;
	};
}

MyMap.h:

namespace yjz
{
	template<class K, class V>
	class map
	{
	public:

	private:
		RBTree<K, pair<const K, V>> _t;
	};
}

上面我们使红黑树节点模版类的模版参数仅用T来表示,当用set实例化红黑树类模版时,红黑树节点类模版参数T是K类型;当用map实例化红黑树类模版时,红黑树节点类模版参数T是pair<K, V>类型。这里相当于实现了一个更高一点维度的泛型。


二、模版参数

有同学可能注意到了,上面用红黑树封装set和map时传过去了两个参数。一个T类型不是就能确定是K还是pair<K, V>了嘛,为什么还要传过去一个K呢?而且像set传过去两个一模一样的K感觉很奇怪。其实这是为了满足红黑树一些接口的特点而不得不这样做。

bool Find(const K& key)
bool Insert(const T& data)

像上面的查找和插入操作,仅通过一个参数T还解决不了问题。对于插入操作如果是set就插入K,如果是map就插入pair<K, V>;但是对于查找操作不管是set还是map我们只能通过key查找,这是set和map的特点。 对于map来说是通过keyvalue,不能说骑驴找驴。

另外,这里的FindInsert函数的返回值也需要改一改,Find函数应该返回节点的迭代器,Insert函数应该根据插入成功或失败返回相应的pair<iterator, bool>

Iterator Find(const K& key)
{
	KeyOfT kot;
	Node* pcur = _root;
	while (pcur)
	{
		if (key < kot(pcur->_data))
		{
			pcur = pcur->_left;
		}
		else if (key > kot(pcur->_data))
		{
			pcur = pcur->_right;
		}
		else
		{
			return Iterator(pcur, _root);
		}
	}
	return End();
}

pair<Iterator, bool> Insert(const T& data)
{
	KeyOfT kot;
	Node* pcur = _root;
	Node* parent = nullptr;
	if (pcur == nullptr)//当前还没有节点
	{
		_root = new Node(data);
		_root->_col = BLACK;//根节点必须是黑色的
		return make_pair(Iterator(_root, _root), true);
	}
	while (pcur)
	{
		if (kot(data) < kot(pcur->_data))
		{
			parent = pcur;
			pcur = pcur->_left;
		}
		else if (kot(data) > kot(pcur->_data))
		{
			parent = pcur;
			pcur = pcur->_right;
		}
		else
		{
			return make_pair(Iterator(pcur, _root), false);
		}
	}
	pcur = new Node(data);
	Node* newnode = pcur;//记录新节点指针,旋转可能会更新pcur

	if (kot(data) < kot(parent->_data))
	{
		parent->_left = pcur;
	}
	else
	{
		parent->_right = pcur;
	}
	pcur->_parent = parent;

	Node* grandfather = parent->_parent;
	Node* uncle = nullptr;
	//当父节点存在,且颜色为红,需要往上更新
	while (parent && parent->_col == RED)
	{
		if (parent == grandfather->_left)
		{
			uncle = grandfather->_right;
		}
		else
		{
			uncle = grandfather->_left;
		}
		if (uncle && uncle->_col == RED)//情况一:u存在且为红
		{
			parent->_col = BLACK;
			uncle->_col = BLACK;
			grandfather->_col = RED;
			if (grandfather->_parent == nullptr)
			{
				grandfather->_col = BLACK;
				return make_pair(Iterator(newnode, _root), true);
			}
			pcur = grandfather;
			parent = pcur->_parent;
			grandfather = parent->_parent;
		}
		else //情况二:u存在且为黑 / 情况三:u不存在
		{
			if (parent == grandfather->_left && pcur == parent->_left)
			{
				RotateR(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else if (parent == grandfather->_left && pcur == parent->_right)
			{
				RotateL(parent);
				RotateR(grandfather);
				pcur->_col = BLACK;
				grandfather->_col = RED;
			}
			else if (parent == grandfather->_right && pcur == parent->_left)
			{
				RotateR(parent);
				RotateL(grandfather);
				pcur->_col = BLACK;
				grandfather->_col = RED;
			}
			else if (parent == grandfather->_right && pcur == parent->_right)
			{
				RotateL(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			break;
		}
	}
	return make_pair(Iterator(newnode, _root), true);
}

三、比较逻辑的重写

当我们修改了红黑树的模版参数,那它的底层实现也要做相应的修改。
例如,插入操作的部分代码:

//...
	while (pcur)
	{
		if (data < pcur->data)
		{
			parent = pcur;
			pcur = pcur->_left;
		}
		else if (data > pcur->data)
		{
			parent = pcur;
			pcur = pcur->_right;
		}
		else
		{
			return false;
		}
	}
//...

同学们想一下,这里还能用data < pcur->data这种直接比较的方式吗?
当然是不可以的。如果是set还好说,keykey比较没问题;但是map就有问题了,因为我们知道pair<K, V>不是单纯的依据first比较的,来看下图中pair<K, V>的比较方式:

在这里插入图片描述

从上图我们可以看到second也加入了比较逻辑,这和我们期望的只依靠first比较是不符合的。
可能有同学想到了用仿函数,但是这里普通的仿函数还解决不了问题,因为仿函数的作用是让我们可以灵活的定制实际需求,而这里的问题是不能知道到底是K还是pair<K, V>

这里我们就要想,我们期望的是:如果是set,就用key比较key;如果是map,就用first比较first。而站在红黑树的角度它是不知道接下来要实例化它的到底是set还是map的,那谁知道呢?当然是set和map他们自己知道嘛。
所以我们可以在set和map实例化红黑树时前,用仿函数提前把keyfirst取出来,这样在红黑树中比较时,通过回调的方式就能拿到我们想要的值。

这里我们再来回顾一下之前学的回调函数:回调函数不是由该函数的实现方直接调用,而是在待定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

MySet.h:

namespace yjz
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
	
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

MyMap.h:

namespace yjz
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
	
	private:
		RBTree<K, pair<K, V>, MapKeyOfT> _t;
	};
}

RBTree.h:

bool Insert(const T& data)
{
	KeyOfT kot;
	Node* pcur = _root;
	Node* parent = nullptr;
	if (pcur == nullptr)//当前还没有节点
	{
		_root = new Node(data);
		_root->_col = BLACK;//根节点必须是黑色的
		return true;
	}
	while (pcur)
	{
		if (kot(data) < kot(pcur->_data))
		{
			parent = pcur;
			pcur = pcur->_left;
		}
		else if (kot(data) > kot(pcur->_data))
		{
			parent = pcur;
			pcur = pcur->_right;
		}
		else
		{
			return false;
		}
	}
	pcur = new Node(data);

	//...

四、迭代器

在这里插入图片描述

set和map的迭代器都是双向迭代器,既可以通过++操作符前进到下一个元素,也可以通过- -操作符回退到前一个元素。因为set和map的底层红黑树是二叉树,不能完成简单的++和- -,所以需要重载运算符。
这里我们先实现一下简单的构造、beginend,以及*->==!=的重载,最后再探讨++和- -的重载如何实现。

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;
	Node* _root;

	RBTreeIterator(Node* node, Node* root)
		:_node(node)
		,_root(root)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

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

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

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

beginend分别返回红黑树最左边节点的迭代器和最右边节点下一个空节点的迭代器。这里我们就暂且让end返回nullptr。(C++标准库中是借助哨兵节点解决end的。)
在这里插入图片描述

Iterator Begin()
{
	Node* leftMost = _root;
	while (leftMost && leftMost->_left)
	{
		leftMost = leftMost->_left;
	}
	return Iterator(leftMost, _root);
}

Iterator End()
{
	return Iterator(nullptr, _root);
}

4.1 const迭代器

除了普通迭代器,还需要const迭代器,const迭代器相较于普通迭代器主要是*->的重载,为了复用普通迭代器,可以增加模版参数来提高泛型,这个在实现list迭代器中也有使用。
另外还有,set的key是不能修改的,map的key也不能修改,但value是可以修改的。既然如此,我们在pair<K, V>中K前面加上const,就能巧妙的解决这个问题。对于set也可以和map保持一致的做法。

MySet.h:

template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
	typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

	iterator begin()
	{
		return _t.Begin();
	}

	iterator end()
	{
		return _t.End();
	}
	 
	const_iterator begin() const
	{
		return _t.Begin();
	}

	const_iterator end() const
	{
		return _t.End();
	}

private:
	RBTree<K, const K, SetKeyOfT> _t;
};

MyMap.h:

template<class K, class V>
struct map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;
	iterator begin()
	{
		return _t.Begin();
	}

	iterator end()
	{
		return _t.End();
	}

	const_iterator begin() const
	{
		return _t.Begin();
	}

	const_iterator end() const
	{
		return _t.End();
	}

private:
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

RBTree.h:

template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T, T&, T*> Iterator;
	typedef RBTreeIterator<T, const T&, const T*> ConstIterator;

	Iterator Begin()
	{
		Node* leftMost = _root;
		while (leftMost && leftMost->_left)
		{
			leftMost = leftMost->_left;
		}
		return Iterator(leftMost, _root);
	}
	
	Iterator End()
	{
		return Iterator(nullptr, _root);
	}
	
	ConstIterator Begin() const
	{
		Node* leftMost = _root;
		while (leftMost && leftMost->_left)
		{
			leftMost = leftMost->_left;
		}
		return ConstIterator(leftMost, _root);
	}
	
	ConstIterator End() const
	{
		return ConstIterator(nullptr, _root);
	}

	RBTree() = default;

	RBTree(const RBTree<K, T, KeyOfT>& t)
	{
		_root = _copy(t._root);
	}

	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	//...

4.2 ++重载

最后到了相对麻烦一点的++和- -重载,我们知道红黑树的遍历走的是中序,中序遍历是左子树、根节点、右子树,那如果通过++走到中序的下一个节点呢?

在这里插入图片描述

it当前指向8这个节点,按照中序遍历++应该指向11这个节点,因为左、根、右,所以++我们应该找当前节点的右节点,那么这时候就有两种情况,即右节点为空和右节点不为空。

上图情况一是右节点不为空,当右节点不为空时中序遍历的下一个节点并不一定就是当前节点的右孩子,而是当前节点右子树的最左节点。
情况二是右节点为空,右节点为空说明当前节点所在子树遍历完了,那当前节点所在子树遍历完了中序遍历下一个节点就是它的父亲吗?并不一定。接下来我们应该判断当前节点是它父亲的左孩子还是右孩子,如果是它父亲的右孩子,则说明它父亲所在子树也遍历完了,需要继续往上更新再做判断;如果是它父亲的左孩子,则下一个节点就是它的父亲。

Self& operator++()
{
	//1.当前节点右子树不为空
	if (_node->_right)
	{
		Node* leftMost = _node->_right;
		while (leftMost && leftMost->_left)
		{
			leftMost = leftMost->_left;
		}
		_node = leftMost;
	}
	else//2.当前节点右子树为空(当前节点所在子树访问完了)
	{
		Node* pcur = _node;
		Node* parent = pcur->_parent;
		while (parent && pcur == parent->_right)
		{
			pcur = parent;
			parent = pcur->_parent;
		}
		_node = parent;
	}
	return *this;
}

4.3 - -重载

在这里插入图片描述
重载- -运算符和++类似,就是反过来而已。
假设当前节点的迭代器it,- -应该得到的是它的左孩子的迭代器,那这个时候也有两种情况,左孩子为空和左孩子不为空。
上图情况一是左孩子不为空,那- -得到的应该是其左子树的最右节点的迭代器;情况二是左孩子为空,左孩子为空说明当前节点所在子树遍历完了,如果当前节点是其父亲的左孩子,那说明其父亲所在子树也遍历完了,需要向上更新;如果当前节点是其父亲的右孩子,则- -得到的就是它的父亲。

值得注意的是,前面我们实现的end返回的是nullptr,而end返回的迭代器- -是要得到二叉树最右节点的迭代器的,显然我们这里的- -并不能实现,那我们就对这种情况做特殊处理。
如果当前指针为空,我们就通过_root来找到红黑树的最右节点。(这也是最开始在迭代器增加_root成员的原因。)

Self& operator--()
{
	//当迭代器是end()时特殊处理
	if (_node == nullptr)
	{
		Node* rightMost = _root;
		while (rightMost && rightMost->_right)
		{
			rightMost = rightMost->_right;
		}
		_node = rightMost;
	}
	//1.左子树不为空,找左子树最右节点
	else if (_node->_left)
	{
		Node* rightMost = _node->_left;
		while (rightMost && rightMost->_right)
		{
			rightMost = rightMost->_right;
		}
		_node = rightMost;
	}
	else//2.左子树为空,当前节点所在子树访问完了
	{
		Node* pcur = _node;
		Node* parent = pcur->_parent;
		while (parent && pcur == parent->_left)
		{
			pcur = parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

五、完整代码

RBTree.h:

#pragma once

#include <iostream>
using namespace std;

enum Colour
{
	RED,
	BLACK
};

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

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

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;
	Node* _root;

	RBTreeIterator(Node* node, Node* root)
		:_node(node)
		,_root(root)
	{}

	Self& operator++()
	{
		//1.当前节点右子树不为空
		if (_node->_right)
		{
			Node* leftMost = _node->_right;
			while (leftMost && leftMost->_left)
			{
				leftMost = leftMost->_left;
			}
			_node = leftMost;
		}
		else//2.当前节点右子树为空(当前节点所在子树访问完了)
		{
			Node* pcur = _node;
			Node* parent = pcur->_parent;
			while (parent && pcur == parent->_right)
			{
				pcur = parent;
				parent = pcur->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Self& operator--()
	{
		//当迭代器是end()时特殊处理
		if (_node == nullptr)
		{
			Node* rightMost = _root;
			while (rightMost && rightMost->_right)
			{
				rightMost = rightMost->_right;
			}
			_node = rightMost;
		}
		//1.左子树不为空,找左子树最右节点
		else if (_node->_left)
		{
			Node* rightMost = _node->_left;
			while (rightMost && rightMost->_right)
			{
				rightMost = rightMost->_right;
			}
			_node = rightMost;
		}
		else//2.左子树为空,当前节点所在子树访问完了
		{
			Node* pcur = _node;
			Node* parent = pcur->_parent;
			while (parent && pcur == parent->_left)
			{
				pcur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Ref operator*()
	{
		return _node->_data;
	}

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

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

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

template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T, T&, T*> Iterator;
	typedef RBTreeIterator<T, const T&, const T*> ConstIterator;

	RBTree() = default;

	RBTree(const RBTree<K, T, KeyOfT>& t)
	{
		_root = _copy(t._root);
	}

	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
	{
		swap(_root, t._root);
		return *this;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	Iterator Begin()
	{
		Node* leftMost = _root;
		while (leftMost && leftMost->_left)
		{
			leftMost = leftMost->_left;
		}
		return Iterator(leftMost, _root);
	}

	Iterator End()
	{
		return Iterator(nullptr, _root);
	}

	ConstIterator Begin() const
	{
		Node* leftMost = _root;
		while (leftMost && leftMost->_left)
		{
			leftMost = leftMost->_left;
		}
		return ConstIterator(leftMost, _root);
	}

	ConstIterator End() const
	{
		return ConstIterator(nullptr, _root);
	}

	Iterator Find(const K& key)
	{
		KeyOfT kot;
		Node* pcur = _root;
		while (pcur)
		{
			if (key < kot(pcur->_data))
			{
				pcur = pcur->_left;
			}
			else if (key > kot(pcur->_data))
			{
				pcur = pcur->_right;
			}
			else
			{
				return Iterator(pcur, _root);
			}
		}
		return End();
	}

	pair<Iterator, bool> Insert(const T& data)
	{
		KeyOfT kot;
		Node* pcur = _root;
		Node* parent = nullptr;
		if (pcur == nullptr)//当前还没有节点
		{
			_root = new Node(data);
			_root->_col = BLACK;//根节点必须是黑色的
			return make_pair(Iterator(_root, _root), true);
		}
		while (pcur)
		{
			if (kot(data) < kot(pcur->_data))
			{
				parent = pcur;
				pcur = pcur->_left;
			}
			else if (kot(data) > kot(pcur->_data))
			{
				parent = pcur;
				pcur = pcur->_right;
			}
			else
			{
				return make_pair(Iterator(pcur, _root), false);
			}
		}
		pcur = new Node(data);
		Node* newnode = pcur;//记录新节点指针,旋转可能会更新pcur

		if (kot(data) < kot(parent->_data))
		{
			parent->_left = pcur;
		}
		else
		{
			parent->_right = pcur;
		}
		pcur->_parent = parent;

		Node* grandfather = parent->_parent;
		Node* uncle = nullptr;
		//当父节点存在,且颜色为红,需要往上更新
		while (parent && parent->_col == RED)
		{
			if (parent == grandfather->_left)
			{
				uncle = grandfather->_right;
			}
			else
			{
				uncle = grandfather->_left;
			}
			if (uncle && uncle->_col == RED)//情况一:u存在且为红
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;
				if (grandfather->_parent == nullptr)
				{
					grandfather->_col = BLACK;
					return make_pair(Iterator(newnode, _root), true);
				}
				pcur = grandfather;
				parent = pcur->_parent;
				grandfather = parent->_parent;
			}
			else //情况二:u存在且为黑 / 情况三:u不存在
			{
				if (parent == grandfather->_left && pcur == parent->_left)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else if (parent == grandfather->_left && pcur == parent->_right)
				{
					RotateL(parent);
					RotateR(grandfather);
					pcur->_col = BLACK;
					grandfather->_col = RED;
				}
				else if (parent == grandfather->_right && pcur == parent->_left)
				{
					RotateR(parent);
					RotateL(grandfather);
					pcur->_col = BLACK;
					grandfather->_col = RED;
				}
				else if (parent == grandfather->_right && pcur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		return make_pair(Iterator(newnode, _root), true);
	}

private:
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		subL->_right = parent;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		Node* parentparent = parent->_parent;
		parent->_parent = subL;
		if (parentparent == nullptr)
		{
			_root = subL;
		}
		else
		{
			if (parent == parentparent->_left)
			{
				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 (parentparent == nullptr)
		{
			_root = subR;
		}
		else
		{
			if (parent == parentparent->_left)
			{
				parentparent->_left = subR;
			}
			else
			{
				parentparent->_right = subR;
			}
		}
		subR->_parent = parentparent;
	}

	Node* _copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		Node* newnode = new Node(root->_kv);
		newnode->_left = copy(root->_left);
		newnode->_right = copy(root->_right);
		return newnode;
	}

	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)//递归一定要有结束条件
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};

MySet.h:

#pragma once

#include "RBTree.h"

namespace yjz
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

		iterator begin()
		{
			return _t.Begin();
		}

		iterator end()
		{
			return _t.End();
		}
		 
		const_iterator begin() const
		{
			return _t.Begin();
		}

		const_iterator end() const
		{
			return _t.End();
		}

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

		iterator find(const K& key)
		{
			return _t.Find(key);
		}
	private:
		RBTree<K, const K, SetKeyOfT> _t;
	};

	void Print(const set<int>& s)
	{
		set<int>::iterator it = s.end();
		while (it != s.begin())
		{
			--it;
			cout << *it << " ";
		}
		cout << endl;
	}

	void test_set()
	{
		set<int> s;
		int a[] = { 4,2,6,1,3,5,15,7,16,14 };
		for (auto e : a)
		{
			s.insert(e);
		}

		for (auto e : s)
		{
			cout << e << " ";
		}
		cout << endl;

		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		set<int>::iterator It = s.end();
		while (It != s.begin())
		{
			--It;
			cout << *It << " ";
		}
		cout << endl;

		Print(s);
	}
}

MyMap.h:

#pragma once

#include "RBTree.h"

namespace yjz
{
	template<class K, class V>
	struct map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;
		iterator begin()
		{
			return _t.Begin();
		}

		iterator end()
		{
			return _t.End();
		}

		const_iterator begin() const
		{
			return _t.Begin();
		}

		const_iterator end() const
		{
			return _t.End();
		}

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

		iterator find(const K& key)
		{
			return _t.Find(key);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};

	void test_map()
	{
		map<string, string> m;
		m.insert({ "front", "前" });
		m.insert({ "behind", "后" });
		m.insert({ "left", "左" });
		m.insert({ "right", "右" });

		map<string, string>::iterator it = m.begin();
		m[it->first] = "前面";
		m["sort"] = "排序";
		m["string"];
		while (it != m.end())
		{
			//it->first += 'x';
			it->second += 'x';
			cout << it->first << "->" << it->second << endl;
			++it;
		}
		cout << endl;
	}
}

本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~

头像

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/890018.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

为什么很多人宁愿加钱买港版,也不愿买国行 iPhone 16

最近的 iPhone 16 市场&#xff0c;真的是倒反天罡&#xff0c;攻守异形啊。 过去&#xff0c;港版 iPhone 都是性价比的次选&#xff0c;便宜个 10% 都得考虑考虑。但今年&#xff0c;港版 iPhone 16 的价格&#xff0c;反而比国行还贵。 比如&#xff0c;闲鱼上某个卖家&am…

[红队apt]文件捆绑攻击流程

免责声明:本文用于了解攻击者攻击手法&#xff0c;切勿用于不法用途 前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理黑客通过文件捆绑进行攻击的流程思路 文件捆绑原理 废话只多说这一句。 1.exe和2.exe被你捆绑为3.exe。 那么你点击了3.exe就等于点…

kafka消息队列核心内容及常见问题

目录 1. 使用消息队列的目的&#xff08;优点与缺点&#xff09; 2. 常见各消息队列对比 3. kafka介绍 3.1 kafka简介 3.2 kafka特点 3.3 kafka系统架构 3.4 设置数据可靠性 3.4.1 Topic 分区副本 3.4.2 消息确认机制 4. 常见问题&#xff08;面试题&#xff09; 4.…

Acwing 排序

1.快速排序 主要思想&#xff1a;基于分治思想。通过选择一个基准元素&#xff0c;将数组分为两部分&#xff0c;左边部分元素都小于等于基准&#xff0c;右边部分元素都大于等于基准。然后对这两部分分别递归地进行排序。 分区逻辑&#xff1a;双指针算法 左指针i从左往右找…

《RabbitMQ篇》消息应答和发布确认

消息应答 消息应答机制&#xff1a;消费者接收信息并处理完之后&#xff0c;告诉rabbitmq该信息已经处理&#xff0c;rabbitmq可以把该信息删除了. 消息自动重新入队&#xff1a;如果处理某个消息的消费者异常关闭了&#xff0c;没有发送ACK确认&#xff0c;rabbitmq会将其重…

金九银十软件测试面试题(800道)

今年你的目标是拿下大厂offer&#xff1f;还是多少万年薪&#xff1f;其实这些都离不开日积月累的过程。 为此我特意整理出一份&#xff08;超详细笔记/面试题&#xff09;它几乎涵盖了所有的测试开发技术栈&#xff0c;非常珍贵&#xff0c;人手一份 肝完进大厂 妥妥的&#…

postgresql 安装

一、下载 PostgreSQL: File Browser 下载地址 PostgreSQL: File Browser 上传到服务器,并解压 二、安装依赖 yum install -y perl-ExtUtils-Embed readline-devel zlib-devel pam-devel libxml2-devel libxslt-devel openldap-devel 创建postgresql 和目录 useradd …

Java-基础

1. 导入模块不能纯粹的复制粘贴&#xff0c;要从new里导入&#xff0c;因为前者建立不了关联 2. 数组 String[] name{"张三","李四","王五"};int[] numsnew int[]{1,2,3};//二维String[][] names{{"张三","李四"},{"…

39 C 语言枚举类型、枚举常量、枚举变量、枚举的遍历、枚举数组、枚举与 switch

目录 1 什么是枚举 2 定义枚举类型 2.1 语法格式 2.2 枚举元素的特点 2.3 案例演示 3 枚举变量 3.1 什么是枚举变量 3.2 定义枚举变量的多种方式 3.3 案例演示 1&#xff1a;标准版枚举类型 3.4 案例演示 2&#xff1a;简化版枚举类型 3.5 案例演示 3&#xff1a;匿…

uniapp学习(005-2 详解Part.2)

零基础入门uniapp Vue3组合式API版本到咸虾米壁纸项目实战&#xff0c;开发打包微信小程序、抖音小程序、H5、安卓APP客户端等 总时长 23:40:00 共116P 此文章包含第41p-第p51的内容 文章目录 mainifest.json文件配置获取微信小程序appid注册微信小程序微信小程序控制台图形界…

22. 括号生成【回溯】

文章目录 22. 括号生成解题思路Go代码 22. 括号生成 22. 括号生成 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["((()))",&quo…

node.js服务器基础

node.js的事件循环 node.js是基于事件驱动的&#xff0c;通常在代码中注册想要等待的事件&#xff0c;设定好回调函数&#xff0c;当事件触发的时候就会调用回调函数。如果node.js没有要处理的事件了&#xff0c;那整个就结束了;事件里面可以继续插入事件&#xff0c;如果有事…

手撕数据结构 —— 带头双向循环链表(C语言讲解)

目录 0.前言 1.什么是带头双向循环链表 理解带头 ​编辑 理解双向 理解循环 2.带头双向循环链表的实现 List.h文件中接口总览 具体实现 结点的定义 申请结点 初始化 打印链表 尾插 尾删 头插 头删 ​编辑​编辑 获取大小 查找 在指定位置前插入 ​编辑…

数据结构--线性表双向链表的实现

目录 思路设计 总体思维导图 插入部分 头插法尾插法 任意位置插入 删除部分 头结点 尾节点 中间节点 只有头结点且删除的就是头结点 ​编辑 清空链表部分 遍历清空链表的所有节点 不遍历清空 各部分代码 Main部分 MyListedList部分 IndexOutOfException部分 …

YOLO11改进 | 注意力机制 | 结合静态和动态上下文信息的注意力机制

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 上下文Transformer&#xff08;CoT&…

WPF中的布局

布局原则 1、不显式设置元素大小。 2、不使用绝对定位。 元素应该根据容器的内容来进行排列。绝对定位在开发前期会带来一些便捷&#xff0c;但扩展性比较差。一旦显示器尺寸或分辨率发生改变&#xff0c;界面的显示效果可能会达不到预期的效果。 3、布局容器可以嵌套使用 常…

【Axure原型分享】标签管理列表

今天和大家分享通过标签管理列表的原型模板&#xff0c;包括增删改查搜索筛选排序分页翻页等效果&#xff0c;这个模板是用中继器制作的&#xff0c;所以使用也很方便&#xff0c;初始数据我们只要在中继器表格里填写即可&#xff0c;具体效果可以观看下方视频或者打开预览地址…

【经管】上市公司供应链金融数据(1990-2023年)

上市公司供应链金融是指上市公司利用其产业链核心地位&#xff0c;通过整合金融资源&#xff0c;为供应链上下游企业提供包括融资、结算、风险管理等在内的综合金融服务。为了衡量上市公司的供应链金融水平&#xff0c;参考周兰等&#xff08;2022&#xff09;的研究方法&#…

【C++入门篇 - 3】:从C到C++第二篇

文章目录 从C到C第二篇new和delete命名空间命名空间的访问 cin和coutstring的基本使用 从C到C第二篇 new和delete 在C中用来向系统申请堆区的内存空间 New的作用相当于C语言中的malloc Delete的作用相当于C语言中的free 注意&#xff1a;在C语言中&#xff0c;如果内存不够…

IBM Flex System服务器硬件监控指标解读

随着企业IT架构的日益复杂&#xff0c;服务器的稳定运行对于保障业务连续性至关重要。IBM Flex System作为一款模块化、可扩展的服务器解决方案&#xff0c;广泛应用于各种企业级环境中。为了确保IBM Flex System服务器的稳定运行&#xff0c;监控易作为一款专业的IT基础设施监…