1. 调整之前实现的红黑树的insert

1.1 整体框架的搭建

新建两个头文件,Mymap.hMyset.h ,一个源文件 test.cpp ,然后把之前实现的红黑树拷贝一份过来。

为了和库里面的一些东西区分开,我们还是把所有自己实现的内容都放在自己的命名空间里。

Mymap.h中搭建框架。key参数就⽤K,value参数就⽤V。

代码语言:javascript

AI代码解释

#include "RBTree.h"
namespace lyj
{
	template<class K, class V>
	class map
	{
	private:
		RBTree<K, pair<K, V>> _t;
	};
}

Myset.h中搭建框架。

代码语言:javascript

AI代码解释

#include "RBTree.h"
namespace lyj
{
	template<class K>
	class set
	{
	private:
		RBTree<K, K> _t;
	};
}

直接对这个 RBTree.h 进行修改。

首先就是把模板参数改掉,红⿊树中的数据类型我们使⽤T

代码语言:javascript

AI代码解释

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)
	{}
};

key参数就⽤K,这里的T就是决定到底是set还是map的。

如果我们传一个K过去就是set,传pair过去就是map。

1.2 insert的修改

因为我们把红⿊树中的数据类型T来表示了,也就是_data,这个_data是一个泛型,可能是set的K,可能是map的pair,用以前的逻辑就不能满足这个比较。

这个_kv换成_data也没用,不适用于set的K场景,并且pair自身支持的比较方法也不是我们想要的,我们需要任何时候都只比较K。

此时我们就需要实现一个仿函数。就是取出K来,set中就取K,map中就取first。

Myset.hset类里实现仿函数Set_Key_Of_T

代码语言:javascript

AI代码解释

struct Set_Key_Of_T
{
	const K& operator()(const K& key)
	{
		return key;
	}
};

set里这个仿函数就是给 key直接返回key就行。

Mymap.hmap类里实现仿函数Map_Key_Of_T

代码语言:javascript

AI代码解释

struct Map_Key_Of_T
{
	const K& operator()(const pair<K, V>& kv)
	{
		return kv.first;
	}
};

map的仿函数就是返回pair里的first。

有仿函数之后,红黑树的模板参数也要多加一个了,叫KeyOfT。

代码语言:javascript

AI代码解释

template<class K, class T, class KeyOfT>
class RBTree
{
    //...
}

insert里的比较逻辑就要要成用这个仿函数写的逻辑。

map的find返回的是一个pair,这个pair的first是一个迭代器,second是一个bool值,所以这里的返回值也要修改一下。

代码语言:javascript

AI代码解释

pair<Iterator, bool> insert(const T& data)
{
}

插入成功,就返回新插入的值的迭代器和true,插入失败就返回已经存在的这个值的迭代器和false。所以整体代码如下。

代码语言:javascript

AI代码解释

pair<Iterator, bool> insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;  //根节点为黑色
		return make_pair(Iterator(_root, _root), true);
	}
	Node* parent = nullptr;
	Node* cur = _root;
	KeyOfT com; //仿函数
	while (cur)
	{
		if (com(cur->_data) > com(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (com(cur->_data) < com(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else 
			return make_pair(Iterator(cur, _root), false);
	}
	cur = new Node(data);
	Node* newnode = cur; //记录新插入的节点
	cur->_col = RED;  //新插入节点为红色
	if (com(cur->_data) < com(parent->_data))
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;

	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left) //u在右
		{
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED) //u存在且为红
			{
				parent->_col = BLACK; //u和p变黑
				uncle->_col = BLACK;
				grandfather->_col = RED;//g变红

				cur = grandfather; //继续向上更新
				parent = cur->_parent;
			}
			else //u不存在 或 u存在且为黑
			{
				if (cur == parent->_left) //单旋
				{
					rotateR(grandfather);//以g为旋转点右旋
					parent->_col = BLACK;  //变色
					grandfather->_col = RED;
				}
				else //双旋
				{
					rotateL(parent); //先对p左旋
					rotateR(grandfather);//再对g右旋
					//变色
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else //u在左
		{
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED) //u存在且为红
			{
				parent->_col = BLACK; //p和u变黑
				uncle->_col = BLACK;
				grandfather->_col = RED;//g变红

				cur = grandfather; //继续向上更新
				parent = cur->_parent;
			}
			else //u不存在 或 存在且为黑
			{
				if (cur == parent->_right) //单旋
				{
					rotateL(grandfather);//以g为中心左旋
					parent->_col = BLACK; //p变黑
					grandfather->_col = RED;//g变红
				}
				else //双旋
				{
					rotateR(parent);//先以p为中心右旋
					rotateL(grandfather);//再以g为中心左旋
					cur->_col = BLACK; //c变黑
					grandfather->_col = RED;//g变红
				}
				break;
			}
		}
	}
	_root->_col = BLACK;
	return make_pair(Iterator(newnode, _root), true);
}

上面的代码相比之前实现的insert,只有插入部分的代码修改了,旋转部分没做修改。

然后我们用set的insert测试一下。

Myset.h中加上insert的函数,在set类public实现。

代码语言:javascript

AI代码解释

public:
	pair<iterator, bool> insert(const K& key) //插入
	{
		return _t.insert(key);
	}
private:
	RBTree<K, K, Set_Key_Of_T> _t;

Mymap.h中加上insert的函数,在map类public实现。

代码语言:javascript

AI代码解释

public:
	pair<iterator, bool> insert(const pair<K, V>& kv) //插入
	{
		return _t.insert(kv);
	}
private:
	RBTree<K, pair<K, V>, Map_Key_Of_T> _t;

test.cpp中测试。如果没报错,目前这个insert就是对的。

代码语言:javascript

AI代码解释

#include "Myset.h"
#include "Mymap.h"
int main()
{
	lyj::set<int> s; //用自己实现的set
	s.insert(5);
	s.insert(3);
	s.insert(2);
	s.insert(4);
	s.insert(1);
	return 0;
}

2. iterator 迭代器的实现

2.1 部分运算符重载

这里需要同时考虑普通的迭代器和const迭代器,所以还是要一个类模板来实现。

代码语言:javascript

AI代码解释

template<class T, class ref, class ptr>
struct RBTree_Iterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTree_Iterator<T, ref, ptr> Self;

};

还需要一个节点的指针,并写一个构造函数。

代码语言:javascript

AI代码解释

template<class T, class ref, class ptr>
struct RBTree_Iterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTree_Iterator<T, ref, ptr> Self;

	Node* _node;
	RBTree_Iterator(Node* node)
		:_node(node)
	{}
};

