【C++】:map和set的封装

朋友们、伙计们,我们又见面了,本期来给大家解读一下set和map的封装,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

【C++】:map和set的封装_第1张图片

目录

1. stl库中的封装

2. 模拟实现的红黑树改进

2.1 存储数据的类型

2.2 添加提取类型的仿函数

2.2.1 改进红黑树的插入

3. 封装红黑树的迭代器

3.1 operator++逻辑

代码实现:

3.2 operator--逻辑

3.3 其他接口

4. 添加红黑树的迭代器

代码实现:

5. set的封装

5.1 set的插入 

set封装代码:

6. map的封装 

6.1 operator[]

map封装代码:

7. 添加迭代器之后的红黑树完整代码


1. stl库中的封装

set和map底层封装采用了红黑树,如果不了解红黑树的铁铁可以去:【C++】:红黑树

那么我们可以简单的来看一看库中对set和map的封装:

【C++】:map和set的封装_第2张图片

【C++】:map和set的封装_第3张图片

从库中可以看到,在传参的时候红黑树中的第二个模版参数决定的是红黑树中存的是什么类型的数据,这样就可以通过传参调用使set与map用一个红黑树即可,根据库中的红黑树,为了实现set与map的封装,首先对我们自己实现的红黑树做个简单的改进。 

2. 模拟实现的红黑树改进

模拟实现红黑树代码:https://blog.csdn.net/Yikefore/article/details/134885925?spm=1001.2014.3001.5501 

2.1 存储数据的类型

因为要根据第二个模版参数来构造红黑树的节点,所以需要将红黑树的节点结构做一变化:

只需要用一个模版参数来构造即可:

#pragma once

//枚举定义节点颜色
enum Color 
{
	RED,    //红色
	BLACK   //黑色
};

//红黑树节点的定义
template
struct RBTreeNode
{
	RBTreeNode* _left;    //左子树节点
	RBTreeNode* _right;   //右子树节点
	RBTreeNode* _parent;  //父节点
	Color _col;              //节点颜色
	T _data;                 //节点的数据

	//节点的构造
	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_col(RED)
		,_data(data)
	{}
};

2.2 添加提取类型的仿函数

由于红黑树种存储的数据类型是通过传参来决定的,由于红黑树的插入是需要通过比较大小的,那么在接下来的插入逻辑中我们并不知道是key模型还是key_value模型。

① 假设是key模型 -> 那么只比较这一个key的大小即可。

② 假设是key_value模型 -> 那么是需要比较key与key的大小,不需要关心value的大小。

如果我们使用的是map,那么就会传递一个pair,我们不妨来看一下pair默认的比较逻辑:

【C++】:map和set的封装_第4张图片

first和second有一个小就表示小,因此pair的默认比较逻辑是不符合我们的要求的,所以我们需要自己使用仿函数来获取key来进行比较。

    template
	class set
	{
	public:
		//提取set的key
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	private:
		RBTree _tree;
	};


    template
	class map
	{
	public:
		//提取map的key
		struct MapKeyOfT
		{
			const K& operator()(const pair& kv)
			{
				return kv.first;
			}
		};
	private:
		RBTree _tree;
	};

为了让set与key都可以使用同一个红黑树,所以也需要对set的key做以提取,这里就可以保证在红黑树的插入结构中的比较逻辑都用的key进行比较。

2.2.1 改进红黑树的插入

由于我们是根据第二个模板参数来确认红黑树节点的类型,所以再插入比较时需要用提取key的仿函数进行提取,再进行比较:

【C++】:map和set的封装_第5张图片

//插入
	bool Insert(const T& data)
	{
		//为空可以直接插入,并将根节点置为黑色
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		KeyOfT 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 false;
		}
		//链接
		//新插入的节点默认为红色节点
		cur = new Node(data);
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//判断节点的颜色,是否破坏了平衡
		//......
	}

3. 封装红黑树的迭代器

红黑树的迭代器封装是一种类似于链表迭代器的样式,不熟悉的铁铁可以去看看:【STL】:list模拟实现

我们使用一份代码,通过传递参数控制const迭代器和非const迭代器。

3.1 operator++逻辑

红黑树的遍历是一种二叉树的中序遍历: 左子树 根 右子树

因此红黑树遍历出的结果是一个升序。我们以下面这棵红黑树为例来演示一下:

【C++】:map和set的封装_第6张图片

中序遍历的第一个节点是1, 最后一个节点是27

中序遍历结果是 1 6 8 11 13 15 17 22 25 27

中序遍历的第一个节点便是这棵红黑树的最左节点;

++it的核心:找中序遍历的下一个节点;

有两种情况:

1. it指向的当前节点,如果右子树不为空,那么下一个节点就是该右子树的最左节点。

【C++】:map和set的封装_第7张图片

