C++map与set模拟实现

在之前的博客中我们提到了map与set在stl的源码中都是由同一棵红黑树来实现,而我们也模拟stl中的实现方式来分别实现map的K-V与set的K模型。
其中未封装红黑树代码如下:红黑树代码自提链接

红黑树的改造

由于我们需要用同一颗红黑树来实现map的K-V与set的K模型,并且我们的红黑树默认实现的K-V的模型,为了同样适应K的模型,我们需要对红黑树进行一些调整。

  1. 首先我们将红黑树第二个模板参数类型的名字改为T,这是为了与之前的K-V模型进行区分。
    并且这个T就不再是之前单单的表示Value了,这个T可能代表的是Key,也有可能是pair<Key,Value>共同构成的键值对。
template<class K, class T>
class RBTree
{
	...
private:
	node* m_root;	//根结点
};
  1. 其次是需要控制map和set传入底层红黑树的模板参数,即map传给RBTree的T模板参数传入pair<K, V>。
template<class K, class V>
class map
{
public:
	...
private:
	RBTree<K, pair<K, V>> m_tree;
};

而set传给RBTree的T模板参数传入K。

template<class K>
class set
{
public:
	...
private:
	RBTree<K, K> m_tree;
};
可能有人会问能不能不要红黑树的第一个模板参数,只保留第二个模板参数呢?

答:不能。
当是set的时候,K和T都代表键值Key,去掉第一个模板参数是可以的。
但如果是map时,K代表键值Key,T代表由Key和Value构成的键值对。如果去掉第一个参数的话,那么就无法直到
Key的类型。

红黑树结点改造

由于我们知道红黑树的第二个参数现在表示为存储的数据类型,那么树结点模板参数与存储的值也要相应改变,模板参数由原来的K和V改成T,让这个T类型的参数来表示结点存储值的类型,即现在的结点数据由从前的pair<K,V> m_kv改变为 T m_data。

具体层次关系如下所示:
在这里插入图片描述
结点修改后的代码如下所示:

//节点的颜色
enum class Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode	//三叉链
{
	RBTreeNode<T>* m_left;	//左节点
	RBTreeNode<T>* m_right;   //右节点
	RBTreeNode<T>* m_parent;  //节点的父结点(红黑树需要 旋转 ,为了实现简单给出的该字段)

	T m_data; //节点数据
	Colour m_c;	//节点颜色


	RBTreeNode(const T& data)
		: m_left(nullptr), m_right(nullptr), m_parent(nullptr)
		, m_data(data),m_c(Colour::RED)
	{}
};

红黑树模板参数增加仿函数类型

现在由于结点当中存储的是T类型,并且这个T类型可能是Key值,也可能是<Key, Value>键值对。
那么当我们需要利用T这个值进行结点的键值比较时,应该如何获取结点的键值呢?

答:我们可以再给红黑树中再增加一个模板参数,通过上层map与set传入仿函数给RBTree的第三个仿函数类型,
我就可以利用仿函数对象来调用重载的operator(),来获取到对应的Key值。

在这里插入图片描述

由于我们知道底层的红黑树它并不知道上层容器现在是map还是set,因此当需要进行两个结点键值的比较时,底层的红黑树都会通过传入的仿函数来获取键值Key,进而进行两个结点键值的比较。

	template<classs K, class V>
	class map
	{
	public:
		class MapKeyOfCompare    //内部类
		{
		public:
			const K& operator()(const pair<K,V>&kv)
			{
				return kv.first;
			}
		};
		...
	private:
		RBTree <K, pair<K, V>, MapKeyOfCompare>m_tree;
	};

但是对于set容器来说,由于set是Key的模型,当set传给RBTree这个类时,其实可以不用传仿函数,直接进行比较,但为了与map使用同一颗封装好的红黑树,这里我们也与map一样统一传入仿函数模板参数。

	template<class K>
	class set
	{
	public:
		class SetKeyOfCompare   //内部类
		{
		public:
			const K& operator()(const K& k)
			{
				return k;
			}
		};
		...
	private:
		RBTree <K, K, SetKeyOfCompare>m_tree;
	};

迭代器

这里红黑树的迭代器实际上就是对红黑树结点的指针进行了封装。

正向迭代器

typedef Treeiterator<T,T&,T*> iterator;  //普通迭代器
typedef Treeiterator<T, const T&, const T*> const_iterator; //常量迭代器
可能有人会疑惑,上面这两个迭代器不都差不多嘛,只是名字不一样,为什么不直接在类型上加const,
而是在模板参数上指定加上const属性呢?

