【C++】map和set的封装

这篇具有很好参考价值的文章主要介绍了【C++】map和set的封装。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 在STL中的map与set

在STL中,map和set都是使用的红黑树

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

map与set在STL中实现是一样的
对于value_type,map的第二个模板参数是pair,而set的第二个模板参数是key
这样写是为了map和set使用同一颗红黑树去复用map和set


set < K > -> rb_tree<K,K>
map<K,V> - > rb_tree<K,pair<const K,V>>

第一个模板参数拿到单独K的类型,使用Find erase接口函数的参数是K
第二个模板参数决定了树的节点是 K 类型 or K V类型

2. 修改自己实现的红黑树

在上一篇文章中 ,实现了红黑树的插入等接口功能,
但是只能对于K V使用,修改模板参数使K或者 K V 都能调用
点击查看: 自己实现的红黑树

修改结构定义

【C++】map和set的封装

原本自己实现的红黑树 模板为 template<class K,class V>
第一个参数代表 key ,第二个参数 代表 value

把第二个参数 改为 T 即 template<class K,class T>
按照STL的写法,使用第二个模板参数决定树的节点

原本的kv包含K V ,但是由于要调用map 和set,所以不知道到底传过来的是什么
所以使用 模板类型的 data 代替


【C++】map和set的封装

在结构定义时,为了让map与set都能调用同一颗红黑树,所以把模板参数改为T
当set要调用时,T变为<K,K>
当map要调用时,T变为<K,pair<const K,V>>

红黑树的insert中如何取到key

【C++】map和set的封装

在insert中由于不知道data代表的是 pair还是K ,所以不能够取first
pair 虽然能够比较,但是不符合预期,所以要自己实现一个仿函数


我们要把key取出来,但是在红黑树中并不知道调用的是 set 还是map,无法知道T代表什么
但是在使用set或者map内部是知道的,所以 分别在map和set内部各自创建一个内部类,其中都写一个operator()


【C++】map和set的封装

在函数模板中添加一个参数,即可找到对应map/set的key值


【C++】map和set的封装

在红黑树内部,使用类实例化一个对象kot,通过kot去调用map/set 中相同的operator() ,取出对应的key值

迭代器

set/map的迭代器是红黑树内部的迭代器

【C++】map和set的封装

【C++】map和set的封装


【C++】map和set的封装


第二个模板参数Ref 第三个模板参数Ptr都是为了迭代器与const迭代器传参时有不同的参数存在,
从而区分普通迭代器与const迭代器


在list的模拟实现中有详细解释 关于 参数Ref 与Ptr 以及operator != -> * 的基本相似的使用

点击查看: 迭代器详细解释

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

若当前右子树不为空,则下一个节点为右树的最左节点即蓝色节点

【C++】map和set的封装

【C++】map和set的封装

6的右子树为空,并且6作为1的右子树,需要继续往上寻找
将parent作为新的cur节点,把爷爷节点作为parent节点
6作为1的右子树,说明1已经遍历完了,所以把1及1的左右子树看作一个整体,1整体是8的左子树,
所以下一个节点返回8


【C++】map和set的封装

若右子树为空,并且孩子作为父节点的左子树,则直接把父节点返回即可

【C++】map和set的封装
operator - -
【C++】map和set的封装

左子树若不为空,则寻找左子树的最右节点

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

cur为当前节点,若cur的左子树为空,并且作为parent的右子树,则直接返回
parent节点

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

begin返回 的是开头的数据 即中序遍历的第一个 即最左节点

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

end返回最后一个数据的下一个 即nullptr

typename 问题
【C++】map和set的封装

加入typename 是因为编译器无法识别RBTree<K, K,setkeyOfT>是静态变量还是类型

在set.h中,寻找到红黑树的迭代器,通过该迭代器调用对应的begin和end ,来实现set的begin和end

map 中 operator[]的实现

【C++】map和set的封装
将insert的返回值设置成迭代器加布尔值
若插入成功,返回新插入节点的迭代器
若插入失败,返回已经有的节点的迭代器


【C++】map和set的封装

在map中,通过设置好的insert返回值来达到[]的作用

operwator [] 详细的解析 ,点击查看迭代器部分 : map和set的使用

解决自己实现的迭代器的key值可以被修改问题
【C++】map和set的封装

自己实现的迭代器的key值可以被修改,但是在STL实际上是不能被修改的


【C++】map和set的封装

在STL中,普通迭代器和const迭代器都是const迭代器


【C++】map和set的封装

在set中同样做出相同的修改,即可解决问题


【C++】map和set的封装
begin调用的是红黑树的普通迭代器,但是return返回的const迭代器
所以正常运行时会报错