2. it指向的当前节点,如果右子树为空,那么分两种情况:

① it指向的当前节点是父节点的左,那么就表明父节点的左子树已经访问完了,接下来直接访问父节点即可。

【C++】:map和set的封装_第8张图片

② it指向的当前结点是父节点的右,那么就表明以父节点为根的这棵子树全部访问完毕,需要访问下一棵子树,那么就要往上找孩子是父亲左的那个祖先节点。

【C++】:map和set的封装_第9张图片

代码实现:
// 红黑树迭代器
template
struct __TreeIterator
{
	typedef __TreeIterator Self;
	typedef RBTreeNode Node;
	Node* _node;

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

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

3.2 operator--逻辑

operator--的逻辑和operator++逻辑正好相反: 右子树 根 左子树

1. it指向的当前节点,如果左子树不为空,那么下一个节点就是该左子树的最右节点。

2. it指向的当前节点,如果右子树为空,那么分两种情况:

① it指向的当前节点是父节点的右,那么就表明父节点的右子树已经访问完了,接下来直接访问父节点即可。

② it指向的当前结点是父节点的左,那么就表明以父节点为根的这棵子树全部访问完毕,需要访问下一棵子树,那么就要往上找孩子是父亲右的那个祖先节点。

代码就不实现了,只需对operator++代码稍微改动即可。 

3.3 其他接口

operator*、operator->、operator==、operator!=

// 红黑树迭代器
template
struct __TreeIterator
{
	typedef __TreeIterator Self;
	typedef RBTreeNode Node;
	Node* _node;

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

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

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

	Self& operator++()
	{
        //......
	}

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

4. 添加红黑树的迭代器

根据我们上面实现的operator++原理来看的话:begin()是最左节点, end()是nullptr,那么为什么end()是nullptr呢?我们接下来看一下:

还是这一棵红黑树

【C++】:map和set的封装_第10张图片

当我们访问到了最后一个节点27时,它的左右子树都为空,并且它是父亲节点的右子树,再进行一步it++时,它就会往上面找孩子是父亲左的祖先节点,因此就会一直循环往上走,直到走到了13,但是13为根节点,没有父亲节点。

因此,当访问最后一个节点27时,再进行it++,找到的就是nullptr,所以需要将end()设置为nullptr。

【C++】:map和set的封装_第11张图片

代码实现:
//红黑树的实现
// set->RBTree _t;
// map->RBTree, MapKeyOfT> _t;
template
class RBTree
{
public:
	typedef __TreeIterator iterator;
	typedef __TreeIterator const_iterator;

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

public:
	typedef RBTreeNode Node;
	//插入
	bool Insert(const T& data)
	//......

	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//判断是否平衡
	//......

	//高度
	//......

	//节点个数
	//......

	//查找
	//......
private:
	//判断是否平衡
	//......

	//右单旋
	//......

	//左单旋
	//......
private:
	Node* _root = nullptr;
};

5. set的封装

在封装之前我们需要了解一个内嵌类型的细节:

对类模板取内嵌类型需要加上typename 

因为编译器在编译的时候不确定是模板还是类型,不能取到内嵌类型,加上typename就表明是一个类型。

根据set的属性:数据是不允许修改的,所以我们直接将普通迭代器和const迭代器都封装为const迭代器:

typedef typename RBTree::const_iterator iterator;
typedef typename RBTree::const_iterator const_iterator;

5.1 set的插入 

set和map的插入的返回值是一个pair,其中pair第一个参数是迭代器,插入成功表示新插入节点的迭代器,插入失败表示已存在节点的迭代器,第二个参数是bool值,表示插入是否成功。

那么我们就需要对我们的Insert再稍作调整:

我们使用pair的特性,用一个Node*类型的去构造一个iterator类型,这样就保证了iterator到const_iterator的转化:

【C++】:map和set的封装_第12张图片

【C++】:map和set的封装_第13张图片

set封装代码:

#pragma once
#include "RBTree.h"

namespace ywh
{
	template
	class set
	{
	public:
		//提取set的key
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree::const_iterator iterator;
		typedef typename RBTree::const_iterator const_iterator;

	public:
		iterator begin() const
		{
			return _tree.begin();
		}