答:我们要知道由于const对象只能调用常函数,但是像平时我们使用std::vector等容器时是不是可以支持 ++、-- 
等运算符重载呢?如果是const对象,它只能调用常函数,一旦加上变成const函数,那我们直接再类型上加的话
const迭代器就不能进行++、--、* 等,而我们要达到的效果是可以进行++、--等,但仅仅是不能修改其元素的值而
已。所以我们这里封装了一个模板指针。我们通过模板的参数不同来控制它的读取操作。

根据上面的阐述,我们就可以写出大致的迭代器基本定义。
具体代码如下所示:

template<class T,class Ref,class Ptr>
class Treeiterator
{
public:
	typedef RBTreeNode<T>node;  //结点类型重命名为node
	typedef Ref reference;
	typedef Ptr pointer;
public:
	Treeiterator(node* Pnode):m_node(Pnode)
	{}
private:
	node* m_node;
};
🗼迭代器构造函数

就是将一个结点的指针传给迭代器构造函数,并赋给我们迭代器的成员变量m_node。

	Treeiterator(node* Pnode):m_node(Pnode)
	{}
🧸迭代器++ - -运算符重载
其中++与- -运算符都是分别要满足正向中序遍历与反向中序遍历的。
即正向中序遍历默认是升序的,而反向中序遍历是降序。

其中迭代器++运算符重载思路是根据红黑树的begin()是指向的是红黑树的最左结点,那么我们此时最左结点开始左孩子一定为nullptr,此时遍历完了最左结点。那么就要开始遍历这个结点的右孩子的最左结点,如果右孩子也是为nullptr我们才需要往上更新继续遍历。

在这里插入图片描述

	self& operator++() //前置++
	{
		if (m_node->m_right != nullptr)
		{
			node* subLeft = m_node->m_right;
			while (subLeft->m_left != nullptr)
			{
				subLeft=subLeft->m_left;
			}
			m_node = subLeft;
		}
		else
		{
			node* cur = m_node;
			node* parent = cur->m_parent;
			while (parent != nullptr&& parent->m_right == cur )
			{
				cur = parent;
				parent = parent->m_parent;
			}
			m_node = parent;
		}
		return *this;
	}

	self operator++(int)  //后置++
	{
		self temp(*this);
		this->operator++(); //调用前置++来实现后置++
		return temp;
	}

与++同样的原理,++是从最左结点开始,相反那么- - 就是从最右结点开始倒序遍历,此时最右结点右孩子一定为nullptr,此时遍历完了最右结点。那么就要开始遍历这个结点的左孩子的最右结点,如果左孩子也是为nullptr我们才需要往上更新继续遍历。

	self& operator--()  //前置--
	{
		if (m_node->m_left != nullptr)
		{
			node* subright = m_node->m_left;
			while (subright->m_right != nullptr)
			{
				subright = subright->m_right;
			}
			m_node = subright;
		}
		else
		{
			node* cur = m_node;
			node* parent = cur->m_parent;
			while (parent != nullptr && parent->m_left == cur)
			{
				cur = parent;
				parent = parent->m_parent;
			}
			m_node = parent;
		}
		return *this;
	}

	self operator--(int)  //后置--
	{
		self temp(*this);
		this->operator--(); //调用前置--来实现后置--
		return temp;
	}
🗽迭代器 * 运算符重载

由于我们知道Ref是有两种类型的,一种是T&,另一种是const T&,所以即便是当我们的对象是const对象时,我们也可以控制它不让外部去修改。

	Ref operator*()
	{
		return m_node->m_data;
	}
🏝迭代器关系运算符重载

因为我们要实现迭代器的相关遍历操作,故我们要重载operator == 与 operator !=。
例如下面的代码:
ZJ::map<int,int>::iterator it = m.begin();
it != m.end()

	bool operator!=(const Treeiterator<T,Ref,Ptr>& obj) //重载!=
	{
		return m_node != obj.m_node;
	}

	bool operator==(const Treeiterator<T, Ref, Ptr>& obj) //重载==
	{
		return m_node == obj.m_node;
	}
🪐迭代器 -> 运算符重载

为什么要重载->运算符呢?这是因为如果我们RBTree中存储的是自定义类型时,我们的迭代器无法使用->去得到其成员。这里的Ptr也是有两种类型的,一种是T*,另一种是const T*,与上面的同理即便是当我们的对象是const对象时,我们也可以控制它不让外部去修改。

	Ptr operator->()
	{
		return &m_node->m_data;
	}

小结

到了这里可能会有人会问为什么我们不写迭代器的拷贝构造函数和析构函数呢?

