【C++】红黑树封装实现 map 和 set

一、源码剖析

我们之前在学习 set 和 map 基本使用时就介绍了 set 和 map 底层都是红黑树,但这里有一个问题 – set 是K模型的容器,而 map 是KV模型的容器,那它们底层为什么能同样都使用红黑树呢?我们可以通过阅读源码来解答这个问题。(本文中使用的源码为 SGI 的 stl30 版本)

//map
#include <stl_tree.h>
#include <stl_map.h>
#include <stl_multimap.h>

//set
#include <stl_tree.h>
#include <stl_set.h>
#include <stl_multiset.h>

可以看到,stl map 和 set 头文件中分别包含了三个头文件,其中 stl_tree.h 是红黑树,stl_map.h 和 stl_set.h 是 map 和 set,而 stl_multimap.h 和 stl_multiset.h 则是 multimap 和 multiset;这就是为什么我们使用 multiset 和 multimap 时只需要包 set 和 map 头文件的原因。

//stl_map.h
template <typename _Key, typename _Tp, typename _Compare = std::less<_Key>,typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class map
{
    
    
public:
    typedef _Key                                          key_type;
    typedef std::pair<const _Key, _Tp>                    value_type;
private:
    typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
    key_compare, _Pair_alloc_type> _Rep_type;
    _Rep_type _M_t;
public:
    pair<iterator, bool> insert(const value_type& __x) {
    
     return _M_t._M_insert_unique(__x); }
    size_type erase(const key_type& __x) {
    
     return _M_t.erase(__x); }
    iterator find(const key_type& __x) {
    
     return _M_t.find(__x); }
};

//stl_set.h
template<typename _Key, typename _Compare = std::less<_Key>, typename _Alloc = std::allocator<_Key> >
class set
{
    
    
public:
    typedef _Key     key_type;
    typedef _Key     value_type;
private:
    typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
    key_compare, _Key_alloc_type> _Rep_type;
    _Rep_type _M_t;
public:
	pair<iterator, bool> insert(const value_type& __x)
    	{
    
    
            std::pair<typename _Rep_type::iterator, bool> __p =
              _M_t._M_insert_unique(__x);
            return std::pair<iterator, bool>(__p.first, __p.second);
        }
    size_type erase(const key_type& __x) {
    
     return _M_t.erase(__x); }
    iterator find(const key_type& __x) {
    
     return _M_t.find(__x); }
};

可以看到,set 和 map 的插入删除查找等各种功能都是封装复用的红黑树的接口,对于 map来说,key_type 就是我们平常所理解的 K,但是 value_type 却是 pair 类型,它的两个参数分别是模板参数 _Key 和 _Tp,也就是我们认为的传递给 map 的 K 和 V;

而对于 set 来说,key_type 也是我们平时认为的 K,但是我们发现 set 中居然还有一个变量 value_type,并且 value_type 的类型就是 key_type 的类型,但是 set 不是K模型的容器吗?它里面为什么要设计一个 value_type 变量呢?要解答这个问题,我们还得阅读红黑树的源码。

//stl_tree.h
//红黑树的基类
struct _Rb_tree_node_base
{
    
    
    typedef _Rb_tree_node_base* _Base_ptr;
    typedef const _Rb_tree_node_base* _Const_Base_ptr;

    _Rb_tree_color	_M_color;
    _Base_ptr		_M_parent;
    _Base_ptr		_M_left;
    _Base_ptr		_M_right;
};

//红黑树的节点结构
template<typename _Val>
struct _Rb_tree_node : public _Rb_tree_node_base
{
    
    
	typedef _Rb_tree_node<_Val>* _Link_type;
    _Val _M_value_field;
};

//红黑树
template<typename _Key, typename _Val, typename _KeyOfValue, typename _Compare, typename _Alloc = allocator<_Val> >
class _Rb_tree
{
    
    
protected:
    typedef _Rb_tree_node_base* 		_Base_ptr;
public:
    typedef _Key 				key_type;
    typedef _Val 				value_type;
    typedef value_type* 			pointer;
    typedef value_type& 			reference;
    typedef _Rb_tree_node<_Val>* 		_Link_type;
};

可以看到,红黑树中定义了一个基类 _Rb_tree_node_base,基类中定义了节点的颜色和三叉链结构,然后让 _Rb_tree_node 去继承基类,并在类中增加成员变量 _M_value_field, _M_value_field 的类型是 _val,而这个 _val 恰好是红黑树的第二个模板参数,也就是 map 和 set 传递过来的 value_type,如下:image-20230331115147143

