C++:map和set的封装原理

文章目录

  • 红黑树的封装
  • map和set的封装
  • 红黑树迭代器的实现
    • operator ++ 和 -- 的实现
      • ++的实现过程
    • 迭代器的其他模块
  • 对于实现const
    • set的解决方案
    • map的解决方案
  • 整体实现

本篇写于红黑树模拟实现后,对map和set进行封装,模拟实现map和set内部的原理

首先,map和set的底层逻辑是红黑树,那么就意味着不再需要像vector/list这样重新进行写入,而是直接在红黑树的基础上进行适当的封装即可

但是,前面实现的红黑树并不能完美的适配所需要的功能,因此要在一定程度上对红黑树进行改造后,才能很好的使用,那么下面首先要对红黑树进行一定程度的改造

本篇将分析STL库中对红黑树的封装原理,搭建出基础的框架,并进行分析

红黑树的封装

拿出一份关于STL中的源码,分析源码中是如何对这这棵树进行封装的

在红黑树的定义中就不太一样,在前面实现的过程中,是直接将pair键值对当做模板中的参数的,导致只有两种,一种是Key/Key模型,一种是Key/Value模型,而在源码的实现中,则是直接将类型放到模板中来实现,用泛型的思想编程
C++:map和set的封装原理_第1张图片
在map和set中,传参直接将需要传递的参数传到Value处:

C++:map和set的封装原理_第2张图片
因此在封装的时候,就可以这样进行封装,先对我们自己完成的红黑树进行一些改善

首先,对节点进行改善

template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _col;
};

map和set的封装

下面可以对map和set进行初步封装了

namespace mymap
{
	template<class K, class V>
	class set
	{
	private:
		RBTree<K, pair<K, V>> _t;
	};
}


namespace myset
{
	template<class K>
	class set
	{
	private:
		RBTree<K, K> _t;
	};
}

下一个遇到的问题是,在插入数据的时候如何比较大小呢?例如下面的场景

C++:map和set的封装原理_第3张图片
上面是在模拟实现红黑树的过程中实现的逻辑,现在问题是,如何对于键值对进行比较?

在库函数中寻找对策:

C++:map和set的封装原理_第4张图片
比较的规则是比较键值对的第一个元素,因此现在需要做的就是想办法取出来键值对的第一个元素进行比较,因此就可以采取这样的思想,在map和set中都搞一个能把数据取出来的仿函数,在进行比较的时候返回的是Key即可,因此要修改红黑树的模板参数,接收一个仿函数的值,并用其来参与插入数据时的比较

namespace mymap
{
	template<class K, class V>
	class map
	{
	private:
		struct MapKeyOfT
		{
			const K& operator()(const V& data)
			{
				// 对于map来讲,Key是传入的data键值对中的第一个元素
				return data.first;
			}
		};
		RBTree<K, pair<K, V>, MapKeyOfT> _t;
	};
}

namespace myset
{
	template<class K>
	class set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& data)
			{
				// 对于set来讲,K就是data本身
				return data.first;
			}
		};
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

此时基本逻辑就已经搭建起来了,在map/set中进行插入元素,实际上就是插入到树中,在外部再对这个插入的过程进行一次封装

C++:map和set的封装原理_第5张图片

pair<iterator,bool> insert(const V& val)
{
	return _t.insert(val);
}

红黑树迭代器的实现

于是,要实现出红黑树的迭代器,红黑树的迭代器和链表的迭代器类似,都要借助一个类来构建

template<class T,class Ptr,class Ref>
struct _TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _TreeIterator<T, T*, T&> Self;

	// 构造函数:迭代器的本质就是封装了一层指针
	_TreeIterator(Node* node)
		:_node(node)
	{}

	// 迭代器中功能的实现
	// 解引用取数据
	Ref operator*()
	{
		return _node->_data;
	}

	// 取数据的地址
	Ptr operator->()
	{
		return &(_node->_data);
	}

	//  实现++



	// 迭代器的本质是节点的指针
	Node* _node;
};

operator ++ 和 – 的实现

++的实现过程

C++:map和set的封装原理_第6张图片

以上图为例,此时指向的是17,从正常来说遍历次序是按照中序遍历,因此比17大的下一个数是22,总结出结论就是,++要寻找的是右孩子的最左子树,如果没有右子树如何处理?

C++:map和set的封装原理_第7张图片

如果没有右子树,就说明此时已经到了遍历结束了左子树,如上图所示,此时要找的是,孩子是父亲左的那个节点的祖先

