[C++] 21. 红黑树封装map&&set

源码下载

https://gitee.com/Lengggsiyu/cpp_code/tree/master/stl30

一. STL源码中的封装逻辑

1)map

2)set

根据我们对map和set的了解,map是key_value结构,这和我们看到的底层一样。
但是怎么set底层也是key_value,不应该只有key么?
这就要看看封装set的红黑树的结构了。

3)rb_tree

  1. 红黑树的第二个模板参数value传什么,红黑树的节点中存的就是什么类型的数据。这样通过模板参数写成泛型就可以让map、set复用同一棵红黑树。set实例化rb_tree时第二个模板参数给的是key,map实例化rb_tree时第二个模板参数给的是pair<const key,T>,这样一棵红黑树既可以实现key搜索场景的set,也可以实现key_value搜索场景的map。

  2. rb_tree第二个模板参数Value已经控制了红黑树结点中存储的数据类型,为什么还要传第一个模板参数Key呢?尤其是set,两个模板参数是一样的。

① 对于map和set,find/erase时的函数参数都是Key,所以第一个模板参数是传给find/erase等函数做形参的类型的。

② 对于set而言两个参数(key_type和value_type)是一样的(都是key);但是对于map而言就完全不一样了(第一个是key,第二个是pair<const key,T>),map insert的是pair对象,但是find和erase的是Key对象。

4)实例化map&&set的传参过程

二. 模拟实现map&&set

1)实现泛型红黑树结构&&封装map和set框架&&KeyOfT

之前我们实现的是key_value的红黑树,现在把之前的代码改造一下,变成泛型的,能同时被map和set复用。

cpp 复制代码
// 结点里存什么类型的数据由传过来的模板参数T决定
template <class T>
struct RBtreeNode
{
	T _data;
	RBtreeNode<T>* _left;
	RBtreeNode<T>* _right;
	RBtreeNode<T>* _parent;
	Color _color;

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

2)1. 结点数据的类型变成泛型,我们的各种比较逻辑也要变了。之前我们是取出pair的first(也就是key)来比较,但是现在_data不一定是pair不能随便取first。

  1. pair对象本身也是可以比较的,但是他比较的逻辑是,先拿first比,first比完还会再拿second比。这不符合我们期望的比较逻辑,对于map我们只希望他的first参与比较。

  2. 那么如何处理能让比较的逻辑同时适合key和key_value?

① 说到控制比较逻辑,很容易想到仿函数。给rbtree加一个模板参数,将map和set各自的仿函数(map和set各自知道自己的数据是key还是key_value,也就可以控制比较逻辑,该不该取first)通过模板参数传给rbtree在仿函数里控制比较逻辑,但是这种方法是行不通的。因为下图中的两种场景,一个是插入时的比较逻辑,一个是查找时的比较逻辑,_data要比较的类型不是只有T(key或pair),还有可能直接是K。连仿函数的参数都确定不了。

② 实际库里面的做法还是用到了仿函数,并通过增加一个模板参数,接受map和set各自的仿函数。只不过这个仿函数的作用不是直接控制比较逻辑,而是**从rbtree的第二个模板参数中取出key。**我们不就是希望只有key参与比较么,不管是什么类型,确保仿函数能取出其中的key部分就好。

2)迭代器

普通迭代器

  1. 因为结点的物理空间也是不连续的,不能用原生指针直接充当迭代器,所以红黑树迭代器的实现思路和框架和list很相似:用一个类封装节点的指针,再通过重载运算符实现像指针一样访问的行为。

  2. 迭代器实现的难点是operator++和operator--的实现,map和set迭代器走的是中序遍历的顺序。
    ++的逻辑规则:

① 迭代器++时,如果it指向的结点的右子树不为空 ,代表当前结点已经访问完了,要访问下一个结点是右子树的中序第一个,一棵树中序第一个是最左结点,所以直接找右子树的最左结点即可。

② 迭代器++时,如果it指向的结点的右子树为空 ,代表当前结点已经访问完了且当前结点所在的子树也访问完了,要访问的下一个结点在当前结点的祖先里面,所以要沿着当前结点到根的祖先路径向上找。
那么是哪个祖先呢? cur == parent->_left 时的parent结点,因为孩子如果是父亲的右,孩子完了父亲也就完了。只有孩子是父亲的左,根据中序遍历的左根右,左完了才能轮到根。

不过也有可能一路找到了根节点也没有符合cur== parent->_left情况的祖先,此时parent为空,代表整棵树都已经遍历完了。

  1. begin() 返回中序的第一个节点,也就是上图中的10。

那么end() 返回最后一个节点的下一个位置应该如何表示呢?