通过这张图相信大家就可以很容易理解为什么 set 和 map 虽然是不同模型的容器,但都可以使用 红黑树 作为底层容器了 – 如果是 map,则红黑树的模板参数 _Val 被实例化为 pair<K,V>,那么红黑树节点中存储的数据的类型也是 pair<K,V>;如果是 set,则红黑树的模板参数 _Val 被实例化为 K,此时红黑树节点中存储的数据的类型就为 K;

也就是说,红黑树完全可以根据第二个模板参数 _Val 的实参类型的不同实例化出符合 set 要求和 符合 map 要求的不同的两个类。到这里有的同学可能会疑惑,既然 _Val 就能区别出 set 和 map,那为什么还要传递第一个模板参数 _Key 呢?这是因为某些函数需要使用 key,比如 find 函数就只能通过 key 进行查询,即在 map 的 find 中不能通过 _Val 即 pair 类型来进行查询。

在明白了 stl 源码是如何解决 set K模型 和 map KV模型 的问题后,我们就可以参照它的做法将我们自己实现的红黑树改造一下:

//节点颜色
enum Color {
    
    
	RED,
	BLACK
};

template<class T>
struct RBTreeNode {
    
    
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _data;
	Color _col;  //节点颜色

	RBTreeNode(const T& data)
		: _data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)  //默认给成红色
	{
    
    }
};

template<class K, class T>
class RBTree {
    
    
	typedef RBTreeNode<T> Node;
public:
	bool insert(const T& data) {
    
    }
};

但是这里还存在一个问题 – 我们在 insert 时需要比较 key 的大小来确定插入位置,在之前模拟实现的红黑树中,我们直接将节点定义为了pair<K,V> 类型,所以我们可以直接取 kv.first 进行比较;但是现在,节点中的数据既可能是 pair 类型,也可能是 key 的类型,所以我们不能再用 data.first,同时由于 stl 中实现的 pair 比较函数并不是单纯比较 first,而是先比较 first,再比较 second:

template <class T1, class T2>
bool operator<  (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs)
{
    
     
    return lhs.first<rhs.first || (!(rhs.first<lhs.first) && lhs.second<rhs.second); 
};

template <class T1, class T2>
bool operator>  (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs) {
    
     return rhs<lhs; }

所以我们也不能使用库中提供的默认比较函数,那么我们就只能自己写一个仿函数来完成红黑树节点数据 _data 的大小比较了;大家看上面的源码可以发现,stl 中也是这样来完成节点比较的 – 红黑树的第三个模板参数 _KeyOfValue:

//set.h
namespace thj {
    
    
	template<class K>
	class set {
    
    
	public:
		struct SetKeyOfT {
    
    
			const K& operator()(const K& k) {
    
    
				return k;
			}
		};

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

	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
};
//RBTree.h
//map: RBTree<K, pair<K,V>, MapKeyOfT> _t;
//set: RBTree<K, K, SetKeyOfT> _t;
template<class K, class T, class KeyOfT>
class RBTree {
    
    
	typedef RBTreeNode<T> Node;
public:
	bool insert(const T& data) {
    
    
		//判断根节点是否为空
		if (_root == nullptr) {
    
    
			_root = new Node(data);
			_root->_col = BLACK;  //根节点的颜色一定为黑
			return true;
		}

		//寻找插入位置
		KeyOfT kot;  //仿函数对象
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur) {
    
    
			if (kot(data) < kot(cur->_data)) {
    
    
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(data) > .kot(cur->_data)) {
    
    
				parent = cur;
				cur = cur->_right;
			}
			else {
    
    
				return false;  //不允许重复节点
			}
		}

		//走到空开始插入,注意这里还需要修改父节点的指向
		//新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
		Node* newNode = new Node(data);
		if (kot(data) < kot(parent->_data))
			parent->_left = newNode;
		else
			parent->_right = newNode;
		newNode->_parent = parent;  //修改父节点指向
        
        //调整节点
    }
private:
    Node* _root;
};

二、红黑树的迭代器

红黑树的迭代器其实和 list 的迭代器差不多 – 单独为迭代器定义一个类,类中重载运算符来实现迭代器的各种行为,比如 operator*() 返回节点中的数据,operator->() 返回节点的地址,operator!=() 比较两个迭代器是否相等;其中,为了满足 const 迭代器返回 const T& 和 const T*,我们需要增加两个模板参数 Ref 和 Ptr;这些东西我们在模拟实现 list 的时候都已经详细讲过了,所以这里不再赘述。红黑树的迭代器和 list 迭代器不同的地方在于红黑树 end() 迭代器的位置以及迭代器如何进行 ++ 与 –

end() 迭代器的位置