根据上述逻辑,完善迭代器的代码

	//  实现++
	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;
	}

	// 实现--
	Self& operator--()
	{
		//  如果左子树存在,就到左子树中找最右节点
		if (_node->_left)
		{
			// 下一个就是右子树的最左节点
			Node* cur = _node->_left;
			while (cur->_right)
			{
				cur = cur->_right;
			}

			_node = cur;
		}
		// 如果左子树不存在,就到孩子是父亲右的那个祖先
		else
		{
			// 左子树 根 右子树
			Node* cur = _node;
			Node* parent = cur->_parent;
			// 只要孩子是父亲的左,就继续向上循环找,直到找到孩子是父亲右的那个节点
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

迭代器的其他模块

	// 判断相等和不相等,实际上就是看迭代器内部封装的节点是否是同一个节点即可
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}

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

对于实现const

目前来说实现的内容已经可以使用了,但是依旧存在一些问题,比如:

#include 
#include "MyMap.h"
#include "MySet.h"
using namespace std;

void testMap1()
{
	mymap::map<int, int> dict;
	dict.insert(make_pair(1, 1));
	dict.insert(make_pair(2, 1));
	dict.insert(make_pair(3, 1));
	dict.insert(make_pair(4,1));
	
	for (auto& e : dict)
	{
		e.first++;
		cout << e.first << " ";
	}
	cout << endl;
}

void testSet1()
{
	myset::set<int> st;
	st.insert(1);
	st.insert(3);
	st.insert(2);
	auto it = st.begin();
	while (it != st.end())
	{
		(*it)++;
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	testSet1();
	testMap1();
	return 0;
}

C++:map和set的封装原理_第8张图片

这里出现的问题是,map和set中的内容可以被修改,这样会导致整个二叉树已经不是一个二叉搜索树了

在map和set库函数中的解决方案是:

C++:map和set的封装原理_第9张图片
C++:map和set的封装原理_第10张图片

set的解决方案

因此只需要按照库中的方法,就可以解决这个问题

但是,此时又会遇见下一个问题:

C++:map和set的封装原理_第11张图片
问题出现的原因是,这里返回的end迭代器的类型是非const版本,而要将它转换为const版本的迭代器就会出现问题,那么这又如何处理呢?

这里使用一种解决方案,这个解决方案利用了pair的一个比较好的特点:构造的方式非常灵活:

C++:map和set的封装原理_第12张图片

上面是关于pair的构造函数,pair最灵活的一点就是,它可以用两个不相等的模板参数来进行构造,它内部的构造函数本质上是这样写的:

template <class T1, class T2> 
struct pair
{
	template <class U,class V>
	pair(const pair<U, V>& pr)
		:first(pr.first)
		,second(pr.second)
	{}
	T1 first;
	T2 second;
};

也就是说,T1,T2,U,V都可以不相等,只需要它们之间存在合适的构造函数进行构造就可以了,因此利用这一点,将红黑树中的insert的返回值,返回一个节点的指针,利用节点的指针来构造set中返回的值就可以了

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

map的解决方案

map的解决方案是,直接在map的源头根治,把pair的第一个元素设置为const即可

C++:map和set的封装原理_第13张图片

整体实现

改造过后的红黑树

#pragma once
#include 
using namespace std;

enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _col;
};

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

	// 构造函数:迭代器的本质就是封装了一层指针
	_TreeIterator(Node* node)
		:_node(node)
	{}

	// 迭代器中功能的实现
	// 解引用取数据
	Ref operator*()
	{
		return _node->_data;
	}

	// 取数据的地址
	Ptr operator->()
	{
		return &(_node->_data);
	}

	//  实现++
	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;
	}

	// 实现--
	Self& operator--()
	{
		//  如果左子树存在,就到左子树中找最右节点
		if (_node->_left)
		{
			// 下一个就是右子树的最左节点
			Node* cur = _node->_left;
			while (cur->_right)
			{
				cur = cur->_right;
			}

			_node = cur;
		}
		// 如果左子树不存在,就到孩子是父亲右的那个祖先
		else
		{
			// 左子树 根 右子树
			Node* cur = _node;
			Node* parent = cur->_parent;
			// 只要孩子是父亲的左,就继续向上循环找,直到找到孩子是父亲右的那个节点
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	// 判断相等和不相等,实际上就是看迭代器内部封装的节点是否是同一个节点即可
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}

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

	// 迭代器的本质是节点的指针
	Node* _node;
};

