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

news2024/11/16 13:54:19

文章目录

  • 一、map和set源码剖析
  • 二、红黑树的迭代器
    • 1.begin()与end()
    • 2.operator++()与operator--()
  • 三、set的模拟实现
  • 四、map的模拟实现
  • 五、完整代码实现
    • 1.RBTree.h
    • 2.set.h
    • 3.map.h
    • 5.Test.cpp

一、map和set源码剖析

我们知道,map和set的底层是红黑树,但是我们这里可能有一个疑问,我们知道,set是K模型的容器,而map是KV模型的容器,那么他们为什么能同样使用红黑树呢?我们来看看STL库中源码是怎样实现的

//map
#include <stl_tree.h>
#include <stl_map.h>
#include <stl_multimap.h>

//set
#include <stl_tree.h>
#include <stl_set.h>
#include <stl_multiset.h>

我们可以看到,stll中map和set头文件分别包含了三个头文件,其中stl_tree.h是红黑树,stl_map.h和stl_set.h是map和set,stl_multimap.h和stl_multiset.h是multimap和multiset,所以这就是我们使用multimap和multiset的时候只需要包map和set头文件的原因

下面我们分别看看map和set这两个类:

map:

//stl_map.h
template <typename _Key, typename _Tp, typename _Compare = std::less<_Key>, typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class map
{
public:
    typedef _Key                                          key_type;
    typedef std::pair<const _Key, _Tp>                    value_type;
private:
    typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
        key_compare, _Pair_alloc_type> _Rep_type;
    _Rep_type _M_t;
public:
    pair<iterator, bool> insert(const value_type& __x) { return _M_t._M_insert_unique(__x); }
    size_type erase(const key_type& __x) { return _M_t.erase(__x); }
    iterator find(const key_type& __x) { return _M_t.find(__x); }
};

//stl_set.h
template<typename _Key, typename _Compare = std::less<_Key>, typename _Alloc = std::allocator<_Key> >
class set
{
public:
    typedef _Key     key_type;
    typedef _Key     value_type;
private:
    typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
        key_compare, _Key_alloc_type> _Rep_type;
    _Rep_type _M_t;
public:
    pair<iterator, bool> insert(const value_type& __x)
    {
        std::pair<typename _Rep_type::iterator, bool> __p =
            _M_t._M_insert_unique(__x);
        return std::pair<iterator, bool>(__p.first, __p.second);
    }
    size_type erase(const key_type& __x) { return _M_t.erase(__x); }
    iterator find(const key_type& __x) { return _M_t.find(__x); }
};

我们可以看到,set和map的插入和删除等接口都是封装调用的红黑树的接口,对于map来说,key_type就是K,而value_type是pair类型,它的两个参数分别是模板参数_Key和_Tp,也就是传递给map的模板参数K和V,而对于set来说,key_type是K,而另一个变量value_type也是K,set不是K模型的容器吗,为什么还需要一个value_type变量呢,下面我们看一看红黑树的源码:

//stl_tree.h
//红黑树的基类
struct _Rb_tree_node_base
{
    typedef _Rb_tree_node_base* _Base_ptr;
    typedef const _Rb_tree_node_base* _Const_Base_ptr;

    _Rb_tree_color	_M_color;
    _Base_ptr		_M_parent;
    _Base_ptr		_M_left;
    _Base_ptr		_M_right;
};

//红黑树的节点结构
template<typename _Val>
struct _Rb_tree_node : public _Rb_tree_node_base
{
	typedef _Rb_tree_node<_Val>* _Link_type;
    _Val _M_value_field;
};

//红黑树
template<typename _Key, typename _Val, typename _KeyOfValue, typename _Compare, typename _Alloc = allocator<_Val> >
class _Rb_tree
{
protected:
    typedef _Rb_tree_node_base* 		_Base_ptr;
public:
    typedef _Key 				key_type;
    typedef _Val 				value_type;
    typedef value_type* 			pointer;
    typedef value_type& 			reference;
    typedef _Rb_tree_node<_Val>* 		_Link_type;
};