【C++】map和set的封装

自己去实现一个普通迭代器类型,当类模板实例化为iterator时,则红框为拷贝构造
当类模板实例化为const_iterator时,通过发生隐式类型转换,使用普通迭代器构造const迭代器
文章来源地址https://www.toymoban.com/news/detail-450097.html

3. 完整代码

RBTree.h

#pragma once
#include<iostream>
#include<cassert>	
using namespace std;
enum colour
{
	RED,//红色 默认为0
	BLACK,//黑色 默认为1
};
template<class T>
struct  RBTreeNode
{

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;//当前节点值 
	colour _col;//表示颜色

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

//迭代器
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)
	{
	}
	//_RBTreeIterator<T,T&,T*> 为普通迭代器
	//这样就可使普通迭代器构造const迭代器 完成隐式类型转换
	_RBTreeIterator(const _RBTreeIterator<T,T&,T*> &it)
		:_node(it._node)
	{
	}

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

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

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

	self& operator++()
	{
		//右子树不为空,则寻找右子树的最左节点
		if (_node->_right)
		{
			Node* subleft = _node->_right;
			while (subleft->_left)
			{
				subleft = subleft->_left;	
			}
			_node = subleft;
		}
		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* subright = _node->_left;
			while (subright->_right)
			{
				subright = subright->_right;
			}
			_node = subright;
		}
		else
		{
			//左子树为空,沿着根路径寻找,找到孩子是父亲的右的那个祖先节点
			Node* cur = _node;
			Node* parent = cur->_parent;


			//若左子树为空,且孩子作为父节点的左子树,就往上处理
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}
		return *this;
	}
};



//仿函数
template<class K, class T, class keyOfT>
class RBTree
{
	typedef RBTreeNode<T>  Node;

public:
	~RBTree()//析构
	{
		_Destroy(_root);
		_root = nullptr;
	}
public:
	typedef	_RBTreeIterator<T, T&, T*> iterator;
	typedef	_RBTreeIterator<T, const T&, const T*> 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);
	}

	Node* Find(const K & key)//查找
	{
		Node* cur = _root;
		keyOfT kot;
		while (cur)
		{
			//kot(cur->_data) 取出当前的key值 
			//_data 有可能为 K 或者 pair类型
			if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	pair<iterator,bool> insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;//若刚插入一个节点,则该节点颜色是黑色
			return make_pair(iterator(_root),true);
		}
		keyOfT kot;
		Node* parent = nullptr;//用于记录cur的前一个节点
		Node* cur = _root;
		while (cur)
		{
			//若插入的值比当前树的值小 插入左边
			//kot(cur->_data) 找到对应的key值
			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(iterator(cur), true);
			}
		}
		cur = new Node(data);
		Node* newnode = cur;
		//再次判断parent当前节点值与插入值大小
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		//cur的上一个节点即为 刚才的记录cur前一个节点的parent
		cur->_parent = parent;

		//当父节点不为NULL并且父节点为红色
		while (parent != nullptr && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;//爷爷节点

			//若父节点为爷爷节点的左子树,则unlce为爷爷节点的右子树
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//       g
				//    p     u
				// c
				//情况1:u存在并且为红,直接变色即可,并继续往上处理
				if (uncle && uncle->_col == RED)
					//若uncle节点为红色,将parent与uncle都置为黑色,爷爷节点置为红色
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况2+3:u不存在或者u存在且为黑,旋转+变色
				else
				{
					//情况2
					//g p c 作为一条直线 所以为单旋
					//    g
					//  p   u
					//c 
					if (cur == parent->_left)
					{
						//右旋转
						RotateR(grandfather);

						//最终p作为最终的根 为黑  g为红 
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况3
					//g p c 作为一条折线 所以为双旋
					//    g
				   //   p   u
				   //     c 
					else
					{
						//左单旋
						RotateL(parent);
						//右单旋
						RotateR(grandfather);
						//最终cur作为最终的根 为黑  g为红 
						cur->_col = BLACK;
						grandfather->_col = RED;
						//父节点继续保持红色
						parent->_col = RED;
					}
					break;
				}
			}

			else//grandfather->_right == parent
				若父节点为爷爷节点的右子树,则unlce为爷爷节点的左子树
			{
				//   g
				// u   p
				//       c
				Node* uncle = grandfather->_left;

				//情况1:u存在并且为红,直接变色即可,并继续往上处理
				if (uncle && uncle->_col == RED)
					//若uncle节点为红色,将parent与uncle都置为黑色,爷爷节点置为红色
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}

				//情况2+3:u不存在或者u存在且为黑,旋转+变色
				else
				{
					//情况2
					//g p c 作为一条直线 所以为单旋
					//    g
					//  u   p
					//        c  
					if (cur == parent->_right)
					{
						//左旋转 
						RotateL(grandfather);

						//最终p作为最终的根 为黑  g为红 
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况3
					//g p c 作为一条折线 所以为双旋
					//   g
				   //  u   p
				   //    c 
					else
					{
						//右单旋
						RotateR(parent);
						//左单旋
						RotateL(grandfather);
						//最终cur作为最终的根 为黑  g为红 
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		//为了避免grandfather节点正好为根时,会被更新成红色的情况
		_root->_col = BLACK;
		return make_pair(iterator(newnode), true);
	}

	void inorder()//中序遍历
	{
		_inorder(_root);
		cout << endl;
	}
	//判断一颗二叉树是否为红黑树
	bool isbalance()
	{
		//检查根是否为黑
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}
		//连续的红色节点
		return _check(_root, 0);
	}

private:
	void _Destroy(Node*root)//销毁
	{
		if (root == nullptr)
		{
			return;
		}
		 //后序遍历
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}
	bool _check(Node* root, int blacknum)
	{
		if (root == nullptr)
		{
			//为空时,blacknum代表一条路径的黑色节点个数
			cout << blacknum << " ";
			return true;
		}
		//blacknum代表黑色节点的个数
		if (root->_col == BLACK)
		{
			blacknum++;
		}
		//若当前节点为红 父节点也为红
		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}
		//遍历整棵树
		return	_check(root->_left, blacknum) && _check(root->_right, blacknum);
	}
	void _inorder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_inorder(root->_left);
		cout << root->_kv.first << " ";
		_inorder(root->_right);
	}
	void RotateL(Node* parent)//左单旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL != nullptr)
		{
			subRL->_parent = parent;
		}
		Node* ppnode = parent->_parent;//记录parent的前一个节点

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

		if (ppnode == nullptr)//说明parent是根即代表整棵树
		{
			_root = subR;//subR作为新的根
			_root->_parent = nullptr;//subR的父节点指向原来的parent,所以要置nullptr
		}
		else//说明旋转的是一部分,所以要跟ppnode相连接
		{
			if (ppnode->_left == parent)//若连接在左子树上
			{
				ppnode->_left = subR;
			}
			else//若连接在右子树上
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;//将subR父节点置为ppnode
		}
	}

	void RotateR(Node* parent)//右单旋
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR != nullptr)
		{
			subLR->_parent = parent;
		}
		Node* ppnode = parent->_parent;//记录parent的父节点
		subL->_right = parent;
		parent->_parent = subL;

		if (ppnode == nullptr)//若旋转整棵树
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else//若旋转整棵树的部分子树
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;//使subL的父节点为ppnode
		}

	}
