AVL树和红黑树(下)

前言

前面两个章节我们介绍了AVL树和红黑树,也了解到了c++stl里的map和set底层实现是应用了红黑树,今天我们就来自己封装一下map和set。在上两节的代码里,我们的AVL树和红黑树只固定了一种类型,也就是需要写两份来满足map和set,今天我们就要用泛型的思想通过模板参数来同时实现set和map。底层就用到我们上一章的红黑树。

迭代器的实现

在最开始,我们首先要搞定迭代器,有了它我们才能实现++、--这些操作。我们需要定义一个结构体__TreeIterator

__TreeIterator

template<class T>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T> Self;
	Node* _node;

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

};

 首先先给RBTreeNode<T> 、__TreeIterator<T>创建一个别名方便后面使用,然后写一个简单的构造函数。接下来我们先来实现简单的!=和==的函数重载。

!=和==的函数重载

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

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

这两个比较简单,就是比较两个Node的值是否相等来确定返回true还是false。

++、-- 迭代器

我先奉上代码再来解释:

Self& operator--()
{
	if (_node->_left)
	{
		Node* subRight = _node->_left;
		while (subRight->_right)
		{
			subRight = subRight->_right;
		}

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

		_node = parent;
	}

	return *this;
}

Self& operator++()
{
	if (_node->_right)
	{
		// 下一个就是右子树的最左节点
		Node* cur = _node->_right;
		while (cur->_left)
		{
			cur = cur->_left;
		}

		_node = cur;
	}
	else
	{
		// 左子树 根 右子树
		// 右为空,找孩子是父亲左的那个祖先
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_right)
		{
			cur = parent;
			parent = parent->_parent;
		}

		_node = parent;
	}

	return *this;
}

 先来解释++函数重载,不论是++还是--,其实都是根据中序遍历来确定的,也就是左子树、根、右子树的顺序。

规律如下:

如果该节点右子树不为空,那么就找右子树的最左节点。

如果该节点右子树为空,说明已经访问完了这棵子树,那么就需要找该节点(孩子)是父亲左的那个节点(祖先),这个祖先就是我们++之后的位置,也就是根,可以理解为这个祖先的左子树已经访问完了,根据中序遍历需要访问根了。

我们拿一个红黑树来说明:

1.以15这个节点来举例,我们知道 ++之后应该到17这个位置。

根据上面的规律,先访问右子树,发现右子树为空 ,那么就需要找15是父亲左的那个节点,循环判断后,发现17就是++之后的位置。

2.以17这个节点来举例,我们知道 ++之后应该到22这个位置。

根据上面的规律,先访问右子树,发现右子树不为空,那么就寻找右子树的最有节点,直到找到22位置。

--的思路和++类似,只不过是反过来。

规律如下:

如果该节点左不为空,那么就找左子树的最右节点。

如果该节点左为空,那么就需要找该节点(孩子)是父亲右的那个节点(祖先),这个祖先就是我们--之后的位置。

大家可是自己带入一下,这里就不举例子了。

*和->的重载

这个比较简单我直接把代码奉上:
 

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

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

RBTree 

在之前我们是把他的数据类型写死的,不能同时实现map和set,今天我们就要使用模板来解决这个问题,在此之前,我先把map和set的代码写出来。

map

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

		// 对类模板取内嵌类型,加typename告诉编译器这里是类型
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;

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

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

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

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

 set

template<class K>
class set
{
public:
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};

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

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

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

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

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

我们不难发现,map和set并没有多少东西,他只是进行了封装,底层调用的红黑树。 而红黑树的模板参数增多了,这就是为什么我们可以同时实例化出map和set的原因。接下来我们来详细看一下增加的模板参数

insert

template<class K, class V, class KeyOfV>

K表示key,V表示value,KeyOfv表示在比较时是通过key还是value来比较。

这样在set中实现了一个SetKeyofV,在map中实现了一个MapKeyofV,只需要在传参时将他们两个传入,便可以实例化出两个不同的版本。

SetKeyofV本身没有变化,他就是返回K这个类型的数据,MapKeyofV是将pair类型的first返回,这样就都可以进行比较了。

库里的insert并不是简单的bool,而是一个pair<iterator, bool>,若插入成功返回节点的迭代器和true,失败返回cur的迭代器和false。

pair<iterator, bool> Insert(const V& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return make_pair(iterator(_root), true);
	}

	Node* parent = nullptr;
	Node* cur = _root;
	KeyOfV kot;

	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);
	Node* newnode = cur;
	cur->_col = RED;
	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)
		{
			//     g
			//   p   u
			// c
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)
			{
				// 变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上更新处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if (cur == parent->_left)
				{
					// 单旋
					//     g
					//   p
					// c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					// 双旋
					//     g
					//   p
					//     c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
		else  // parent == grandfather->_right
		{
			//     g
			//   u   p 
			//          c
			//
			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
				{
					//     g
					//   u   p 
					//     c
					//
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
	}

	_root->_col = BLACK;

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

这就是改变后的insert代码。

[]重载

在map中[]的使用非常方便,既能添加又能修改,这里我们就来实现一下它,他其实就是用了红黑树里的insert,然后返回值变成了pair的second的引用,代码如下:

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

const的迭代器稍微复杂,有时间我会更新给大家。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值