我们可以看到,红黑树中定义了一个基类_Rb_tree_node_base,基类中定义了节点的颜色和三叉链结构,然后让_Rb_tree_node去继承基类,并在类中增加成员变量_M_value_field,_M_value_field的类型是_val,而这个_val恰好是红黑树的第二个模板参数,也就是map和set传递过来的value_type,如下:

在这里插入图片描述

红黑树作为底层容器,如果是map,则红黑树的模板参数_val会被实例化成pair<K,V>,那么红黑树节点中存储的数据的类型也是pair<K,V>,如果是set,则红黑树的模板参数_val被实例化成K,此时红黑树节点中存储的数据的类型为K。所以红黑树可以根据第二个模板参数_val的实参类型实例化出符合set和map要求的两个类,我们看到这里,可能会有一个疑问,那么为什么还需要传递第一个模板参数呢,这是因为某些函数需要使用key,比如map的find函数只能通过key进程查询,即在map的find中不能够通过_val即pair类型来进行查询。

我们知道怎样使用一棵红黑树来封装map和set之后,我们需要先将自己的红黑树改造一下:

//节点颜色
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)
		: _data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)  //默认给成红色
	{}
};

template<class K, class T>
class RBTree {
	typedef RBTreeNode<T> Node;
public:
	bool insert(const T& data) {}
};

但是我们会发现这里存在一个问题,我们在插入的时候需要比较key的大小来确定插入的位置,在之前我们模拟实现红黑树的时候,我们直接将节点定义成为了pair<K,V>类型,所以我们可以直接取pair->first进行比较,但是现在节点中的数据可能是pair类型的,也有可能是key类型的,所以我们不能单纯的使用data.first,同时由于stl中实现的pair比较函数并不是单纯的比较first,而是先比较first再比较second:

template <class T1, class T2>
bool operator<  (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs)
{ 
    return lhs.first<rhs.first || (!(rhs.first<lhs.first) && lhs.second<rhs.second); 
};

template <class T1, class T2>
bool operator>  (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs) { return rhs<lhs; }

那么这个时候,我们也不能够使用库中提供的默认的比较函数,所以我们只能自己写一个仿函数来完成红黑树节点数据的比较,我们看stl源码中可以发现,stl中也是通过这样的方式来完成节点数据的比较–在红黑树中传递第三个模板参数–_KeyOfValue

所以我们再map和set中写一个仿函数,对于set来说函数返回key即可,map返回pair中第一个数据first即可,代码如下:

map:

//map.h
namespace hdp {
	template<class K, class V>
	class map {
	public:
		struct MapKeyOfT {
			const K& operator()(const pair<const K, V>& kv) {
				return kv.first;
			}
		};

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

set:

//set.h
namespace hdp {
	template<class K>
	class set {
	public:
		struct SetKeyOfT {
			const K& operator()(const K& k) {
				return k;
			}
		};

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

所以这个时候,我们在红黑树实现的时候,在比较节点大小的时候,我们使用该仿函数的对象,来帮助我们完成对节点数据的比较:

/RBTree.h
//map: RBTree<K, pair<K,V>, MapKeyOfT> _t;
//set: RBTree<K, K, SetKeyOfT> _t;
template<class K, class T, class KeyOfT>
class RBTree {
	typedef RBTreeNode<T> Node;
public:
	bool insert(const T& data) {
		//判断根节点是否为空
		if (_root == nullptr) {
			_root = new Node(data);
			_root->_col = BLACK;  //根节点的颜色一定为黑
			return true;
		}

		//寻找插入位置
		KeyOfT kot;  //仿函数对象
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur) {
			if (kot(data) < kot(cur->_data)) {
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(data) > .kot(cur->_data)) {
				parent = cur;
				cur = cur->_right;
			}
			else {
				return false;  //不允许重复节点
			}
		}

		//走到空开始插入,注意这里还需要修改父节点的指向
		//新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
		Node* newNode = new Node(data);
		if (kot(data) < kot(parent->_data))
			parent->_left = newNode;
		else
			parent->_right = newNode;
		newNode->_parent = parent;  //修改父节点指向
        