template<class K, class T, class KeyOfT>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	typedef _TreeIterator<T, T&, T*> iterator;
	typedef _TreeIterator<T, const T&, const T*> const_iterator;
	//  红黑树的构造函数
	RBTree()
		:_root(nullptr)
	{}

	// 红黑树的析构函数
	~RBTree()
	{
		DelNode(_root);
	}

	// 红黑树迭代器部分
	iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	const_iterator begin() const
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}

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

	// 元素的插入
	pair<Node*, bool> insert(const T& data)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		KeyOfT kot;
		// 根据搜索二叉树的基本逻辑完成
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(_root, true);
		}
		else
		{
			// 插入数据
			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 make_pair(cur, false);
				}
			}
			// 此时parent指向最后一个节点,cur为空
			cur = new Node(data);
			if (kot(parent->_data) > kot(cur->_data))
			{
				// 如果插入节点小于它的父亲,就插入到左边
				parent->_left = cur;
				cur->_parent = parent;
			}
			else
			{
				// 如果插入节点大于它的父亲,就插入到右边
				parent->_right = cur;
				cur->_parent = parent;
			}
		}

		// 至此,普通二叉搜索树的插入已经完成,该进行红黑树的高度调整了
		// 终止条件是parent为空,或者parent已经是黑色了,就意味着不需要调整了
		// parent是红色,意味着grandparent一定存在
		while (parent && parent->_col == RED)
		{
			// 更变的核心是舅舅,因此要先找到舅舅
			// 整体来说还有两种情况,parent可能是grandparent的左或者右,舅舅就是另外一边
			Node* grandparent = parent->_parent;
			if (parent == grandparent->_left)
			{
				Node* uncle = grandparent->_right;
				// 1. 舅舅存在,并且是红色
				if (uncle && uncle->_col == RED)
				{
					//     g
					//   p   u
					// c

					// 变色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;

					// 向上处理
					cur = grandparent;
					parent = cur->_parent;
				}

				// 2. 舅舅不存在
				else
				{
					// 如果cur是左孩子
					if (cur == parent->_left)
					{
						//     g
						//   p
						// c

						// 对grandparent进行右旋
						RotateR(grandparent);
						// 变色
						cur->_col = grandparent->_col = RED;
						parent->_col = BLACK;
					}
					// 如果cur是右孩子
					else
					{
						//     g               g
						//  p       -->     c         -->    c
						//    c           p                p   g

						// 对parent左旋,对grandparent右旋
						RotateL(parent);
						RotateR(grandparent);
						// 变色
						cur->_col = BLACK;
						parent->_col = grandparent->_col = RED;
					}

					// 更新之后parent和grandparent顺序乱了,而且也不需要继续调整了,直接break
					break;
				}
			}

			// parent是grandparent的右孩子,相同的逻辑再进行一次
			else
			{
				Node* uncle = grandparent->_left;
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandparent->_col = RED;

					// 继续往上处理
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)
					{
						//   g
						//      p
						//         c
						RotateL(grandparent);
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						//     g
						//       p 
						//     c

						RotateR(parent);
						RotateL(grandparent);
						cur->_col = BLACK;
						grandparent->_col = RED;
					}

					break;
				}
			}
		}
		// 不管上面怎么变都无所谓,只需要保证根节点是黑的就可以了
		_root->_col = BLACK;

		return make_pair(cur, true);
	}

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

		parent->_right = subRL;
		subR->_left = parent;

		Node* parentParent = parent->_parent;

		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}

			subR->_parent = parentParent;
		}
	}

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

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

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

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

			subL->_parent = parentParent;
		}
	}

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

		DelNode(root->_left);
		DelNode(root->_right);
		delete(root);
	}

	Node* _root = nullptr;
};

依据红黑树封装出的set

#pragma once

#include "RBTree.h"

namespace myset
{
	template<class K>
	class set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& data)
			{
				// 对于set来讲,K就是data本身
				return data;
			}
		};
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

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

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

		iterator end() const
		{
			return _t.end();
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

依据红黑树构造出的map

#pragma once

#include "RBTree.h"


namespace mymap
{
	template<class K, class V>
	class map
	{
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& data)
			{
				// 对于map来讲,key是键值对的第一个元素
				return data.first;
			}
		};
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
		pair<iterator, bool> insert(const pair<K, V>& val)
		{
			return _t.insert(val);
		}

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

你可能感兴趣的:(C++,#,模拟实现,知识总结,c++,windows,开发语言)