【C++】封装红黑树实现 mymap 和 myset

文章目录

  • 模拟实现 map 和 set
    • 1. 如何复用前面写过的红黑树
    • 2. 支持 Insert
    • 3. 迭代器的实现
      • 3.1 RBTree 的 iterator
      • 3.2 map、set 的迭代器实现
    • 3.3 map 支持 []

前面我们实现了模拟实现了红黑树,今天就通过复用之前的代码来封装 map 和 set。

模拟实现 map 和 set

1. 如何复用前面写过的红黑树

map、set的底层都是红黑树,但是set在使用时需要传一个 K 模板参数就可以了,但是 map 则需要传一个 pair 的模板参数,这就导致了虽然 set、map的底层都是红黑树,但是我们需要写 Key 和 pair 两种结构的红黑树代码,这显然过于冗杂,那该怎么办呢?
是的,我们可以使用模板让编译器自己生成,虽说在编译器生成后还是两棵独立的红黑树代码,但是我们手写部分明显少了很多,也更方便后续的维护。
前面我实现红黑树的时候是使用 K,V 模板实现的,但是我们这里可以对其修改一下,变成 K,T 模板,T指的是这棵树存储的是 K 类型还是 pair 类型

还有一个问题,我们这里使用的是 K,T 模板参数,既然 T 参数可以表示底层存储类型,为什么还要一个 K 模板参数呢?这样会不会很冗余?
你可以想想 Insert、find、erase 的场景,在 Insert 的时候确实是可以直接用 T 没错。但是如果你是 map ,你在 find、erase 的时候,是使用 pair 的吗?显然不是吧,使用的是 K,所以这里需要额外使用一个 K 模板参数

所以我们的 RBTreeNode 直接用 T 模板参数表示它的存储的类型
这里又引申出来个问题,我们之前使用 K,V 的时候可以直接使用它的 first 进行比较,但是这里我们并不知道它的类型到底是 K 还是 pair ,所以这里我们需要引入一个KeyOfT 仿函数来帮我们支持比较。

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

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

template<class K, class V>
class RBTree {}


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

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

template<class K, class Tclass KeyOfT>
class RBTree {}

在 map 里面实现 MapKeyOfT,在 set 里面实现 SetKeyOfT,用于各自支持比较

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

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

我们前面说过在红黑树类前有一个 KeyOfT 模板参数,等需要使用的时候,就在要使用的地方通过 KeyOfT 实例化一个对象,再把要比较的对象传过去再进行比较。

// 类似于这样的使用方式
KeyOfT kot;
if (kot(cur->_data) < kot(data))

2. 支持 Insert

因为我们在 map、set 类中分别存在着一个对应的红黑树对象,所以我们想的应该是直接通过调用底层红黑树的 Insert 函数来实现,我们外层的 map、set 仅仅只是一层壳。

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

3. 迭代器的实现

要想支持 map、set 的迭代器,得现在底层支持 RBTree 的迭代器

3.1 RBTree 的 iterator

还是老规矩,迭代器模板参数数量为三个,分别为 T、Ref、Ptr,以便于在使用时通过控制参数是否为 const 类型来实例化为 普通迭代器 和 const 迭代器。

RBTree 的迭代器和链表的迭代器比较相似,因为都是链式结果。最大的区别在于它们++、-- 的方式不同。因为RBTree 的底层是一棵树,所以相对而言会复杂许多。

operator++
因为红黑树是一棵自平衡的二叉搜索树,所以我们对它 ++ 的设计就是通过模仿中序的方式 来实现的。

那么就来讨论一下中序。
中序代表着这一节点已经使用,而下一节点是右子树的最左(最小)节点,所以:

  • 如果当前节点存在右孩子的话,就先取它的右节点,再取这个右孩子的最左节点
  • 如果当前节点不存在右孩子的话:
    • 如果当前节点是父节点的左,根据中序左->根->右,那么下一节点就是当前节点的父节点
    • 如果当前节点是父节点的右, 当前节点访问完了,就表示父节点的子树访问完了。那我们就模拟回溯的行为,往根的祖先中去找,直到找到一个节点,它是父亲的左节点,那么那个节点的父亲节点就是下一个要访问的节点