        //调整节点
    }
private:
    Node* _root;
};

二、红黑树的迭代器

红黑树的迭代器的实现方式和list的实现方式类似,都需要为迭代器单独实现出一个类,在类中重载运算符来实现迭代器的各种行为,operator*()返回节点中的数据,operator->()返回节点的地址,operator!=()用来比较两个迭代器是否相等,此外,为了满足实现普通迭代器和const迭代器两个版本,我们需要增加两个模板参数与 Ref 和Ptr,使其能够返回T&和T*.

1.begin()与end()

STL中规定,迭代器中的begin()和end()是一个左闭右开的区间,即begin()指向第一个数据,而end()指向最后一个数据的下一个位置,对红黑树进行中序遍历之后,我们就可以得到一个有序的序列,因此,begin()可以指向红黑树中最小的节点的位置(最左侧的节点的位置),end()可以放在最大节点(最右侧节点)的下一个位置。

为了使得end()能够指向最右节点的下一个位置,stl源码实现中增加了一个哨兵位的头结点,该节点的左节点指向这棵树的最左节点,也就是begin(),该节点的右节点指向这棵树的最右节点,parent指向nullptr,然后 让根父节点指向它,最右节点的右指针指向它,所以在stl源码的实现中这个哨兵位的头结点就是end():

在这里插入图片描述

我们安装库中实现的方式就需要更新我们实现红黑树的结构,那样会比较的麻烦,这里我们实现简单的方式,将end()直接定义为nullptr

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*> const_iterator;

public:
	iterator begin()
	{
		Node* left = _root;
		while (left->_left)
		{
			left = left->_left;
		}

		return iterator(left);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	const_iterator begin() const
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}

		return const_iterator(left);
	}