答:这是因为我们的迭代器只是用来遍历容器中的部分或全部元素,方便我们不用去理解底层,就能得到元素的各个
值。每个迭代器对象代表的是容器中的确定的地址,并且这些结点元素析构和拷贝并不归我们管,结点应该归我们的
RBTree管,所以编译器默认提供的浅拷贝就已经足够了。

反向迭代器

这里的方向迭代器实际上就是正向迭代器的一个封装,红黑树的反向迭代器与stl中的priority_queue、stack和queue等一样就是一个适配器。

//迭代器适配器
template<class iterator>
class reverseIterator
{
public:
	typedef typename iterator::reference Ref;
	typedef typename iterator::pointer Ptr;
public:
	
	reverseIterator(iterator it) :m_it(it)
	{}
	
	Ref operator *()
	{
		return *m_it;
	}

	Ptr operator->()
	{
		return m_it.operator->();
	}

	reverseIterator<iterator>& operator++() //前置++
	{
		--m_it;
		return *this;
	}

	reverseIterator<iterator> operator++(int) //后置++
	{
		reverseIterator<iterator> temp(*this);
		--m_it;
		return temp;
	}

	reverseIterator<iterator>& operator--()  //前置--
	{
		++m_it;
		return *this;
	}

	reverseIterator<iterator> operator--(int) //后置--
	{
		reverseIterator<iterator> temp(*this);
		++m_it;
		return temp;
	}
	bool operator!=(const reverseIterator<iterator>& obj)
	{
		return m_it!=obj.m_it;
	}

	bool operator==(const reverseIterator<iterator>& obj)
	{
		return m_it == obj.m_it;
	}

private:
	iterator m_it;
};

封装好的红黑树及迭代器

其中在三个类中分别有下图的代码可能会有人迷惑,我在这阐述一下。
在这里插入图片描述

为什么这里给这个类型重命名需要加上typename或者class关键字呢?

答:这是因为当前这个类还未实例化时,又要将该类的模板参数传入到下一层的模板中,导致编译器找不到具体实例
化,故加上这个关键字是为了告诉编译器,你有这个东西但是需要进行实例化之后时候再来检查。

封装好的红黑树完整代码如下:

#pragma once
#include<iostream>
#include<stack>
#include<assert.h>
using namespace std;

//节点的颜色
enum class Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode	//三叉链
{
	RBTreeNode<T>* m_left;	//左节点
	RBTreeNode<T>* m_right;   //右节点
	RBTreeNode<T>* m_parent;  //节点的父结点(红黑树需要 旋转 ,为了实现简单给出的该字段)

	T m_data; //节点数据
	Colour m_c;	//节点颜色


	RBTreeNode(const T& data)
		: m_left(nullptr), m_right(nullptr), m_parent(nullptr)
		, m_data(data),m_c(Colour::RED)
	{}
};

template<class T,class Ref,class Ptr>
class Treeiterator
{
public:
	typedef RBTreeNode<T>node; //结点类型重命名为node
	typedef Ref reference;     
	typedef Ptr pointer;
	typedef Treeiterator<T, Ref, Ptr> self;
public:


	Treeiterator(node* Pnode):m_node(Pnode)
	{}

	Ref operator*()
	{
		return m_node->m_data;
	}
	Ptr operator->()
	{
		return &m_node->m_data;
	}


	self& operator++() //前置++
	{
		if (m_node->m_right != nullptr)
		{
			node* subLeft = m_node->m_right;
			while (subLeft->m_left != nullptr)
			{
				subLeft=subLeft->m_left;
			}
			m_node = subLeft;
		}
		else
		{
			node* cur = m_node;
			node* parent = cur->m_parent;
			while (parent != nullptr&& parent->m_right == cur )
			{
				cur = parent;
				parent = parent->m_parent;
			}
			m_node = parent;
		}
		return *this;
	}

	self operator++(int)  //后置++
	{
		self temp(*this);
		this->operator++();
		return temp;
	}

	self& operator--()  //前置--
	{
		if (m_node->m_left != nullptr)
		{
			node* subright = m_node->m_left;
			while (subright->m_right != nullptr)
			{
				subright = subright->m_right;
			}
			m_node = subright;
		}
		else
		{
			node* cur = m_node;
			node* parent = cur->m_parent;
			while (parent != nullptr && parent->m_left == cur)
			{
				cur = parent;
				parent = parent->m_parent;
			}
			m_node = parent;
		}
		return *this;
	}

	self operator--(int)  //后置--
	{
		self temp(*this);
		this->operator--();
		return temp;
	}

	bool operator!=(const Treeiterator<T,Ref,Ptr>& obj)
	{
		return m_node != obj.m_node;
	}