如何表示 begin 呢?
按照二叉搜索树的性质,它第一个访问位置为整棵树的最左节点,那么就是它

如何表示 end 呢?
按照上面++的思路,当我们找完最后一个有效节点之后,你再按照上面的思路进行一次++,你会发现你到了 _root 节点的父节点的位置。_root 节点的父节点为空,所以我们将 end 设置为 nullptr

operator–
迭代器 – 的实现刚好和 ++ 反过来,它的访问顺序是 右->根->左。

  • 因为 end 位置是 nullptr,所以需要特判一下,直接返回整棵树的最右节点即可
  • 如果左子树不为空,按照中序,我们可以确定下一个访问位置是左子树的最后一个节点(最右节点)
  • 如果左子树为空,下一个节点是 孩子是父节点的左节点 的那个父节点

其他的和之前实现其他迭代器的方式是一样的

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

	Node* _node;
	Node* _root;


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

	Self operator++()
	{
		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 = cur->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self operator--()
	{
		if (_node == nullptr) // --end()
		{
			// --end(),特殊处理,走到中序最后一个节点,整棵树的最右节点
			Node* rightMost = _root;
			while (rightMost && rightMost->_right)
				rightMost = rightMost->_right;

			_node = rightMost;
		}
		else if (_node->_left)
		{
			// 左子树不为空,中序左子树最后一个
			Node* rightMost = _root->_left;
			while (rightMost->_right)
				rightMost = rightMost->_right;

			_node = rightMost;
		}
		else
		{
			// 孩子是父亲右的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}
			_node = parent;
		}

		return *this;
	}

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

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

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

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

3.2 map、set 的迭代器实现

都是通过调用底层红黑树的迭代器来实现的。

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

 public:
 		// 这里 typename 是为了声明这是类型
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;

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

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

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

		const_iterator end()  const
		{
			return _t.End();
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};

};


// set中:
namespace zkp
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

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

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

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

		const_iterator end()  const
		{
			return _t.End();
		}
	private:
		// 反正 set 的key不允许修改,直接加 const
		RBTree<K, const K, SetKeyOfT> _t;
	};
}

3.3 map 支持 []

我们在使用库中的 map 时,是可以直接使用 [] 进行插入和修改的,而要支持这个,则需要修改底层红黑树 Insert 的返回值,和 map 中 insert 的返回值

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

		// set 中:
		// 这里只是为了维护代码堆统一性,并无实际作用
		pair<iterator, bool> insert(const K& key)
		{
			return _t.Insert(key);
		}

	// 底层红黑树的插入
	pair<Iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;

			//return pair(Iterator(_root, _root), true);
			return { Iterator(_root, _root), true }; // 直接用 C++11 的方式写吧
		}

		// 插入部分
		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		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 { Iterator(cur, _root), false };
			}
		}

		cur = new Node(data);
		// 保存新节点,等下返回的时候用
		Node* newnode = cur;
		cur->_col = RED;
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		// 链接父亲
		cur->_parent = parent;

		// 父亲是红色节点,出现连续红色节点,开始处理
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				//   g
				// p   u
				Node* uncle = grandfather->_right;
				// 如果叔叔节点也是红色,直接变色并继续向上调整就可以了,不需要旋转
				if (uncle && uncle->_col == RED)
				{
					// 变色
					uncle->_col = parent->_col = BLACK;
					grandfather->_col = RED;

					// 将 g 作为新的 c 继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 到这就该考虑 p、c 同侧还是异侧的情况了
					// p、c同侧单旋,异侧双旋

					if (cur == parent->_left)
					{
						// 同侧单旋

						//		g
						//	  p   u
						//  c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						// 异侧双旋

						//		g
						//	  p   u
						//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					// 单旋双旋都是直接跳出循环
					break;
				}
			}
			else
			{
				//   g
				// u   p
				Node* uncle = grandfather->_left;
				// 叔叔为红,变色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 还是考虑同侧异侧的问题,代码和上面刚好对称

					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 { Iterator(newnode, _root), true };
	}

你可能感兴趣的:(c++,开发语言,stl)