	const_iterator end()  const
	{
		return const_iterator(nullptr);
	}
}

2.operator++()与operator–()

map和set的遍历是按照红黑树的中序遍历进行的,红黑树是一棵二叉搜索树,那么红黑树的迭代器++应该是指向中序遍历的下一个节点的位置,而–就是走到中序遍历的上一个节点的位置。

我们在遍历时可以分为两种情况:

1.如果当前节点有右孩子,那么迭代器++就应该移动到右子树的最左节点

2.如果当前节点没有右孩子,则迭代器++应该移动到当前节点为父节点的左孩子的那一个祖先节点

我们以下面的图为例:

在这里插入图片描述

我们假设当前节点为13,由于13有右孩子,那么迭代器++就应该移动到13的右子树的最左节点,即15.辱人格当前节点为6,由于6没有右孩子,所以迭代器++就应该移动到当前节点为父节点的左孩子的那一个祖先节点,也就是8.为什么不是移动到8呢,这是因为中序遍历的遍历顺序是左子树 根 右子树,我们访问6的时候,而6是1的右孩子,那么1一定在6之前就已经访问过了,所以我们需要找到cur==parent->_left的parent节点,也就是8.

迭代器的–和++的过程相反,情况分为下面的两种:

1.如果当前节点有左孩子,那么迭代器–移动到左孩子的最右节点

2.如果当前节点没有左孩子,那么迭代器–就移动到当前节点为父节点的右孩子的那一个祖先节点

假设当前节点为13,那么迭代器–就应该移动到左孩子的最右节点,即11,我们正着中序遍历的时候,是访问了11就访问13,反过来访问了13就访问11,然后我们继续–,此时11没有左孩子,那么就移动到当前节点为父节点的右孩子的祖先节点,即8,我们发现也是正确的

迭代器代码如下:

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	typedef __RBTreeIterator<T, T&, T*> iterator;

	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}


	// 普通迭代器的时候,他是拷贝构造
	// const迭代器的时候,他是构造,支持用普通迭代器构造const迭代器
	__RBTreeIterator(const iterator& it)
		:_node(it._node)
	{}

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

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

	Self& operator++()
	{
		if (_node->_right)
		{
			Node* min = _node->_right;
			//找右树的最小节点
			while (min->_left)
			{
				min = min->_left;
			}

			_node = min;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;

			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			Node* max = _node->_left;

			//找左树的最大节点
			while (max->_right)
			{
				max = max->_right;
			}

			_node = max;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;

			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}


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

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

三、set的模拟实现

我们在模拟实现set的时候,需要在类中定义一个RBTree类型的成员变量,其中模板参数分别为K,K,SetOfT.然后插入查找等成员函数直接调用红黑树的接口即可。但是我们需要注意set迭代器的实现。

我们知道,set中key的值是不能够被修改的,因为这样会破坏二叉搜索树的结构,所以我们的普通迭代器和const迭代器返回的key值都应该是const,否则set中key值就可以被修改。所以我们需要让set的普通迭代器和const迭代器都使用红黑树的const迭代器来进行封装:

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

在这里插入图片描述

在这里插入图片描述

我们发现报错的原因是因为_t.begin()和_t.end() 返回的是红黑树的普通迭代器,而set中用于接受返回值的iterator其本质封装的是红黑树的const迭代器,所以这样报错的原因是将红黑树的普通迭代器赋值给了红黑树的const迭代器

解决这个问题的方式有很多,比如set的begin()和end()调用红黑树的cbegin()和cend(),我们这样参考stl库的方式

iterator begin() const { return _M_t.begin();}
iterator end() const { return _M_t.end();}

我们可以看到,stl库中解决的办法很简单,将begin()和end()修饰为const成员函数即可,这样因为当普通成员函数调用const成员函数时权限会被缩小,也就是说,当begin()和end()变成const成员函数时,_t就只能调用红黑树的const迭代器了,此时iterator就能够正确接受函数的返回值了

set的模拟实现如下:

#include "RBTree.h"

namespace hdp
{
	template<class K>
	class set {
	public:
		//仿函数
		struct SetKeyOfT {
			const K& operator()(const K& k) {
				return k;
			}
		};

		//迭代器
		//typename的作用是告诉编译器这是一个类型,而不是静态变量
		//使用红黑树的const迭代器来封装set的普通迭代器,从而保证K不能被修改
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

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

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

		//std::pair<iterator, bool> insert(const K& key)
        //{
		//	//先用红黑树的普通迭代器接受插入函数的返回值
		//	std::pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> p = _t.insert(key);
		//	//再用普通迭代器构造const迭代器进行返回--红黑树的const迭代器就是set的普通迭代器
		//	return std::pair< iterator, bool>(p.first, p.second);
		//}

		bool insert(const K& key)
        {
			return _t.insert(key);
		}

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

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

四、map的模拟实现

map 和set一样,模拟实现的前面部分直接复用红黑树的接口即可,不同的是,map是KV模型的容器,KV模型的key虽然也不允许被修改,因为这可能会破坏搜索树的结构,但是value是可以进行修改的,所以map的普通迭代器封装红黑树的普通迭代器即可,而不用将其封装为红黑树的const迭代器

此外,我们也不需要担心map的key被修改的问题,因为map在定义红黑树成员函数的时候传递给红黑树T模板参数为pair<const K,V>,他保证了map的key值不能被修改:

typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

//const K保证了map的key不会被修改
RBTree<K, std::pair<const K, V>, MapKeyOfT> _t;

相比于set。map还需要封装operator[](const K& key)函数的实现,我们知道,map的operator[]同时具备插入,查找和修改的功能。此时,我们红黑树的实现中的插入就需要带返回值,返回值为pair<iterator,bool>

pair<iterator, bool> Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(iterator(_root), true);
	}
	else
	{
		KeyOfT kot;
		Node* cur = _root;
		Node* parent = nullptr;

		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}

		cur = new Node(data);
		cur->_col = RED;
		Node* newnode = cur;

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


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

				// 情况一  uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二
					if (cur == parent->_left)
					{
						RotateR(grandfather);

						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					// 情况三
					else
					{
						RotateL(parent);
						RotateR(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// g
					//     p
					// c
					if (cur == parent->_left)
					{
						RotateR(parent);
						RotateL(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					// g
					//    p
					//      c
					else
					{
						RotateL(grandfather);

						parent->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}
		_root->_col = BLACK;

		return make_pair(iterator(newnode), true);
	}
}

此外,我们map和set的插入逻辑也需要进行修改:

set.h

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

map.h

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

V& operator[](const K& key)
{
	pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));

	return ret.first->second;
}

此时,我们运行程序的时候发现,set.h又有报错:

在这里插入图片描述

这个报错是由于红黑树的普通迭代器赋值给const迭代器造成的,insert函数返回的是有红黑树的普通迭代器以及布尔值构成的键值对,但是接收返回值的却是红黑树的const迭代器(set的普通迭代器)以及布尔值构成的键值对

那么我们还能向之前那样通过姐const进行修饰来解决这个问题吗,显然是不行的,这样并不能其任何作用,我们参考源码的做法:

//set的insert
std::pair<iterator, bool> insert(const value_type& __x)
{
    std::pair<typename _Rep_type::iterator, bool> __p = _M_t._M_insert_unique(__x);
    return std::pair<iterator, bool>(__p.first, __p.second);
}

//红黑树的迭代器
template <class value, class Ref, class Ptr>
struct _rb_tree_iterator : public _rb_tree_base_iterator
{
    typedef _rb_tree_iterator<value, value&, value*> iterator;
    typedef _rb_tree_iterator<value, Ref, Ptr> self;
    typedef __rb_tree_node<value>* link_ type;
    _rb_tree_iterator(const iterator& it) { node = it.node; }  //拷贝构造
};

我们可以看到,stl中并不是直接将insert函数的返回值进行返回,而是先将它的返回值赋值给一个键值对,该键值对由红黑树的普通迭代器和一个布尔值构成,然后再用该键值对构造一个红黑树的const迭代器(set的普通迭代器)和一个布尔值组成的键值对进行返回,这样就不会发现类型冲突了

为什么红黑树的普通迭代器能够构造出const迭代器呢?这是由于红黑树实现了一个拷贝构造函数,该函数的参数是一个普通迭代器,而不是Self,这是因为

1.当模板实例化为<T,T&,T*>时,iterator和Self相同,此时这是一个拷贝构造函数

2.当模板实例化为<T,const T&,const T*>时,Self是const迭代器,而iterator是一个普通迭代器,它可以用一个普通迭代器构造出一个const迭代器。

所以我们可以在红黑树迭代器类中的构造函数增加一个拷贝构造函数来实现用普通迭代器来构造一个const迭代器。现在我们参照stl源码中的思路来构造我们的迭代器和set的insert函数:

__RBTreeIterator:

template<class T, class Ref, class Ptr>
struct __RBTreeIterator {
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	typedef __RBTreeIterator<T, T&, T*> iterator;  //普通迭代器
	Node* _node;

	__RBTreeIterator(Node* node)
		: _node(node)
	{}

	//当模板实例化为<T, T&, T*>时,iterator和Self相同,这是一个拷贝构造函数
	//当模板实例化为<T, const T&, const T*>时,Self是const迭代器,而iterator是普通迭代器,此时这是一个构造函数,它可以用普通迭代器构造一个const迭代器
	__RBTreeIterator(const iterator& s)
		: _node(s._node)
	{}
};

set.h

std::pair<iterator, bool> insert(const k& key) {
    //先用红黑树的普通迭代器接受插入函数的返回值
    std::pair<typename rbtree<k, k, setkeyoft>::iterator, bool> p = _t.insert(key);
    //再用普通迭代器构造const迭代器进行返回--红黑树的const迭代器就是set的普通迭代器
    return std::pair< iterator, bool>(p.first, p.second);
}

map的模拟实现如下:

#pragma once

#include "RBTree.h"

namespace hdp
{
	template<class K,class V>
	class 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>::const_iterator 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);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));

			return ret.first->second;
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

五、完整代码实现

1.RBTree.h

#pragma once

//定义颜色
enum Color
{
	RED,
	BLACK,
};

// 定义节点
template<class T>
struct RBTreeNode
{
	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Color _col;

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

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	typedef __RBTreeIterator<T, T&, T*> iterator;

	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}


	// 普通迭代器的时候,他是拷贝构造
	// const迭代器的时候,他是构造,支持用普通迭代器构造const迭代器
	__RBTreeIterator(const iterator& it)
		:_node(it._node)
	{}

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

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

	Self& operator++()
	{
		if (_node->_right)
		{
			Node* min = _node->_right;
			//找右树的最小节点
			while (min->_left)
			{
				min = min->_left;
			}

			_node = min;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;

			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			Node* max = _node->_left;

			//找左树的最大节点
			while (max->_right)
			{
				max = max->_right;
			}

			_node = max;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;

			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}


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

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


// map->RBTree<K, pair<const K, V>, MapKeyOfT> _t;
// set->RBTree<K, K, SetKeyOfT> _t;
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*> const_iterator;

public:
	iterator begin()
	{
		Node* left = _root;
		while (left->_left)
		{
			left = left->_left;
		}

		return iterator(left);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	const_iterator begin() const
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}

		return const_iterator(left);
	}

	const_iterator end()  const
	{
		return const_iterator(nullptr);
	}


	pair<iterator,bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}
		else
		{
			KeyOfT kot;
			Node* cur = _root;
			Node* parent = nullptr;

			while (cur)
			{
				if (kot(cur->_data) < kot(data))
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kot(cur->_data) > kot(data))
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return make_pair(iterator(cur), false);
				}
			}

			cur = new Node(data);
			cur->_col = RED;
			Node* newnode = cur;

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


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

					// 情况一  uncle存在且为红
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;

						cur = grandfather;
						parent = cur->_parent;
					}
					else
					{
						// 情况二
						if (cur == parent->_left)
						{
							RotateR(grandfather);

							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						// 情况三
						else
						{
							RotateL(parent);
							RotateR(grandfather);

							cur->_col = BLACK;
							grandfather->_col = RED;
						}

						break;
					}
				}
				else
				{
					Node* uncle = grandfather->_left;
					if (uncle && uncle->_col == RED)
					{
						uncle->_col = parent->_col = BLACK;
						grandfather->_col = RED;

						cur = grandfather;
						parent = cur->_parent;
					}
					else
					{
						// g
						//     p
						// c
						if (cur == parent->_left)
						{
							RotateR(parent);
							RotateL(grandfather);

							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						// g
						//    p
						//      c
						else
						{
							RotateL(grandfather);

							parent->_col = BLACK;
							grandfather->_col = RED;
						}

						break;
					}
				}
			}
			_root->_col = BLACK;

			return make_pair(iterator(newnode), true);
		}
	}
	void RotateL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;

		parent->_right = SubRL;
		if (SubRL)
			SubRL->_parent = parent;

		Node* ppNode = parent->_parent;

		SubR->_left = parent;
		parent->_parent = SubR;

		if (ppNode == nullptr)
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = SubR;
				SubR->_parent = ppNode;
			}
			else
			{
				ppNode->_right = SubR;
				SubR->_parent = ppNode;
			}
		}
	}

	void RotateR(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;

		parent->_left = SubLR;
		if (SubLR)
			SubLR->_parent = parent;

		Node* ppNode = parent->_parent;
		SubL->_right = parent;
		parent->_parent = SubL;

		if (ppNode == nullptr)
		{
			_root = SubL;
			SubL->_parent = nullptr;
		}
		else
		{
			if (parent == ppNode->_left)
			{
				ppNode->_left = SubL;
			}
			else
			{
				ppNode->_right = SubL;
			}

			SubL->_parent = ppNode;
		}
	}

	void InOrder()
	{
		_InOrder(_root);
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

	bool check(Node* root, int blackNum, int ref)
	{
		if (root == nullptr)
		{
			if (blackNum != ref)
			{
				cout << "违反规则:本条路径的黑色节点的数量跟最左路径不相等" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "违反规则:出现连续红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
			blackNum++;

		return check(root->_left, blackNum, ref)
			&& check(root->_right, blackNum, ref);
	}


	bool IsBalance()
	{
		if (_root == nullptr)
			return true;

		if (_root->_col != BLACK)
			return false;

		int ref = 0;// 统计黑节点的个数
		Node* left = _root;
		while (left)
		{

			if (left->_col == BLACK)
				ref++;

			left = left->_left;
		}

		return check(_root, 0, ref);
	}
private:
	Node* _root = nullptr;
};

2.set.h

#pragma once

#include "T_RBTree.h"

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

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

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

		std::pair<iterator, bool> insert(const K& key) 
		{
			//先用红黑树的普通迭代器接受插入函数的返回值
			std::pair<typename rbtree<K, K, setkeyoft>::iterator, bool> p = _t.insert(key);
			//再用普通迭代器构造const迭代器进行返回--红黑树的const迭代器就是set的普通迭代器
			return std::pair< iterator, bool>(p.first, p.second);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

3.map.h

#pragma once

#include "T_RBTree.h"

namespace hdp
{
	template<class K,class V>
	class 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>::const_iterator 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);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));

			return ret.first->second;
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

5.Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
using namespace std;


#include "T_RBTree.h"
#include "map.h"
#include "set.h"

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

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

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

void test_map()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	hdp::map<int, int> m;
	for (auto e : a)
	{
		m.insert(make_pair(e, e));
	}

	hdp::map<int, int>::iterator it = m.begin();
	while (it != m.end())
	{
		//it->first++;
		it->second++;
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;

	hdp::map<string, int> countMap;
	// 统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "香蕉", "草莓","苹果", "西瓜", "苹果", "苹果", 
		"西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	for (auto& e : arr)
	{
		countMap[e]++;
	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}

int main()
{
	test_set();
	//test_map();
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何用Python快速搭建一个文件传输服务

当我的朋友需要把他电脑上面的文件从他的电脑传递到我电脑上的时候&#xff0c;我只需要启动服务 启动服务&#xff01; 他打开web界面 就能把文件传递到我电脑上&#xff08;还能够实时显示进度&#xff09; 文件就已经在我电脑上的uploads文件夹里面了 项目结构如下 templat…

python浮点运算不准确

1.问题 1.12.2的最后结果并不等于3.3 2. 解决方法 错误示范 引入了Decimal计算&#xff0c;但是计算类型是float型&#xff0c;依然计算不准确 正确解决方法 把类型转化为字符串引入计算

动手实战 | 使用 Transformers 包进行概率时间序列预测

最近使用深度学习进行时间序列预测而不是经典方法涌现出诸多创新。本文将为大家演示一个基于 HuggingFace Transformers 包构建的概率时间序列预测的案例。 概率预测 通常&#xff0c;经典方法针对数据集中的每个时间序列单独拟合。然而&#xff0c;当处理大量时间序列时&…

spring中的扩展点解析以及实践使用

文章目录 1、ApplicationContextInitializer2、BeanDefinitionRegistryPostProcessor3、BeanFactoryPostProcessor4、InstantiationAwareBeanPostProcessor5、SmartInstantiationAwareBeanPostProcessor6、BeanFactoryAware7、ApplicationContextAwareProcessor8、BeanNameAwar…

查找文件所在的具体位置

Linux Command 命令: find – walk a file hierarchy (遍历文件层次结构) ;DESCRIPTION 描述: 在指定目录下查找文件和目录, 可以使用不同的选项来过滤和限制查找的结果 ; Grammar Format $ find <在哪个路径下查找> <可选参数…> 常用选项 -name <pattern>…

【javaEE面试题(四)线程不安全的原因】【1. 修改共享数据 2. 操作不是原子性 3. 内存可见性 4. 代码顺序性】

4. 多线程带来的的风险-线程安全 (重点) 4.1 观察线程不安全 static class Counter {public int count 0;void increase() {count;} } public static void main(String[] args) throws InterruptedException {final Counter counter new Counter();Thread t1 new Thread(()…

VMware16.0安装教程和创建

许可证&#xff1a; ZF3R0-FHED2-M80TY-8QYGC-NPKYFYF390-0HF8P-M81RQ-2DXQE-M2UT6ZF71R-DMX85-08DQY-8YMNC-PPHV8设置网络 添加镜像 下载centos7镜像网址https://mirrors.aliyun.com/centos/7/isos/x86_64/?spma2c6h.25603864.0.0.d7724511YPrZpg win10镜像地址https://ww…

Linux+Docker+Gitlab+Jenkins+虚拟内存

最近想研究一下怎么自动化发布项目,于是找到了gitlab+jenkins这个组合,正好借机也研究一下最近很火的docker技术。本篇共分为五部分,分别为安装要求,内存虚拟化,安装docker,安装gitlab,安装jenkins。 一、 安装要求 1 Docker安装要求: 1.1 操作系统 Docker只支持64…

unittest单元测试

java的单元测试框架Junit和TestNG&#xff0c;python里面也有单元测试框架-unittest,相当于是一个python版的junit。python里面的单元测试框架除了unittest,还有一个pytest框架&#xff0c;但是用的比较少 unittest注意点&#xff1a; 导入unittest模块 类名的第一个字母大写&…

代码随想录算法学习心得 40 | 139. 单词拆分、背包问题总结...

一、单词拆分 链接&#xff1a;力扣 描述&#xff1a;给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 思路如下&…

【Linux | Shell】Linux 安全系统 —— 用户、组、文件权限 - 阅读笔记

目录 一、Linux 的安全性1.1 /etc/passwd 文件1.2 /etc/shadow 文件1.3 添加新用户 —— useradd1.4 删除用户 —— userdel1.5 修改用户 —— usermod、passwd、chpasswd 二、使用 Linux 组2.1 /etc/group 文件2.2 创建新组 —— groupadd2.3 修改组 —— groupmod 三、理解文…

Jenkins可持续集成Python自动化脚本

目录 前言 一、Jenkins搭建在Windows上 二、Jenkins搭建在Linux上 &#x1f381;更多干货 完整版文档下载方式&#xff1a; 本文讲解Jenkins如何每次定时的从SVN服务器上拉取最新的代码并执行本地库里的脚本 前言 1、本地代码库目录F:\5i5jautest内有测试文件all_tests.…

Attention,注意力机制

在机器视觉任务中&#xff0c;每一张图片都有重点区域&#xff0c;而非每一个像素对模型理解图片都同等重要。 在自然语言处理任务中&#xff0c;每一段文字都有重点词语&#xff0c;而非每一个字对模型理解语句都同等重要。 如此&#xff0c;在神经网络模型中引入注意力&#…

做投票小程序线上投票制作制作图片投票链接如何做投票小程序

小程序投票活动如何做&#xff1f;很多企业在运营当中&#xff0c;都会通过投票活动来进行推广&#xff0c;从而达到吸粉、增加用户粘度等效果。而此类投票活动&#xff0c;通过小程序就可以实现&#xff0c;操作简单。 我们现在要以“时尚新态度”为主题进行一次投票活动&…

WiFi 时钟+本地温度

[ WiFi 时钟 ] [ WiFi 天气时钟 ] [ WiFi 时钟本地温度 ] 夏天到了&#xff0c;显示器上放一个时钟&#xff0c;顺便实时测量本地室温&#xff0c;看看空调工作是否正常也算是个实用制作。 用到零件共 4 个&#xff1a; 400孔面包板 &#xff08; 大号…

docker配置nacos

1 拉取nacos镜像并启动 docker pull nacos/nacos-server 2 启动nacos命令 docker run -d --name nacos -p 8848:8848 -e PREFER_HOST_MODEhostname -e MODEstandalone nacos/nacos-server 至此&#xff0c;我们已经可以使用nacos服务&#xff0c;UI地址:http://:8848/nacos …

计算机体系结构基础知识介绍之高级分支预测(二)

一、标记混合预测器 分支预测的目的是根据历史信息来预测分支指令的跳转方向和目标地址&#xff0c;从而提高流水线的效率。不同的分支预测方法有不同的优缺点&#xff0c;因此有人提出了一种将多种预测方法结合起来的方案&#xff0c;混合预测器。这种方案可以根据不同的分支…

适合Docker的场景以及不适合的场景

文章来自&#xff1a;When to use and when not to use Docker和7 Cases When You Should Not Use Docker&#xff0c;以及互联网网上的一些零散内容。这篇文章只是基于我自己的理解&#xff0c;进行简单的概述。 适合的场景 你的团队不是一成不变的。 当有新的成员加入&#…

写作技巧:如何让你的文章充满生命力?

在写作中&#xff0c;让文章充满生命力是很重要的一点。如果一篇文章缺乏生命力&#xff0c;那么读者很难被吸引&#xff0c;也很难从中获得任何收获。那么&#xff0c;如何让你的文章充满生命力呢&#xff1f; 以下是几个建议&#xff1a; 1.真实感情&#xff1a;写作的时候要…

Django_视图中的request对象详解(八)

目录 Request 属性 方法 QueryDict 源码等资料获取方法 Request 当URLconf文件匹配到用户输入的路径后&#xff0c;会调用对应的view函数&#xff0c;并创建一个包含请求元数据的 HttpRequest 对象 传入该函数的第一个参数&#xff0c;常用request表示。 HttpRequest实…