		iterator end() const
		{
			return _tree.end();
		}
		pair insert(const K& key)
		{
			_tree.Insert(key);
		}
	private:
		RBTree _tree;
	};
}

6. map的封装 

map的封装需要实现operator[],返回的pair的second,需要借助于Insert。

6.1 operator[]

关于operator[]的详细介绍和原理都在【C++】:set和map 中,需要的可以去看一看。

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

map封装代码:

#pragma once
#include "RBTree.h"

namespace ywh
{
	template
	class map
	{
	public:
		//提取map的key
		struct MapKeyOfT
		{
			const K& operator()(const pair& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree, MapKeyOfT>::iterator iterator;
		typedef typename RBTree, MapKeyOfT>::const_iterator const_iterator;

	public:
		iterator begin()
		{
			return _tree.begin();
		}
		const_iterator begin() const
		{
			return _tree.begin();
		}

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

		pair insert(const pair& kv)
		{
			return _tree.Insert(kv);
		}
		V& operator[](const K& key)
		{
			pair ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		RBTree, MapKeyOfT> _tree;
	};
}

7. 添加迭代器之后的红黑树完整代码

#pragma once

//枚举定义节点颜色
enum Color 
{
	RED,    //红色
	BLACK   //黑色
};

//红黑树节点的定义
template
struct RBTreeNode
{
	RBTreeNode* _left;    //左子树节点
	RBTreeNode* _right;   //右子树节点
	RBTreeNode* _parent;  //父节点
	Color _col;              //节点颜色
	T _data;                 //节点的数据

	//节点的构造
	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_col(RED)
		,_data(data)
	{}
};

// 红黑树迭代器
template
struct __TreeIterator
{
	typedef __TreeIterator Self;
	typedef RBTreeNode Node;
	Node* _node;

	__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& s)
	{
		return _node != s._node;
	}
	bool operator==(const Self& s)
	{
		return _node == s._node;
	}
};

//红黑树的实现
// set->RBTree _t;
// map->RBTree, MapKeyOfT> _t;
template
class RBTree
{
public:
	typedef __TreeIterator iterator;
	typedef __TreeIterator const_iterator;

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

public:
	typedef RBTreeNode Node;

	//插入
	//pair Instrt(const T& data)
	pair Insert(const T& data)
	{
		//为空可以直接插入,并将根节点置为黑色
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(_root, true);
		}
		Node* cur = _root;
		Node* parent = nullptr;
		KeyOfT 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(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)
			{
				Node* uncle = grandfather->_right;
				//叔叔节点存在且为红
				if (uncle && uncle->_col == RED)
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//将grandfather变为新的cur继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else  //叔叔节点不存在或者存在且为黑
				{
					if (cur == parent->_left)   //该路径的parent已经是grandfather的左
					{
						//旋转+变色
						Rotate_right(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else                       //cur是parent的右
					{
						//双旋+变色
						Rotate_left(parent);
						Rotate_right(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else //parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				//叔叔节点存在且为红
				if (uncle && uncle->_col == RED)
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//将grandfather变为新的cur继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)   //该路径的parent已经是grandfather的右
					{
						//旋转+变色
						Rotate_left(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else                        //cur是parent的左
					{
						Rotate_right(parent);
						Rotate_left(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		//将根节点再次置黑保持红黑树的平衡
		_root->_col = BLACK;
		return make_pair(newnode, true);
	}

	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//判断是否平衡
	bool IsBalance()
	{
		if (_root == nullptr)
			return true;
		//1.判断根是否为黑
		if (_root->_col == RED)
			return false;

		int standard_val = 0;  	//最左路径的黑色节点个数
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				standard_val++;
			cur = cur->_left;
		}
		int Black_size = 0;
		return Check(_root,standard_val,Black_size);
	}
	//高度
	int Height()
	{
		return _Height(_root);
	}
	//节点个数
	size_t Size()
	{
		return _Size(_root);
	}
	//查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return NULL;
	}
private:
	size_t _Size(Node* root)
	{
		if (root == NULL)
			return 0;

		return _Size(root->_left)
			+ _Size(root->_right) + 1;
	}
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}
	//判断是否平衡
	bool Check(Node* root, const int standard_val, int Black_size)
	{
		if (root == nullptr)
		{
			if (Black_size != standard_val)   //比较黑色节点的个数
			{
				cout << "存在黑色节点数量不相等的路径" << endl;
				return false;
			}
			else
				return true;
		}
		//判断它与它父亲的颜色
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "有连续的红色节点" << endl;

			return false;
		}
		//黑色节点计数器++
		if (root->_col == BLACK)
		{
			Black_size++;
		}
		//递归它的左右子树
		return Check(root->_left, standard_val, Black_size)
			&& Check(root->_right, standard_val, Black_size);
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
	//右单旋
	void Rotate_right(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if(subLR)
			subLR->_parent = parent;
		Node* ppNode = parent->_parent;
		subL->_right = parent;
		parent->_parent = subL;
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
				subL->_parent = ppNode;
			}
			else
			{
				ppNode->_right = subL;
				subL->_parent = ppNode;
			}
		}
	}

	//左单旋
	void Rotate_left(Node* parent)
	{
		Node* subR = parent->_right;  
		Node* subRL = subR->_left; 
		Node* ppNode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			subR->_parent = ppNode;
		}
	}
private:
	Node* _root = nullptr;
};

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!    

你可能感兴趣的:(C++,c++,开发语言,封装)