private:
	Node* _root = nullptr;
};


map.h

#pragma once
#include"RBTree.h"
namespace yzq
{
	template<class K, class V>
	class map
	{
		struct mapkeyOfT
		{
			const K& operator()(const pair<const K,V> & kv)
			{
				return kv.first;
			}
		};
	public:
		//加入typename 是因为编译器无法识别RBTree<K, K,setkeyOfT>是静态变量还是类型
		typedef typename RBTree<K, pair<const K,V>, mapkeyOfT>::iterator  iterator;
		iterator begin()//复用红黑树的begin
		{
			return _t.begin();
		}
		iterator end()//复用红黑树的end
		{
			return _t.end();
		}
	public:
		pair<iterator,bool> insert(const pair<const K, V>& kv)
		{
			//调用红黑树中的insert
			return _t.insert(kv);
		}
		V& operator[](const K&key)
		{
			pair<iterator,bool>ret=_t.insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		RBTree<K, pair<const K, V>,mapkeyOfT> _t;
	};
	void test_map()
	{
		map<int, int> v;
		v.insert(make_pair(1, 1));
		v.insert(make_pair(2, 2));
		v.insert(make_pair(3, 3));

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

set.h

#pragma once
#pragma once
#include"RBTree.h"



namespace yzq
{
	template<class K>
	class set
	{
		struct setkeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		//加入typename 是因为编译器无法识别RBTree<K, K,setkeyOfT>是静态变量还是类型
		typedef typename RBTree<K, K,setkeyOfT>::const_iterator  iterator;
		typedef typename RBTree<K, K, setkeyOfT>::const_iterator  const_iterator;

		iterator begin()//复用红黑树的begin
		{
			return _t.begin();
		}
		iterator end()//复用红黑树的end
		{
			return _t.end();
		}
	public:
		pair<iterator,bool> insert(const K& key)
		{
			return _t.insert(key);
		}
	private:
		RBTree<K, K, setkeyOfT> _t;
	};
	void test_set()
	{
		set<int> v;
		v.insert(1);
		v.insert(5);
		v.insert(2);
		v.insert(8);
		set<int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			
			++it;
		}
		cout << endl;
	}
}

到了这里,关于【C++】map和set的封装的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++【一棵红黑树封装 set 和 map】