STL 明确规定,begin() 与 end() 代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此,begin() 可以放在红黑树中最小节点 (即最左侧节点) 的位置,end() 可以放在最大节点 (最右侧节点) 的下一个位置

为了让 end() 能够指向最右节点的下一个节点, stl 源码中增加了一个哨兵位的头结点,该节点的 left 指向这棵树的最左节点,也就是 begin(),right 指向这棵树的最右节点,parent 指向 nullptr,然后让根的父节点指向它,最右节点的 right 也指向它;所以在 stl 源码的实现中这个哨兵位的头结点就是 end():image-20230331215544625

但是我们前面模拟实现的红黑树并没有设置哨兵位头结点,而重新去改又太麻烦,所以我们下面就直接将 end() 定义为 nullptr 了:

template<class K, class T, class KeyOfT>
class RBTree {
    
    
	typedef RBTreeNode<T> Node;
public:
    //迭代器
    typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;
    
    //begin是树的最左节点
    iterator begin() {
    
    
        if (_root == nullptr)
            return iterator(nullptr);

        Node* left = _root;
        while (left->_left) {
    
    
            left = left->_left;
        }

        return iterator(left);
    }

    //end定义为空
    iterator end() {
    
    
        return iterator(nullptr);
    }

    const_iterator begin() const {
    
    
        if (_root == nullptr)
            return const_iterator(nullptr);

        Node* left = _root;
        while (left->_left) {
    
    
            left = left->_left;
        }

        return const_iterator(left);
    }

    const_iterator end() const {
    
    
        return const_iterator(nullptr);
    }
};

迭代器的 ++ 与 –

由于红黑树是一棵二叉搜索树,那么对红黑树进行中序遍历得到的是一个有序序列,所以红黑树的迭代器 ++ 就应该是跳到中序遍历中下一个节点的位置,-- 则是反过来跳到中序遍历中上一个节点的位置。

红黑树迭代器 ++ 一共分为两种情况:

  1. 若当前节点有右孩子,那么迭代器++跳到右子树的最左节点;
  2. 若当前节点没有右孩子,则迭代器++跳到 当前节点为 父节点的左孩子 的那一个祖先节点

这样说可能有点抽象,我们以一个具体的例子来说:image-20230331221452254

如上,假设当前节点是13,可以看到13有右孩子,那么迭代器++就应该跳到13右孩子的最左节点,也就是15;如果当前节点是11,11没有右孩子,那么迭代器++就应该跳到当前节点为 父节点的左孩子 的那一个祖先节点,也就是13,为什么不能跳到8的位置呢?这是因为中序遍历的遍历顺序为 左 根 右,此时8已经被访问过了,所以我们需要找到 cur == parent->_left 的parent 节点,也就是 13;也只有这样,迭代器++才是按中序来走的。

红黑树迭代器 – 则是将 ++ 完全倒过来:

  1. 若当前节点有左孩子,那么迭代器++跳到左子树的最右节点;
  2. 若当前节点没有左孩子,则迭代器++跳到 当前节点为 父节点的右孩子 的那一个祖先节点

这里我就不再给出示例了,大家可以对照着上面的图自己走一走,看看–是不是按照中序遍历的倒序来走的。

迭代器代码如下:

//红黑树的迭代器
//__RBTreeIterator<T, T&, T*> Self  //普通迭代器
//__RBTreeIterator<T, const T&, const T*> Self  //const迭代器
template<class T, class Ref, class Ptr>
struct __RBTreeIterator {
    
    
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;

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

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