然后还是在类里实现一下operator*, operator->, operator!= 和 operator== 这4个运算符重载。

代码语言:javascript

AI代码解释

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;
}

然后在RBTree类里在重命名一下。

代码语言:javascript

AI代码解释

typedef typename RBTree_Iterator<T, T&, T*> Iterator;
typedef typename RBTree_Iterator<T, const T&, const T*> const_Iterator;

2.2 迭代器
2.2.1 begin和end

以下图为例,map和set的迭代器⾛的是中序遍历,左⼦树->根结点->右⼦树,那么 begin() 会返回 中序第⼀个 结点的iterator也就是10 所在结点的迭代器。

end()如何表⽰呢?图中,当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18

到根没有⽗亲,没有找到孩⼦是⽗亲左的那个祖先,这是⽗亲为空了,那我们就把it中的结点指针

置为nullptr,我们⽤ nullptr去充当end

RBTree.hRBTree类中public实现。

代码语言:javascript

AI代码解释

Iterator Begin()
{
	Node* cur = _root;
	while (cur && cur->_left)
	{
		cur = cur->_left;  //找最左节点
	}
	return Iterator(cur);
}

const_Iterator Begin() const //const迭代器
{
	Node* cur = _root;
	while (cur && cur->_left)
	{
		cur = cur->_left;  //找最左节点
	}
	return Iterator(cur);
}

代码语言:javascript

AI代码解释

Iterator End()
{
	return Iterator(nullptr);
}

const_Iterator End() const //const迭代器
{
	return Iterator(nullptr);
}

Myset.hset类里要封装一下。

代码语言:javascript

AI代码解释

public:
	typedef typename RBTree<K, K, Set_Key_Of_T>::Iterator iterator;
    typedef typename RBTree<K, K, Set_Key_Of_T>::const_Iterator const_iterator;
	iterator begin()
	{
		return _t.Begin();
	}
	iterator end()
	{
		return _t.End();
	}

    const_iterator begin() const  //const迭代器
	{
		return _t.Begin();
	}
	const_iterator end() const
	{
		return _t.End();
	}

Mymap.hmap类里也要封装一下。

代码语言:javascript

AI代码解释

public:
	typedef typename RBTree<K, pair<K, V>, Map_Key_Of_T>::Iterator iterator;
    typedef typename RBTree<K, pair<K, V>, Map_Key_Of_T>::const_Iterator const_iterator;
	iterator begin()
	{
		return _t.Begin();
	}
	iterator end()
	{
		return _t.End();
	}

    const_iterator begin() const  //const迭代器
	{
		return _t.Begin();
	}
	const_iterator end() const
	{
		return _t.End();
	}
2.2.2 ++和--

迭代器++的核⼼逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下⼀个结点。

  • 迭代器++时,如果it指向的结点的右⼦树不为空,代表当前结点已经访问完了,要访问下⼀个结点是右⼦树的中序第⼀个,⼀棵树中序第⼀个是最左结点,所以直接找右⼦树的最左结点即可。
  • 迭代器++时,如果it指向的结点的右⼦树空,代表当前结点已经访问完了且当前结点所在的⼦树也访问完了,要访问的下⼀个结点在当前结点的祖先⾥⾯,所以要沿着当前结点到根的祖先路径向上找。

如果当前结点是⽗亲的左,根据中序左⼦树->根结点->右⼦树,那么下⼀个访问的结点就是当前结点的⽗亲。

如下图:it指向25,25右为空,25是30的左,所以下⼀个访问的结点就是30。


 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