如上图:当it指向50,++it 时,50是40的右,40是30的右,30是18的右,18到根了没有父亲,没有找到孩子是父亲左的那个祖先,这时父亲为空,那我们就把it中的结点指针置为nullptr,用nullptr去充当end。

sgi_stl源码中,红黑树增加了一个哨兵位头结点 做为end(),这哨兵位头结点和根互为父亲,左指向最左结点,右指向最右结点 (在插入删除的过程中要实时维护)。

相比我们用nullptr作为end(),各有利弊,直接用空做end()需要在重载operator--时特殊处理一下,让迭代器结点指向最右结点。

  1. 迭代器--的实现 跟++的思路完全类似,只是逻辑正好反过来,因为他访问顺序是右子树->根结点->左子树。对end()位置--一定要注意要特殊处理。
cpp 复制代码
self& operator--()
{
	// 期望--end()能跳到最右节点
	if (_node == nullptr)
	{
		Node* maxright = _root;
		while (maxright && maxright->_right)
		{
			maxright = maxright->_right;
		}
		_node = maxright;
		return *this;
	}
	// 左子树不为空,找左子树最右结点
	else if (_node->_left)
	{
		_node = _node->_left;
		while (_node->_right)
			_node = _node->_right;
		return *this;
	}
	// 左子树为空
	else
	{
		Node* parent = _node->_parent;
		if (parent && _node != parent->_right)
		{
			_node = parent;
			parent = parent->_parent;
		}
		_node = parent;
		return *this;
	}
}

const迭代器

和链表部分思路一致,是普通还是const我们通过模板参数传,从而让编译器自己实例化,避免我么写两份几乎一样的冗余代码。

整体结构比较复杂,一层套一层,其实我感觉只有自己真正从头到尾写一遍才能真正捋清,不然真的很混乱。
map和set封装rbtree,rbtree结构中实例化普通迭代器和const迭代器,再下层普通迭代器和const迭代器都写在一个类模板中,通过传递模板参数区分。

3)key不支持修改

对应的结构中的其他位置不要忘记修改,加上const。

4)operator[] --> 依靠insert

  1. 回顾一下 operator[] 和 insert 的行为:
cpp 复制代码
V& operator[](const K& key)
{
	std::pair<iterator, bool> ret = _tree.Insert({ key, V() });

	// 不管是否插入成功,此时我们想插入的数据一定已经保存在iterator中
	// 先取出iterator
	iterator it = ret.first;

	// 再取出value并返回
	return it->second;
}

三. 具体完整代码

1)rbtree.h

cpp 复制代码
#pragma once
#include <iostream>

// 实现一个Key-value结构的红黑树

enum Color
{
	RED,
	BLACK
};

// 结点里存什么类型的数据由传过来的模板参数T决定
template <class T>
struct RBtreeNode
{
	T _data;
	RBtreeNode<T>* _left;
	RBtreeNode<T>* _right;
	RBtreeNode<T>* _parent;
	Color _color;

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

// 迭代器结构(普通 + const版)
template <class T, class Ref, class Ptr>
class RBtreeIterator
{
	typedef RBtreeNode<T> Node;
	typedef RBtreeIterator<T, Ref, Ptr> self;

private:
	Node* _node;
	Node* _root; // 为了end()--能拿到根

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

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

	// ->有连续解引用的特性(编译器自动处理),要返回一个支持再次接引用的类型
	Ptr operator->() // T*  const T*
	{
		return &(_node->_data);
	}

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

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

	self& operator++()
	{
		// 右子树不为空,找右子树最左结点
		if (_node->_right)
		{
			_node = _node->_right;
			while (_node->_left)
				_node = _node->_left;
			return *this; // 不能直接返回_node,产生临时对象不能给引用
		}
		// 右子树为空
		else
		{
			Node* parent = _node->_parent;
			while (parent && _node != parent->_left)
			{
				_node = parent;
				parent = parent->_parent;
			}
			_node = parent; // 不能直接返回parent,产生临时对象不能给引用
			return *this;
		}
	}
	//self& operator++(int)
	//{
	//
	//}

	self& operator--()
	{
		// 期望--end()能跳到最右节点
		if (_node == nullptr)
		{
			Node* maxright = _root;
			while (maxright && maxright->_right)
			{
				maxright = maxright->_right;
			}
			_node = maxright;
			return *this;
		}
		// 左子树不为空,找左子树最右结点
		else if (_node->_left)
		{
			_node = _node->_left;
			while (_node->_right)
				_node = _node->_right;
			return *this;
		}
		// 左子树为空
		else
		{
			Node* parent = _node->_parent;
			if (parent && _node != parent->_right)
			{
				_node = parent;
				parent = parent->_parent;
			}
			_node = parent;
			return *this;
		}
	}
	//self& operator--(int)
	//{
	//
	//}
};

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*> ConstIterator;

private:
	Node* _root = nullptr;

private:
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;