    ✨个人主页: 北 海 🎉所属专栏: C++修行之路 🎃操作环境: Visual Studio 2019 版本 16.11.17 红黑树的基本情况我们已经在上一篇文章中学习过了,本文主要研究的是红黑树的实际应用:封装实现 set 和 map ,看看如何通过一棵红黑树满足两个不同的数据结构;在正式封装之前,

    2024年02月11日
    浏览(42)
  • 【C++】map与set容器——红黑树底层封装

    💭STL中,容器大概可分为两种类型——序列式容器和关联式容器。在前面的系列文章中,我们已经介绍了诸多序列式容器,如:vector、list、stack、queue等,它们以序列的形式存储数据。 💭而关联式容器也是一种非常重要的容器。标准的STL关联式容器分为set(集合)和map(映

    2023年04月11日
    浏览(40)
  • 【C++】 使用红黑树模拟实现STL中的map与set

    前面的文章我们学习了红黑树,也提到了C++STL中的map和set的底层其实就是用的红黑树来实现的(而map和set的使用我们前面也学过了)。 既然红黑树我们也学习过了,那这篇文章我们就用红黑树来简单实现一下STL中的map和set,重点是学习它的框架。 上一篇文章我们实现了红黑

    2024年02月12日
    浏览(31)
  • 【C++】用红黑树迭代器封装map和set

    封装有点难 - . - 文章目录 前言 一、红黑树原先代码的修改 二、红黑树迭代器的实现 总结 因为我们要将红黑树封装让map和set使用,所以我们要在原来的基础上将红黑树代码进行修改,最主要的是修改模板参数,下面我们直接进入正题: 首先我们拿出STL中的源代码,看看大佬

    2024年02月06日
    浏览(43)
  • 【C++】使用红黑树进行封装map和set

    🌇个人主页:平凡的小苏 📚学习格言:命运给你一个低的起点,是想看你精彩的翻盘,而不是让你自甘堕落,脚下的路虽然难走,但我还能走,比起向阳而生,我更想尝试逆风翻盘 。 🛸 C++专栏 : C++内功修炼基地 家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真

    2024年02月07日
    浏览(47)
  • Learning C++ No.23【红黑树封装set和map】

    北京时间:2023/5/17/22:19,不知道是以前学的不够扎实,还是很久没有学习相关知识,对有的知识可以说是遗忘了许多,以该篇博客有关知识为例,我发现我对迭代器和模板的有关知识的理解还不够透彻,不知道是对以前知识的遗忘,还是现在所学确实有难度,反正导致我很懵

    2024年02月05日
    浏览(51)
  • 【C++】用一棵红黑树同时封装出map和set

    苦厄难夺凌云志,不死终有出头日。 1. 在源码里面,对于map和set的实现,底层是用同一棵红黑树封装出来的,并不是用了两棵红黑树,一个红黑树结点存key,一个红黑树结点存key,value的键值对,这样的方式太不符合大佬的水准了,实际上在红黑树底层中用了一个模板参数Va

    2023年04月13日
    浏览(43)
  • 【C++】STL——用一颗红黑树封装出map和set

    我们都知道set是K模型的容器,而map是KV模型的容器,但是它俩的底层都是用红黑树实现的,上篇博文中我们模拟实现了一颗红黑树,接下来将对其进行改造,继而用一颗红黑树封装出map和set。 本质上map和set其内部的主要功能都是套用了红黑树现成的接口,只是稍作改动即可

    2023年04月15日
    浏览(35)
  • AVL树,红黑树,红黑树封装map和set

    二叉搜索树虽可以缩短查找的效率,但如果 数据有序或接近有序二叉搜索树将退化为单支树 ,查找元素相当于在顺序表中搜索元素, 效率低下 。因此,咱们中国的邻居俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法: 当向二叉搜索树中插入

    2023年04月25日
    浏览(62)
  • 红黑树封装set和map(插入部分)

    之前我们实现了红黑树的插入的部分,本文主要介绍将之前实现的红黑树封装成map和set。我们是以学习的角度来封装容器,不用非要把库中容器所有功能都实现出来。我们主要目的是学习库中代码设计技巧和模板复用的思想。 我们在实现之前还是和以前一样去看看库中是怎么

    2024年02月07日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包