	bool operator==(const Treeiterator<T, Ref, Ptr>& obj)
	{
		return m_node == obj.m_node;
	}
private:
	node* m_node;

};

//迭代器适配器
template<class iterator>
class reverseIterator
{
public:
	typedef typename iterator::reference Ref;
	typedef typename iterator::pointer Ptr;
public:
	
	reverseIterator(iterator it) :m_it(it)
	{}
	
	Ref operator *()
	{
		return *m_it;
	}

	Ptr operator->()
	{
		return m_it.operator->();
	}

	reverseIterator<iterator>& operator++()
	{
		--m_it;
		return *this;
	}

	reverseIterator<iterator> operator++(int)
	{
		reverseIterator<iterator> temp(*this);
		--m_it;
		return temp;
	}

	reverseIterator<iterator>& operator--()
	{
		++m_it;
		return *this;
	}

	reverseIterator<iterator> operator--(int)
	{
		reverseIterator<iterator> temp(*this);
		++m_it;
		return temp;
	}
	bool operator!=(const reverseIterator<iterator>& obj)
	{
		return m_it!=obj.m_it;
	}

	bool operator==(const reverseIterator<iterator>& obj)
	{
		return m_it == obj.m_it;
	}

private:
	iterator m_it;
};

template<class K,class T,class KOfCompare>
class RBTree
{
public:
	typedef RBTreeNode<T> node;
	typedef Treeiterator<T,T&,T*> iterator;
	typedef Treeiterator<T, const T&, const T*> const_iterator;
	typedef reverseIterator<iterator> reverse_iterator;
	RBTree() :m_root(nullptr)
	{}

	RBTree(const RBTree<K, T, KOfCompare>& obj)
	{
		m_root = copy(obj.m_root, nullptr);
	}

	RBTree<K, T, KOfCompare>& operator=(const RBTree<K, T, KOfCompare>& obj)
	{
		if (this != &obj)
		{
			RBTree<K, T, KOfCompare>temp = obj;	//调用拷贝构造
			std::swap(temp.m_root, this->m_root);
		}
		return *this;
	}

	~RBTree() //利用后序来释放结点
	{
		if (m_root == nullptr)
		{
			return;
		}
		node* cur = m_root;
		node* prev = nullptr;
		stack<node*>s;
		while (cur != nullptr || !s.empty())
		{
			while (cur != nullptr)	//一直把左结点放进栈中
			{
				s.push(cur);
				cur = cur->m_left;
			}
			cur = s.top();
			s.pop();
			//cur->m_right==prev 这个判断很重要,防止访问完右结点时,来回横跳
			if (cur->m_right == nullptr || cur->m_right == prev)
			{
				prev = cur;
				cur->m_left = nullptr;
				cur->m_parent = nullptr;
				cur->m_right = nullptr;
				delete cur;
				cur = nullptr;
			}
			else
			{
				s.push(cur);
				cur = cur->m_right;
			}
		}
	}

	reverse_iterator rbegin()
	{
		node* cur = m_root;
		while (cur != nullptr && cur->m_right != nullptr)
		{
			cur = cur->m_right;
		}
		return reverse_iterator(iterator(cur));
	}

	reverse_iterator rend()
	{
		return reverse_iterator(iterator(nullptr));
	}