		// 一定要记住每一个节点中都有一个_parent,不要忘记更新
		parent->_left = subLR;
		if (subLR) // h为0的情况
			subLR->_parent = parent;

		subL->_right = parent;
		parent->_parent = subL;

		// 旋转完这个局部子树,要看看pparent是否为空
		// 也就是原本的parent是不是整棵树的根,他还有没有父亲
		if (pparent == nullptr)
		{
			// 直接把新根给_root
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
				pparent->_left = subL;
			else
				pparent->_right = subL;

			subL->_parent = pparent;
		}
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;

		// 一定要记住每一个节点中都有一个_parent,不要忘记更新
		parent->_right = subRL;
		if (subRL) // h为0的情况
			subRL->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		// 旋转完这个局部子树,要看看pparent是否为空
		// 也就是原本的parent是不是整棵树的根,他还有没有父亲
		if (pparent == nullptr)
		{
			// 直接把新根给_root
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
				pparent->_left = subR;
			else
				pparent->_right = subR;

			subR->_parent = pparent;
		}
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)	return;

		KeyOfT kot;
		_InOrder(root->_left);
		std::cout << kot(root->_data) << " ";
		_InOrder(root->_right);
	}

	// 统计节点个数
	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}


public:
	Iterator Begin()
	{
		// 中序第一个--最左节点
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return Iterator(cur, _root);
	}

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

	ConstIterator Begin() const
	{
		// 中序第一个--最左节点
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return ConstIterator(cur, _root);
	}

	ConstIterator End() const
	{
		return ConstIterator(nullptr,_root);
	}

	std::pair<Iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK;
			return { Iterator(_root, _root), true};
		}

		KeyOfT kot;
		Node* cur = _root;
		Node* parent = nullptr;
		// 找空位
		while (cur)
		{
			if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else return { Iterator(cur, _root), false };

		}

		cur = new Node(data);
		Node* newnode = cur; // 记录插入位置,留着返回
		cur->_color = RED;
		// 先直接插入
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		// parent第一次确实是一定存在的,但是我们这是一个循环向上处理的逻辑
		// 当处理到根节点时,根节点没有父亲
		// 如果父亲存在且为红,根据叔叔决定处理方式,并向上处理
		while (parent && parent->_color == RED)
		{
			// 通过爷爷找叔叔
			Node* grandfather = parent->_parent;
			// 叔叔在右
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;

				// 如果叔叔存在且为红,直接变色并向上处理
				if (uncle && uncle->_color == RED)
				{
					// 变色
					uncle->_color = parent->_color = BLACK;
					grandfather->_color = RED;
					// 向上移动更新
					cur = grandfather; // !!颜色改变可能影响上级的是爷爷,成为新的cur
					parent = cur->_parent;
				}
				// 叔叔不存在 或者 存在且为黑,变色+旋转(单旋?双旋?)
				else
				{
					// 变色+右旋
					//if (parent == grandfather->_left && cur == parent->_left) // uncle在右,parent一定在左
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					// 变色+左右双旋
					if (cur == parent->_right)
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break; // 不再继续向上更新
				}
			}
			// 叔叔在左
			else
			{
				Node* uncle = grandfather->_left;

				// 叔叔存在且为红
				if (uncle && uncle->_color == RED)
				{
					// 变色
					uncle->_color = parent->_color = BLACK;
					grandfather->_color = RED;
					// 向上移动更新
					cur = grandfather; // !!颜色改变可能影响上级的是爷爷,成为新的cur
					parent = cur->_parent;
				}
				// 叔叔不存在 或者 存在且为黑,变色+旋转(单旋?双旋?)
				else
				{
					// 变色+左旋
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					// 变色+左右双旋
					if (cur == parent->_left)
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
		}
		// 如果没处理到根根还是黑的,但是可能处理根时将根变为红,需要变回来
		// 简单处理,无论是否处理到根,最后都将根置黑一次
		_root->_color = BLACK;

		// 如果父亲存在且为黑,直接插入结束,return true;(不止这一种情况return true;)
		return {Iterator(newnode, _root), true};
	}

	Iterator Find(const K& key)
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < key)	cur = cur->_right;
			else if (kot(cur->_data) > key)	cur = cur->_left;
			else return Iterator(cur, _root);
		}
		return End();
	}

	void InOrder()
	{
		_InOrder(_root);
		std::cout << std::endl;
	}

	int Size()
	{
		return _Size(_root);
	}
};

2)mymap.h

