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

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

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

一.红黑树的完成

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

#pragma once
#include 
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类型。
所以我们一开始并不知道树里存的数据是什么类型,那么就用T表示。当传的模板参数是K类型,树里存的就是K类型,当传的模板参数是pair类型,树里存的就是pair类型,所以我们可以通过传模板参数来决定树里存的是什么数据类型。
所以我们改造红黑树里的数据都改成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类型数据取出来。那么我们可以给红黑树增加一个模板参数,给仿函数用。一旦遇到要比较的逻辑时,我们就可以将数据里的K值取出来进行比较。
仿函数实现的原理:当T类型是K类型数据时,直接返回K值即可,当T类型是pair数据时,返回里面的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类型的红黑树。

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类型的
    };//根据传给模板参数T不同的参数,让红黑树来存不同的数据
}

【数据结构】&&【C++】封装红黑树模拟实现map和set容器_第1张图片
【数据结构】&&【C++】封装红黑树模拟实现map和set容器_第2张图片

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

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

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

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

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

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

【数据结构】&&【C++】封装红黑树模拟实现map和set容器_第3张图片

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::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类型的
        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类型,我们不能想set那个让普通迭代器变成const迭代器,因为map要求Value的值还是可以修改的,所以不让pair类型无法修改,而是单纯的让里面的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类型数据来找到Value值。

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

//改造insert,返回值变成pair类型
	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。

【数据结构】&&【C++】封装红黑树模拟实现map和set容器_第4张图片
这样修改是对的吗?有没有什么问题呢?
但是这时会出现一个问题,set里面的insert报错。这是为什么呢?
【数据结构】&&【C++】封装红黑树模拟实现map和set容器_第5张图片
问题在于,我们之前让普通迭代变成const迭代器,而这里的pair中的iterator其实本质上是const_iterator。
是pair类型的。而红黑树里的insert返回的是普通迭代器,也就是pair类型的。这是两个不同的类型,无法直接将pair类型转换成pair类型的。所以会报错。
【数据结构】&&【C++】封装红黑树模拟实现map和set容器_第6张图片
【数据结构】&&【C++】封装红黑树模拟实现map和set容器_第7张图片

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

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

STL库里是支持将普通迭代器转换成const迭代器。那么库里是如何实现的呢?
【数据结构】&&【C++】封装红黑树模拟实现map和set容器_第8张图片

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类型的构造函数,将普通迭代器转换成const迭代器。

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

 //这里的iterator是const_iterator
        pair<iterator,bool> insert(const K& kv)
        {
            //这里insert返回的是树里的普通迭代器_treeIterator 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值。
        }

你可能感兴趣的:(C++(进阶学习),数据结构与算法(进阶学习),数据结构,c++,开发语言)