	iterator begin()
	{
		node* cur = m_root;
		while (cur != nullptr&&cur->m_left!=nullptr)
		{
			cur = cur->m_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	


	pair<iterator,bool> insert(const T& data)
	{
		if (m_root == nullptr)
		{
			m_root = new node(data);
			m_root->m_c = Colour::BLACK;	//根节点的颜色必须为黑
			return make_pair(iterator(m_root),true);
		}
		else
		{
			node* parent = nullptr;	
			node* cur = m_root;
			while (cur!=nullptr)
			{
				if ( m_com( cur->m_data ) < m_com(data))
				{
					parent = cur;
					cur = cur->m_right;
				}
				else if (m_com(cur->m_data) > m_com(data))
				{
					parent = cur;
					cur = cur->m_left;
				}
				else
				{
					return make_pair(iterator(cur), true);
				}
			}

			cur = new node(data);	//默认构造函数颜色初始化为RED了
			node* newnode = cur;
			if ( m_com(parent->m_data)> m_com(data))  //判断新插入的节点是要插入到我们的左边还是右边,并用父节点的指针连接起来
			{
				parent->m_left = cur;
				cur->m_parent = parent;
			}
			else
			{
				parent->m_right = cur;
				cur->m_parent = parent;
			}


			while (parent != nullptr && parent->m_c == Colour::RED)	//因为我们插入的颜色是红色,当我们的父亲节点的颜色也为红时,要一直向上调整颜色
			{
				//可能有人会有疑惑,这里这个祖父节点为什么不判断它为空指针的情况,并且如果是空指针,下面访问是不是会造成空指针访问崩溃呢?
				//其实不然,如果我们的节点中插入前只有头节点一个,那么它就不会进入到这个循环体里
				//因为根节点的颜色为 黑
				//如果此时这个红黑树中有两个节点,第二个节点不管它在根节点的左边还是右边。
				//此时那么我们假设另外一个节点在根结点的左边
				//那么如果我们要插入的元素是插入到根结点的右边,那么此时我们和上面同样的情况,进步来这个循环体里
				//如果插入到第二个节点的下面,那么此时虽然parent的颜色为空,但是我们的 祖父节点(grandfather)并不为空
				//综上所诉所以这里不需要判断它是否为空指针访问情况,不会造成空指针崩溃现象。
				node* grandfather=parent->m_parent;	//祖父

				//要变成什么颜色关键看叔叔uncle,所以我们找到叔叔的位置在左还是右
				if (grandfather->m_left == parent)  //判断
				{
					node* uncle = grandfather->m_right;
					//第一种情况:叔叔存在 且为 红
					if (uncle != nullptr && uncle->m_c == Colour::RED)
					{
						grandfather->m_c = Colour::RED;
						parent->m_c = uncle->m_c = Colour::BLACK;

						//为什么让cur=grandfather,而不是parent呢?
						//这是因为此时这种情况,parent的颜色变成黑色,黑色时,这个节点就不需要判断了。
						//如果从parent开始,就不能继续往上更新了。会直接终止循环。
						//而是从我们刚刚变成红色的grandfather节点开始,有可能此时grandfather节点变为红
						//导致上层每条路径的黑色节点数目不同发生错误。
						cur = grandfather;			//继续往上更新颜色
						parent = cur->m_parent;
					}
					//第二 第三种情况:叔叔不存在 或 叔叔存在且为 黑
					else
					{
						//双旋 -> 单旋
						if (parent->m_right == cur)	//判断路径是否是折线,如果是折线就需要先左旋,在右旋
						{						//左旋完成后,将parent指针与cur指针的指向更改,因为cur与parent的相对次序反了,不然会出现错误
							RotateL(parent);
							std::swap(parent, cur);
						}

						//这里有可能是第三种情况左旋转换而来
						RotateR(grandfather);
						grandfather->m_c = Colour::RED;
						parent->m_c = Colour::BLACK;

						//因为不管你上面是 红色还是黑色,之后都不会影响其每条路径的黑色节点个数
						break;	//可以写也可以不写
					}
				}
				else
				{
					//此时uncle(叔叔)节点在我们的左边
					node* uncle = grandfather->m_left;
					//第一种情况:叔叔存在 且为 红
					if (uncle != nullptr && uncle->m_c == Colour::RED)
					{
						grandfather->m_c = Colour::RED;
						parent->m_c = uncle->m_c = Colour::BLACK;

						cur = grandfather;			//继续往上更新颜色
						parent = cur->m_parent;
					}
					//第二 第三种情况:叔叔不存在 或 叔叔存在且为 黑
					else
					{
						//双旋 -> 单旋
						if (parent->m_left == cur)	//判断路径是否是折线,如果是折线就需要先右旋,在左旋
						{						//右旋完成后,将parent指针与cur指针的指向更改,因为cur与parent的相对次序反了,不然会出现错误
							RotateR(parent);
							std::swap(parent, cur);
						}

						//这里有可能是第三种情况右旋转换而来
						RotateL(grandfather);
						grandfather->m_c = Colour::RED;
						parent->m_c = Colour::BLACK;

						//因为不管你上面是 红色还是黑色,之后都不会影响其每条路径的黑色节点个数
						break;	//可以写也可以不写
					}
				}
			}
			m_root->m_c = Colour::BLACK;	//始终让头节点的颜色为黑
			return make_pair(iterator(newnode), true);
		}
	}

	//删除函数 删除看兄弟
	bool erase(const K& key)
	{
		//用于遍历二叉树
		node* parent = nullptr;
		node* cur = m_root;
		//用于标记实际的待删除结点及其父结点
		node* delParentPos = nullptr;
		node* delPos = nullptr;
		while (cur != nullptr)
		{
			if (key < m_com(cur->m_data)) //所给key值小于当前结点的key值
			{
				//往该结点的左子树走
				parent = cur;
				cur = cur->m_left;
			}
			else if (key > m_com(cur->m_data)) //所给key值大于当前结点的key值
			{
				//往该结点的右子树走
				parent = cur;
				cur = cur->m_right;
			}
			else //找到了待删除结点
			{
				if (cur->m_left == nullptr) //待删除结点的左子树为空
				{
					if (cur == m_root) //待删除结点是根结点
					{
						m_root = m_root->m_right; //让根结点的右子树作为新的根结点
						if (m_root)
						{
							m_root->m_parent = nullptr;
							m_root->m_c = Colour::BLACK; //根结点为黑色
						}
						delete cur; //删除原根结点
						return true;
					}
					else
					{
						delParentPos = parent; //标记实际删除结点的父结点
						delPos = cur; //标记实际删除的结点
					}
					break; //进行红黑树的调整以及结点的实际删除
				}
				else if (cur->m_right == nullptr) //待删除结点的右子树为空
				{
					if (cur == m_root) //待删除结点是根结点
					{
						m_root = m_root->m_left; //让根结点的左子树作为新的根结点
						if (m_root)
						{
							m_root->m_parent = nullptr;
							m_root->m_c = Colour::BLACK; //根结点为黑色
						}
						delete cur; //删除原根结点
						return true;
					}
					else
					{
						delParentPos = parent; //标记实际删除结点的父结点
						delPos = cur; //标记实际删除的结点
					}
					break; //进行红黑树的调整以及结点的实际删除
				}
				else //待删除结点的左右子树均不为空
				{
					//替换法删除
					//寻找待删除结点右子树当中key值最小的结点作为实际删除结点
					node* minParent = cur;
					node* minRight = cur->m_right;
					while (minRight->m_left != nullptr)
					{
						minParent = minRight;
						minRight = minRight->m_left;
					}
					cur->m_data = minRight->m_data; 
					delParentPos = minParent; //标记实际删除结点的父结点
					delPos = minRight; //标记实际删除的结点
					break; //进行红黑树的调整以及结点的实际删除
				}
			}
		}

		if (delPos == nullptr) //delPos没有被修改过,说明没有找到待删除结点
		{
			return false;
		}

		//记录待删除结点及其父结点(用于后续实际删除)
		node* del = delPos;
		node* delP = delParentPos;

		//调整红黑树 为了删除黑色结点后来恢复红黑树的性质
		if (delPos->m_c == Colour::BLACK) //删除的是黑色结点
		{
			if (delPos->m_left != nullptr) //待删除结点有一个红色的左孩子(不可能是黑色)
			{
				delPos->m_left->m_c = Colour::BLACK; //将这个红色的左孩子变黑即可
			}
			else if (delPos->m_right != nullptr) //待删除结点有一个红色的右孩子(不可能是黑色)
			{
				delPos->m_right->m_c = Colour::BLACK; //将这个红色的右孩子变黑即可
			}
			else //待删除结点的左右均为空
			{
				while (delPos != m_root) //可能一直调整到根结点
				{
					if (delPos == delParentPos->m_left) //待删除结点是其父结点的左孩子
					{
						node* brother = delParentPos->m_right; //兄弟结点是其父结点的右孩子
						//情况一:brother为红色
						if (brother->m_c == Colour::RED)
						{
							delParentPos->m_c = Colour::RED;
							brother->m_c = Colour::BLACK;
							RotateL(delParentPos);
							//需要继续处理
							brother = delParentPos->m_right; //更新brother(否则在本循环中执行其他情况的代码会出错)
						}

						//情况二:brother为黑色,且其左右孩子都是黑色结点或者 一边为空一变为黑时
						//甚至两边都为NIL,就是两边都为空时
						if (((brother->m_left == nullptr) || (brother->m_left->m_c == Colour::BLACK))
							&& ((brother->m_right == nullptr) || (brother->m_right->m_c == Colour::BLACK)))
						{
							brother->m_c = Colour::RED;
							if (delParentPos->m_c == Colour::RED)
							{
								delParentPos->m_c = Colour::BLACK;
								break;
							}
							else
							{
								//需要继续处理
								delPos = delParentPos;
								delParentPos = delPos->m_parent;
							}
						}
						else
						{
							//情况三:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空
							if ((brother->m_right == nullptr) || (brother->m_right->m_c == Colour::BLACK))
							{
								brother->m_left->m_c = Colour::BLACK;
								brother->m_c = Colour::RED;
								RotateR(brother);
								//需要继续处理
								brother = delParentPos->m_right; //更新brother(否则执行下面情况四的代码会出错)
							}

							//情况四:brother为黑色,且其右孩子是红色结点
							brother->m_c = delParentPos->m_c;
							delParentPos->m_c = Colour::BLACK;
							brother->m_right->m_c = Colour::BLACK;
							RotateL(delParentPos);
							break; //情况四执行完毕后调整一定结束
						}
					}
					else //delPos == delParentPos->m_right //待删除结点是其父结点的右孩子
					{
						node* brother = delParentPos->m_left; //兄弟结点是其父结点的左孩子
						//情况一:brother为红色
						if (brother->m_c == Colour::RED) //brother为红色
						{
							delParentPos->m_c = Colour::RED;
							brother->m_c = Colour::BLACK;
							RotateR(delParentPos);
							//需要继续处理
							brother = delParentPos->m_left; //更新brother(否则在本循环中执行其他情况的代码会出错)
						}

						//情况二:brother为黑色,且其左右孩子都是黑色结点或为空
						if (((brother->m_left == nullptr) || (brother->m_left->m_c == Colour::BLACK))
							&& ((brother->m_right == nullptr) || (brother->m_right->m_c == Colour::BLACK)))

						{
							brother->m_c = Colour::RED;
							if (delParentPos->m_c == Colour::RED)
							{
								delParentPos->m_c = Colour::BLACK;
								break;
							}
							else
							{
								//需要继续处理
								delPos = delParentPos;
								delParentPos = delPos->m_parent;
							}
						}
						else
						{
							//情况三:brother为黑色,且其右孩子是红色结点,左孩子是黑色结点或为空
							if ((brother->m_left == nullptr) || (brother->m_left->m_c == Colour::BLACK))
							{
								brother->m_right->m_c = Colour::BLACK;
								brother->m_c = Colour::RED;
								RotateL(brother);
								//需要继续处理
								brother = delParentPos->m_left; //更新brother(否则执行下面情况四的代码会出错)
							}

							//情况四:brother为黑色,且其左孩子是红色结点
							brother->m_c = delParentPos->m_c;
							delParentPos->m_c = Colour::BLACK;
							brother->m_left->m_c = Colour::BLACK;
							RotateR(delParentPos);
							break; //情况四执行完毕后调整一定结束
						}
					}
				}
			}
		}

		//链接删除结点的孩子与删除结点父亲的链接关系
		if (del->m_left == nullptr) //实际删除结点的左子树为空
		{
			if (del == delP->m_left) //实际删除结点是其父结点的左孩子
			{
				delP->m_left = del->m_right;
				if (del->m_right != nullptr)
				{
					del->m_right->m_parent = delP;
				}
			}
			else //实际删除结点是其父结点的右孩子
			{
				delP->m_right = del->m_right;
				if (del->m_right != nullptr)
				{
					del->m_right->m_parent = delP;
				}
			}
		}
		else //实际删除结点的右子树为空
		{
			if (del == delP->m_left) //实际删除结点是其父结点的左孩子
			{
				delP->m_left = del->m_left;
				if (del->m_left != nullptr)
				{
					del->m_left->m_parent = delP;
				}
			}
			else //实际删除结点是其父结点的右孩子
			{
				delP->m_right = del->m_left;
				if (del->m_left != nullptr)
				{
					del->m_left->m_parent = delP;
				}
			}
		}

		//最后将结点删除
		delete del;
		return true;
	}

private:
	//传进来的是不平衡的结点的指针
	void RotateL(node* parent)	//左旋
	{
		node* subR = parent->m_right;	//出问题的右结点
		node* subRL = subR->m_left;		//出问题的右结点的左结点

		parent->m_right = subRL;		//首先先将我们的parent的右指向subRL
		if (subRL != nullptr)			//再将subRL的父亲指向parent结点,但这样要判断一下是否是空指针,如果subRL是空指针的话,
										//那么解引用它会出现问题
		{
			subRL->m_parent = parent;
		}

		node* curParent = parent->m_parent;	//拿一个结点存储parent的父亲
		parent->m_parent = subR;			//再使得parent的父亲指针指向它原先的右结点(subR)

		subR->m_left = parent;				//在让subR的左指向parent
		if (parent == m_root)				//在这里得判断一下它是否为根,如果parent为根的话,那么我们的根结点指针也得改变
											//并且将subR的父亲指针置为空,此时subR此时为根结点
		{
			m_root = subR;
			subR->m_parent = nullptr;
		}
		else
		{									//如果不为头节点,那么我们只需要将subR的父亲指针指向parent之前的父亲结点
			if (curParent->m_left == parent)
			{
				curParent->m_left = subR;
			}
			else
			{
				curParent->m_right = subR;
			}
			subR->m_parent = curParent;
		}
	}


	void RotateR(node* parent)	//右旋
	{
		node* subL = parent->m_left;
		node* subLR = subL->m_right;

		parent->m_left = subLR;
		if (subLR != nullptr)
		{
			subLR->m_parent = parent;
		}

		node* curParent = parent->m_parent;
		parent->m_parent = subL;

		subL->m_right = parent;
		if (parent == m_root)
		{
			m_root = subL;
			subL->m_parent = nullptr;
		}
		else
		{
			if (curParent->m_left == parent)
			{
				curParent->m_left = subL;
			}
			else
			{
				curParent->m_right = subL;
			}
			subL->m_parent = curParent;
		}

	}


	node* copy(node* root, node* parent)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		node* copyNode = new node(root->m_data);
		copyNode->m_c = root->m_c;
		copyNode->m_parent = parent;
		copyNode->m_left = copy(root->m_left, copyNode);
		copyNode->m_right = copy(root->m_right, copyNode);
		return copyNode;
	}


private:
	node* m_root;	//根结点
	KOfCompare m_com;	//利用该对象调用仿函数来比较大小
};

map模拟实现

其中map就是封装了红黑树对象,然后根据红黑树的对象来调用红黑树的相关接口实现的。
代码如下:

namespace ZJ
{
	template<class K, class V>
	class map
	{
	public:
		class MapKeyOfCompare    //内部类
		{
		public:
			const K& operator()(const pair<K,V>&kv)
			{
				return kv.first;
			}
		};
		typedef class RBTree<K, pair<K,V>, MapKeyOfCompare>::iterator iterator;
		typedef class RBTree<K, pair<K, V>, MapKeyOfCompare>::reverse_iterator reverse_iterator;