	//++迭代器
	Self& operator++() {
    
    
		//如果右子树不为空,则++迭代器为右子树的最左节点
		if (_node->_right) {
    
    
			Node* cur = _node->_right;
			while (cur->_left)
				cur = cur->_left;
			_node = cur;
		}
		//如果右子树为空,则++迭代器为 cur为父节点的左孩子的那一个祖先节点
		else {
    
    
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur != parent->_left) {
    
    
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

		return *this;
	}

	//迭代器++
	Self& operator++(int) {
    
    
		Self& tmp = *this;
		operator++();
		return tmp;
	}

	//--迭代器
	Self& operator--() {
    
    
		//如果左子树存在,则--迭代器为左子树的最右节点
		if (_node->_left) {
    
    
			Node* cur = _node->_left;
			while (cur->_right)
				cur = cur->_right;
			_node = cur;
		}
		//如果左子树为空,则--迭代器为 cur为父节点的右孩子的那一个祖先节点
		else {
    
    
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right != cur) {
    
    
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

		return *this;
	}

	//迭代器--
	Self& operator--(int) {
    
    
		Self& tmp = *this;
		operator--();
		return tmp;
	}
};

三、模拟实现 set

set 模拟实现的前面部分很简单,在类中定义一个 RBTree 类型的成员变量,然后插入、查找等函数都直接调用红黑树的对应函数即可;set 模拟实现的重难点在于 set 迭代器的实现;

我们可以直接将红黑树的迭代器封装为 set 的迭代器,如下:

//迭代器
//typename的作用是告诉编译器这是一个类型,而不是静态变量
typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

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

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

但是这样会存在一个问题 – 我们可以通过迭代器来修改 key 的值,很显然这是错误的,因为它可能会破坏搜索树的结构:image-20230331223942044

要解决这个问题也很简单,我们直接让 set 的普通迭代器和 const 迭代器都使用红黑树的 const 迭代器来进行封装即可,这样 set 迭代器解引用得到的就是 const K& 了;但是这样做之后发现虽然的确不能通过迭代器来修改 key 的值了,但是这里又发生了新的错误:

//使用红黑树的const迭代器来封装set的普通迭代器
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

image-20230331224524163

image-20230331230411409

仔细观察思考其实可以发现报错是因为 _t.begin() 和 _t.end() 返回的都是红黑树的普通迭代器,而 set 中用于接受返回值的 iterator 其本质封装的是红黑树的 const 迭代器,所以这里报错的原因是 将红黑树的普通迭代器赋值给红黑树的 const 迭代器

这个问题的解决办法有很多,比如 set 的 begin() 和 end() 调用红黑树的 cbegin() 和 cend(),这里我们参考 stl 库中的做法:

iterator begin() const {
    
     return _M_t.begin(); }
iterator end() const {
    
     return _M_t.end(); }

可以看到,stl 库中的解决办法很简单 – 将 begin() 和 end() 修饰为 const 成员函数即可;为什么这样简单的一步就可以呢?其实是因为当普通成员调用 const 成员函数时其权限会被缩小,也就是说,当 begin() 和 end() 变为 const 成员函数时 _t 就只能调用红黑树的 const 迭代器了,此时 iterator 就能够正确接受函数的返回值了;

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

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

image-20230331231911292

set 初步模拟实现的代码如下 – 后面还会进行改动:

#pragma once

#include <utility>
#include "RBTree.h"

namespace thj {
    
    
	template<class K>
	class set {
    
    
	public:
		//仿函数
		struct SetKeyOfT {
    
    
			const K& operator()(const K& k) {
    
    
				return k;
			}
		};

		//迭代器
		//typename的作用是告诉编译器这是一个类型,而不是静态变量
		//使用红黑树的const迭代器来封装set的普通迭代器,从而保证K不能被修改
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

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

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

		//std::pair<iterator, bool> insert(const K& key) {
    
    
		//	//先用红黑树的普通迭代器接受插入函数的返回值
		//	std::pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> p = _t.insert(key);
		//	//再用普通迭代器构造const迭代器进行返回--红黑树的const迭代器就是set的普通迭代器
		//	return std::pair< iterator, bool>(p.first, p.second);
		//}

		bool insert(const K& key) {
    
    
			return _t.insert(key);
		}

		iterator find(const K& key) {
    
    
			return _t.find(key);
		}

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

image-20230331232151244


四、模拟实现 map

map 和 set 一样,模拟实现的前面部分很简单如插入、查找都直接复用红黑树的相应函数实现;不同的是,map 是 KV模型 的容器, KV模型 的 key 虽然也不允许修改,因为这可能会破坏搜索树的结构,但是 value 是运行修改的,所以 map 的普通迭代器封装红黑树的普通迭代器即可,而不用将其封装为红黑树的 const 迭代器;

同时,我们也不用担心 map 的 key 被修改的问题,因为 map 在定义红黑树的成员变量时传递给红黑树的 T 模板参数为 pair<const K, V>,它保证了 map 的 key 值不能被修改:

typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

//const K保证了map的key不会被修改
RBTree<K, std::pair<const K, V>, MapKeyOfT> _t;

map 模拟实现的难点在于 operator[](const K& key) 函数的实现 – 在前面map 和 set 的使用那一节中我们知道 map 支持 [] 访问,并且 map 的 operator[] 其实同时具备插入、查找和修改 (修改 value) 的功能,下面我将之前博客中的图放出来便于大家回忆:。image-20230323204954760image-20230323205942366

现在我们的目标是要让自己模拟实现的 map 也支持 [] 操作,即也重载 operator[];很显然,要能够重载 [] 操作符我们首先要修改我们之前模拟实现中 insert 函数和 find 函数的返回值,并且要将 RBTree、map、set 三个地方都修改:

RBTree.h:

//返回值为 pair<iterator, bool>
std::pair<iterator, bool> insert(const T& data) {
    
    
    //判断根节点是否为空
    if (_root == nullptr) {
    
    
        _root = new Node(data);
        _root->_col = BLACK;  //根节点的颜色一定为黑
        return std::make_pair(iterator(_root), true);  
    }

    //寻找插入位置
    KeyOfT kot;  //仿函数对象
    Node* parent = nullptr;
    Node* cur = _root;
    while (cur) {
    
    
        if (kot(data) < kot(cur->_data)) {
    
    
            parent = cur;
            cur = cur->_left;
        }
        else if (kot(data) > kot(cur->_data)) {
    
    
            parent = cur;
            cur = cur->_right;
        }
        else {
    
    
            return std::make_pair(iterator(cur), false);  //不允许重复节点
        }
    }

    //走到空开始插入,注意这里还需要修改父节点的指向
    //新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
    Node* newNode = new Node(data);
    if (kot(data) < kot(parent->_data))
        parent->_left = newNode;
    else
        parent->_right = newNode;
    newNode->_parent = parent;  //修改父节点指向
    
    //调整节点
    return std::make_pair(iterator(newNode), true);
}

//查找
iterator find(const K& key) {
    
    
    Node* cur = _root;
    while (cur) {
    
    
        if (key > cur->_key)
            cur = cur->_right;
        else if (key < cur->_key)
            cur = cur->_left;
        else
            return iterator(cur);
    }

    //cur为空说明找不到
    return end();
}

set.h:

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

iterator find(const K& key) {
    
    
    return _t.find(key);
}

map.h:

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

iterator find(const K& key) {
    
    
    return _t.find(key);
}

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

但是当我们修改完毕后运行程序我们会发现 set.h 中又有报错:image-20230331235841244

image-20230331235934472

其实这次报错还是由红黑树的普通迭代器赋值给 const 迭代器造成的 – insert 函数返回的是由红黑树的普通迭代器以及布尔值构成的键值对,但是接受返回值的却是由红黑树的 const 迭代器 (set 的普通迭代器) 以及布尔值构成的键值对;

那么这次我们还能否像之前一样通过加 const 来解决呢?显然是不能的,这样并不起任何作用;那应该怎么做呢?还是参考源码:

//set的insert
std::pair<iterator, bool> insert(const value_type& __x)
{
    
    
    std::pair<typename _Rep_type::iterator, bool> __p = _M_t._M_insert_unique(__x);
    return std::pair<iterator, bool>(__p.first, __p.second);
}

//红黑树的迭代器
template <class value, class Ref, class Ptr>
struct _rb_tree_iterator : public _rb_tree_base_iterator
{
    
    
    typedef _rb_tree_iterator<value, value&, value*> iterator;
    typedef _rb_tree_iterator<value, Ref, Ptr> self;
    typedef __rb_tree_node<value>* link_ type;
    _rb_tree_iterator(const iterator& it) {
    
     node = it.node; }  //拷贝构造
};

可以看到,stl 中并不是直接将 insert 函数的返回值进行返回,而是先将它的返回值赋值给一个键值对,该键值对由红黑树的普通迭代器和一个布尔值构成,然后再用该键值对构造一个由红黑树的 const 迭代器 (set 的普通迭代器) 和布尔值组成的键值对进行返回,这样就不会发生类型冲突了;

那么为什么红黑树的普通迭代器能够构造出 const 迭代器呢?答案在红黑树的迭代器中 – 可以看到红黑树的迭代器中貌似实现了一个拷贝构造函数,但奇怪的地方在于该函数的参数是一个普通迭代器,而不是 Self,而这就是关键所在:

  1. 当模板实例化为 <T, T&, T*> 时,iterator 和 Self 相同,此时这是一个拷贝构造函数;
  2. 当模板实例化为 <T, const T&, const T*> 时,Self 是 const 迭代器,而 iterator 是普通迭代器,此时这是一个构造函数,它可以用普通迭代器构造出一个 const 迭代器

所以我们可以通过红黑树迭代器类中的 构造函数 来实现用普通迭代器来构造 const 迭代器。

现在我们参照 stl 源码的思路来改造我们的迭代器和 set insert 函数:

__RBTreeIterator:

template<class T, class Ref, class Ptr>
struct __RBTreeIterator {
    
    
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	typedef __RBTreeIterator<T, T&, T*> iterator;  //普通迭代器
	Node* _node;

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

	//当模板实例化为<T, T&, T*>时,iterator和Self相同,这是一个拷贝构造函数
	//当模板实例化为<T, const T&, const T*>时,Self是const迭代器,而iterator是普通迭代器,此时这是一个构造函数,它可以用普通迭代器构造一个const迭代器
	__RBTreeIterator(const iterator& s)
		: _node(s._node)
	{
    
    }
};

set.h:

std::pair<iterator, bool> insert(const k& key) {
    
    
    //先用红黑树的普通迭代器接受插入函数的返回值
    std::pair<typename rbtree<k, k, setkeyoft>::iterator, bool> p = _t.insert(key);
    //再用普通迭代器构造const迭代器进行返回--红黑树的const迭代器就是set的普通迭代器
    return std::pair< iterator, bool>(p.first, p.second);
}

至此,我们使用红黑树来封装 set 和 map 的工作就终于完成了。

map 模拟实现的代码如下:

#pragma once

#include <utility>
#include "RBTree.h"

namespace thj {
    
    
	template<class K, class V>
	class map {
    
    
	public:
		//仿函数
		struct MapKeyOfT {
    
    
			const K& operator()(const std::pair<const K, V>& kv) {
    
    
				return kv.first;
			}
		};

		//迭代器
		//typename的作用是告诉编译器这是一个类型,而不是静态变量
		typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::const_iterator 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();
		}

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

		iterator find(const K& key) {
    
    
			return _t.find(key);
		}

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

	private:
		RBTree<K, std::pair<const K, V>, MapKeyOfT> _t;
	};
};

image-20230401005148133

image-20230401005209898


五、完整代码 (最终实现)

1、RBTree.h

#pragma once

#include <utility>

//节点颜色
enum Color {
    
    
	RED,
	BLACK
};

template<class T>
struct RBTreeNode {
    
    
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _data;
	Color _col;  //节点颜色

	RBTreeNode(const T& data)
		: _data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)  //默认给成红色
	{
    
    }
};

//红黑树的迭代器
template<class T, class Ref, class Ptr>
struct __RBTreeIterator {
    
    
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	typedef __RBTreeIterator<T, T&, T*> iterator;  //普通迭代器
	Node* _node;

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

	//当模板实例化为<T, T&, T*>时,iterator和Self相同,这是一个拷贝构造函数
	//当模板实例化为<T, const T&, const T*>时,Self是const迭代器,而iterator是普通迭代器,
    //此时这是一个构造函数,它可以用普通迭代器构造一个const迭代器
	__RBTreeIterator(const iterator& s)
		: _node(s._node)
	{
    
    }

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

	//++迭代器
	Self& operator++() {
    
    
		//如果右子树不为空,则++迭代器为右子树的最左节点
		if (_node->_right) {
    
    
			Node* cur = _node->_right;
			while (cur->_left)
				cur = cur->_left;
			_node = cur;
		}
		//如果右子树为空,则++迭代器为 cur为父节点的左孩子的那一个祖先节点
		else {
    
    
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur != parent->_left) {
    
    
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

		return *this;
	}

	//迭代器++
	Self& operator++(int) {
    
    
		Self& tmp = *this;
		operator++();
		return tmp;
	}

	//--迭代器
	Self& operator--() {
    
    
		//如果左子树存在,则--迭代器为左子树的最右节点
		if (_node->_left) {
    
    
			Node* cur = _node->_left;
			while (cur->_right)
				cur = cur->_right;
			_node = cur;
		}
		//如果左子树为空,则--迭代器为 cur为父节点的右孩子的那一个祖先节点
		else {
    
    
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right != cur) {
    
    
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

		return *this;
	}

	//迭代器--
	Self& operator--(int) {
    
    
		Self& tmp = *this;
		operator--();
		return tmp;
	}
};

//map: RBTree<K, pair<K,V>, MapKeyOfT> _t;
//set: RBTree<K, K, SetKeyOfT> _t;
template<class K, class T, class KeyOfT>
class RBTree {
    
    
	typedef RBTreeNode<T> Node;
public:
	//-----------------------迭代器-------------------------------//
	typedef __RBTreeIterator<T, T&, T*> iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;

	//begin是树的最左节点
	iterator begin() {
    
    
		if (_root == nullptr)
			return iterator(nullptr);

		Node* left = _root;
		while (left->_left) {
    
    
			left = left->_left;
		}

		return iterator(left);
	}

	//end是为空
	iterator end() {
    
    
		return iterator(nullptr);
	}

	const_iterator begin() const {
    
    
		if (_root == nullptr)
			return const_iterator(nullptr);

		Node* left = _root;
		while (left->_left) {
    
    
			left = left->_left;
		}

		return const_iterator(left);
	}

	const_iterator end() const {
    
    
		return const_iterator(nullptr);
	}
	//-----------------------------------------------------//

	std::pair<iterator, bool> insert(const T& data) {
    
    
		//判断根节点是否为空
		if (_root == nullptr) {
    
    
			_root = new Node(data);
			_root->_col = BLACK;  //根节点的颜色一定为黑
			return std::make_pair(iterator(_root), true);
		}

		//寻找插入位置
		KeyOfT kot;  //仿函数对象
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur) {
    
    
			if (kot(data) < kot(cur->_data)) {
    
    
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(data) > kot(cur->_data)) {
    
    
				parent = cur;
				cur = cur->_right;
			}
			else {
    
    
				return std::make_pair(iterator(cur), false);  //不允许重复节点
			}
		}

		//走到空开始插入,注意这里还需要修改父节点的指向
		//新增节点的颜色为默认被初始化为红色,所以这里不需要再显式赋值
		Node* newNode = new Node(data);
		if (kot(data) < kot(parent->_data))
			parent->_left = newNode;
		else
			parent->_right = newNode;
		newNode->_parent = parent;  //修改父节点指向

		cur = newNode;
		//如果父节点颜色为红色,则需要进行调整,且最多调整到根
		while (parent && parent->_col == RED) {
    
    
			Node* grandfater = parent->_parent;  //祖父节点

			if (grandfater == nullptr)
				break;

			//如果父节点在祖父节点的左边
			if (parent == grandfater->_left) {
    
    
				Node* uncle = grandfater->_right;  //叔叔节点

				//情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
				if (uncle && uncle->_col == RED) {
    
    
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上调整
					cur = grandfater;
					parent = cur->_parent;
				}
				//情况二:叔叔不存在或叔叔存在且为黑
				else {
    
    
					//如果cur在parent的左边,则右单旋--注意走到这里p已经是g的左边了
					if (cur == parent->_left) {
    
    
						rotateR(grandfater);

						//更新节点颜色
						parent->_col = BLACK;
						cur->_col = grandfater->_col = RED;
					}
					else {
    
      //左右双旋
						//先以parent为轴进行左单旋
						rotateL(parent);
						//再以grandfather进行右单旋
						rotateR(grandfater);

						//更新节点颜色--此时cur为子树的根节点
						cur->_col = BLACK;
						parent->_col = grandfater->_col = RED;
					}

					//最后需要跳出循环,因为此时不会再影响上层
					break;
				}
			}
			//如果父节点在祖父节点的右边
			else {
    
    
				Node* uncle = grandfater->_left;  //叔叔节点

				//情况一:叔叔存在且为红--父和叔叔变黑,爷爷变红,继续向上调整
				if (uncle && uncle->_col == RED) {
    
    
					parent->_col = BLACK;

					uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上调整
					cur = grandfater;
					parent = cur->_parent;
				}
				//情况二:叔叔不存在或叔叔存在且为黑
				else {
    
    
					//如果cur在parent的右边,则左单旋--注意走到这里p已经是g的右边了
					if (cur == parent->_right) {
    
    
						rotateL(grandfater);

						//更新节点颜色
						parent->_col = BLACK;
						cur->_col = grandfater->_col = RED;
					}
					else {
    
      //右左双旋
						//先以parent为轴进行右单旋
						rotateR(parent);
						//再以grandfather进行左单旋
						rotateL(grandfater);

						//更新节点颜色--此时cur为子树的根节点
						cur->_col = BLACK;
						parent->_col = grandfater->_col = RED;
					}

					//最后需要跳出循环,因为此时不会再影响上层
					break;
				}
			}
		}

		//在循环外面将_root的颜色重新置黑,避免循环内部将根节点的颜色变红了
		_root->_col = BLACK;

		return std::make_pair(iterator(newNode), true);
	}

	//查找
	iterator find(const K& key) {
    
    
		Node* cur = _root;
		while (cur) {
    
    
			if (key > cur->_key)
				cur = cur->_right;
			else if (key < cur->_key)
				cur = cur->_left;
			else
				return iterator(cur);
		}

		//cur为空说明找不到
		return end();
	}

	//左单旋--右右
	void rotateL(Node* parent) {
    
    
		Node* subR = parent->_right;  //右子树
		Node* subRL = subR->_left;  //右子树的左子树

		//右子树的左子树链接到parent的右边
		parent->_right = subRL;
		if (subRL)  //subR可能为空(h==0)
			subRL->_parent = parent;

		//parent链接到右子树的左边
		subR->_left = parent;
		Node* pparent = parent->_parent;  //保存parent的父节点
		parent->_parent = subR;

		//让subR成为子树的根
		if (pparent) {
    
    
			if (pparent->_left == parent) {
    
    
				pparent->_left = subR;
				subR->_parent = pparent;
			}
			else {
    
    
				pparent->_right = subR;
				subR->_parent = pparent;
			}
		}
		else {
    
      //如果parent的parent为空,说明parent是整棵树根节点,此时subR成为新的根节点
			subR->_parent = nullptr;
			_root = subR;
		}
	}

	//右单旋--左左
	void rotateR(Node* parent) {
    
    
		Node* subL = parent->_left;  //左子树
		Node* subLR = subL->_right;  //左子树的右子树

		//将左子树的右子树链接到根的左边
		parent->_left = subLR;
		if (subLR)  //应对h==0的情况
			subLR->_parent = parent;

		//将根链接到左子树的右边
		subL->_right = parent;
		Node* pparent = parent->_parent;  //记录父节点的父节点
		parent->_parent = subL;

		//让subL成为子树的根
		if (pparent) {
    
    
			if (pparent->_left == parent) {
    
    
				pparent->_left = subL;
				subL->_parent = pparent;
			}
			else {
    
    
				pparent->_right = subL;
				subL->_parent = pparent;
			}
		}
		else {
    
      //如果parent的parent为空,说明parent是整棵树的根节点,此时subL成为新的根节点
			subL->_parent = nullptr;
			_root = subL;
		}
	}

private:
	Node* _root = nullptr;
};

2、set.h

#pragma once

#include <utility>
#include "RBTree.h"

namespace thj {
    
    
	template<class K>
	class set {
    
    
	public:
		//仿函数
		struct SetKeyOfT {
    
    
			const K& operator()(const K& k) {
    
    
				return k;
			}
		};

		//迭代器
		//typename的作用是告诉编译器这是一个类型,而不是静态变量
		//使用红黑树的const迭代器来封装set的普通迭代器,从而保证K不能被修改
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
		
		iterator begin() const {
    
    
			return _t.begin();
		}

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

		std::pair<iterator, bool> insert(const K& key) {
    
    
			//先用红黑树的普通迭代器接受插入函数的返回值
			std::pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> p = _t.insert(key);
			//再用普通迭代器构造const迭代器进行返回--红黑树的const迭代器就是set的普通迭代器
			return std::pair< iterator, bool>(p.first, p.second);
		}

		iterator find(const K& key) {
    
    
			return _t.find(key);
		}

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

3、map.h

#pragma once

#include <utility>
#include "RBTree.h"

namespace thj {
    
    
	template<class K, class V>
	class map {
    
    
	public:
		//仿函数
		struct MapKeyOfT {
    
    
			const K& operator()(const std::pair<const K, V>& kv) {
    
    
				return kv.first;
			}
		};

		//迭代器
		//typename的作用是告诉编译器这是一个类型,而不是静态变量
		typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::const_iterator 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();
		}

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

		iterator find(const K& key) {
    
    
			return _t.find(key);
		}

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

	private:
		RBTree<K, std::pair<const K, V>, MapKeyOfT> _t;
	};
};

4、test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
#include <utility>
using namespace std;
#include "RBTree.h"
#include "map.h"
#include "set.h"
#include <string>

void set_test1() {
    
    
	int a[] = {
    
     16, 3, 7, 11, 9, 26, 18, 14, 15 };
	thj::set<int> s;
	for (auto e : a)
		s.insert(e);

	thj::set<int>::iterator it = s.begin();
	while (it != s.end()) {
    
    
		//*it = 10;
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

void map_test1() {
    
    
	int a[] = {
    
     16, 3, 7, 11, 9, 26, 18, 14, 15 };
	thj::map<int, int> m;
	for (auto e : a)
		m.insert(std::make_pair(e, e));

	thj::map<int, int>::iterator it = m.begin();
	while (it != m.end()) {
    
    
		//it->first++;
		it->second++;
		cout << it->first << ":" << it->second << " ";
		++it;
	}
	cout << endl;
}

void map_test2() {
    
    
	string arr[] = {
    
     "苹果", "西瓜", "芒果", "西瓜", "苹果", "梨子", "西瓜","苹果", "香蕉", "西瓜", "香蕉" };
	thj::map<string, int> countMap;
	for (auto& str : arr)
		countMap[str]++;

	for (auto& kv : countMap)
		cout << kv.first << ":" << kv.second << " ";
	cout << endl;
}

void map_test3() {
    
    
	srand((size_t)time(0));
	thj::map<int, int> m;
	int N = 1000;
	for (int i = 0; i < N; i++) {
    
    
		m.insert(make_pair(rand(), rand()));
	}

	auto it = m.begin();
	while (it != m.end()) {
    
    
		cout << it->first << endl;
		++it;
	}
}

int main() {
    
    
	//set_test1();
	//map_test1();
	map_test2();
	//map_test3();

	return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_62391199/article/details/129928618