cpp 复制代码
#pragma once
#include "rb_tree.h"


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

	private:
		RBtree<K, std::pair<const K, V>, mapKeyOfT> _tree;

	public:
		typedef typename RBtree<K, std::pair<const K, V>, mapKeyOfT>::Iterator iterator;
		typedef typename RBtree<K, std::pair<const K, V>, mapKeyOfT>::ConstIterator const_iterator;

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

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

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

		const_iterator end() const
		{
			return _tree.End();
		}

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

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

		V& operator[](const K& key)
		{
			std::pair<iterator, bool> ret = _tree.Insert({ key, V() });

			// 不管是否插入成功,此时我们想插入的数据一定已经保存在iterator中
			// 先取出iterator
			iterator it = ret.first;

			// 再取出value并返回
			return it->second;
		}
	};

	void Print(const map<std::string, std::string>& m)
	{
		for (const auto& e : m)
		{
			std::cout << e.first << " : " << e.second << std::endl;
		}
	}

	void testmap01()
	{
		map<std::string, std::string> m;
		m.insert({ "sort", "排序"});
		m.insert({ "left", "左边" });
		m.insert({ "right", "右边" });
		m.insert({ "string", "字符串" });

		// 需要迭代器支持
		for (auto& e : m)
		{
			// key值不支持修改
			//e.first += 'm';
			e.second += 'm';
			std::cout << e.first << " : " << e.second << std::endl;
		}

		Print(m);

		// find
		map<std::string, std::string>::iterator it = m.find("string");
		std::cout << it->first << " : " << it->second << std::endl;
	}

	void testmap02() // 统计次数 -- operator[]
	{
		std::string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果",
							"苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
		map<std::string, int> countmap;

		for (const auto& str : arr)
		{
			countmap[str]++; // 这种写法更简洁
		}

		for (const auto& e : countmap)
			std::cout << e.first << " : " << e.second << std::endl;
		std::cout << std::endl;
	}
}

3)myset.h

cpp 复制代码
#pragma once
#include "rb_tree.h"


namespace laosi
{
	template <class K>
	class set
	{
		struct setKeyOfT
		{
			K operator()(const K& k)
			{
				return k;
			}
		};

	private:
		RBtree<K, const K, setKeyOfT> _tree;

	public:
		typedef typename RBtree<K, const K, setKeyOfT>::Iterator iterator;
		typedef typename RBtree<K, const K, setKeyOfT>::ConstIterator const_iterator;

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

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

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

		const_iterator end() const
		{
			return _tree.End();
		}

		std::pair<iterator, bool> insert(const K& k)
		{
			return _tree.Insert(k);
		}

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


	void Print(const set<int>& s)
	{
		set<int>::const_iterator it = s.end();
		while (it != s.begin())
		{
			//*it += 1;
			--it;
			std::cout << *it << " ";
		}
		std::cout << std::endl;
	}
	void testset01()
	{
		set<int> s;
		s.insert(5);
		s.insert(1);
		s.insert(14);
		s.insert(77);
		s.insert(2);
		s.insert(1);
		s.insert(30);

		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			//std::cout << ++(*it) << " "; // key值不可修改
			++it;
		}
		std::cout << std::endl;

		Print(s);

		// find
		std::cout << *(s.find(77)) << std::endl;
		//std::cout << *(s.find(100)) << std::endl; // 不能对空指针解引用
	}
}

4)test.cpp

cpp 复制代码
#include <iostream>

#include "mymap.h"
#include "myset.h"

int main()
{
	//laosi::set<int> s;
	//laosi::map<int, int> m;

	laosi::testset01();
	laosi::testmap01();
	laosi::testmap02();

	return 0;
}
相关推荐
旖-旎7 小时前
分治(快速选择算法)(3)
c++·算法·leetcode·排序算法·快速选择
xiaoye-duck7 小时前
【C++:哈希表封装】哈希表封装 myunordered_map/myunordered_set 实战:底层原理 + 完整实现
数据结构·c++·散列表
A.A呐7 小时前
【C++第二十三章】C++11
开发语言·c++
亿秒签到8 小时前
L2-007 家庭房产
数据结构·c++·算法
2401_892070988 小时前
【Linux C++ 日志系统实战】日志消息对象 LogMessage 完整实现:流式拼装 + 标准化输出
linux·c++·日志系统·流式日志
paeamecium9 小时前
【PAT甲级真题】- Longest Symmetric String (25)
数据结构·c++·算法·pat考试
A.A呐9 小时前
【C++第二十二章】哈希与散列
c++·算法·哈希算法
wangjialelele10 小时前
从磁盘查找理解 B 树 | B+树:原理、插入、分裂与性能分析
c语言·开发语言·数据结构·c++·b树
Tanecious.10 小时前
蓝桥杯备赛:Day1-P1101 单词方阵
c语言·c++·蓝桥杯
木子墨51610 小时前
LeetCode 热题 100 精讲 | 链表篇:反转链表·环形链表·有序链表·LRU
数据结构·c++·算法·力扣