		iterator begin()
		{
			return m_tree.begin();
		}
		iterator end()
		{
			return m_tree.end();
		}

		reverse_iterator rbegin()
		{
			return m_tree.rbegin();
		}
		reverse_iterator rend()
		{
			return m_tree.rend();
		}

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

set模拟实现

与map同理,都是对同一棵红黑树对象的封装,然后根据红黑树对象调用红黑树相关接口来实现set的相关接口。
代码如下:

namespace ZJ
{
	template<class K>
	class set
	{
	public:
		class SetKeyOfCompare   //内部类
		{
		public:
			const K& operator()(const K& k)
			{
				return k;
			}
		};
		typedef class RBTree<K, K, SetKeyOfCompare>::iterator iterator;
		typedef class RBTree<K, K, SetKeyOfCompare>::reverse_iterator reverse_iterator;


		iterator begin()
		{
			return m_tree.begin();
		}
		iterator end()
		{
			return m_tree.end();
		}

		reverse_iterator rbegin()
		{
			return m_tree.rbegin();
		}
		reverse_iterator rend()
		{
			return m_tree.rend();
		}
		pair<iterator, bool> insert(const K& k)
		{
			return m_tree.insert(k);
		}
		void erase(const K& key)
		{
			m_tree.erase(key);
		}
	private:
		RBTree <K, K, SetKeyOfCompare>m_tree;
	};
}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
unordered_map与unordered_set有什么区别? 回答: unordered_map和unordered_set都是关联式容器,类似于键值对 (key-value) 的模型。它们的底层实现方式不同,unordered_map使用哈希表作为底层数据结构,而unordered_set也是使用哈希表。unordered_map和unordered_set的区别在于它们存储的类型不同,即unordered_map存储键值对,而unordered_set存储单个元素。此外,unordered_map和unordered_set在功能上也有一些区别。unordered_map提供了以键为索引的查找功能,而unordered_set则提供了判断元素是否存在的功能。从效率上来看,unordered_map和unordered_set的增删查改操作的时间复杂度都是O(1),即常数时间。而mapset的时间复杂度为O(logN),其中N是容器中的元素数量。所以在对效率要求较高的情况下,选择unordered_map和unordered_set会更合适。但是,unordered_map和unordered_set相比于mapset会消耗更多的内存空间。因此,在对数据有排序或者对空间有要求的情况下,选择mapset;而对于对效率有要求的情况,选择unordered_map和unordered_set更合适。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [STL详解(十二)—— unordered_set、unordered_map的介绍及使用](https://blog.csdn.net/chenlong_cxy/article/details/122277348)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [模拟实现unordered_map和unordered_set超详解(C++)](https://blog.csdn.net/m0_67430750/article/details/124760725)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [unordered_set和unordered_map的使用【STL】](https://blog.csdn.net/m0_63312733/article/details/128000844)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值