首页 > 编程语言 >(multi)map和set--C++

(multi)map和set--C++

时间:2024-10-21 22:53:10浏览次数:3  
标签:map multi set const 迭代 -- key type

文章目录

一、序列式容器和关联式容器

前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。

关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。

本章讲解的map和set底层是红黑树,红黑树是一颗平衡二叉搜索树。set是key搜索场景的结构, map是key/value搜索场景的结构。

二、set系列的使用

1、set和multiset参考文档

https://legacy.cplusplus.com/reference/set/

2、set类的介绍

  1. set的声明如下,T就是set底层关键字的类型
  2. set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数
  3. set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。
  4. 一般情况下,我们都不需要传后两个模版参数。
  5. set底层是用红黑树实现,增删查效率是 O(logN),迭代器遍历是走的搜索树的中序,所以是有序的。
  6. 前面部分我们已经学习了vector/list等容器的使用,STL容器接口设计,高度相似,所以这里我们就不需要一个一个的介绍,而是直接看文档,挑比较重要的接口进行介绍。
template < class T, 			// set::key_type/value_type
	class Compare = less<T>, 	// set::key_compare/value_compare
	class Alloc = allocator<T> 	// set::allocator_type
	> class set;

3、set的构造和迭代器

set的构造我们关注以下几个接口即可。

set的支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,set的iterator和const_iterator都不支持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。

// empty (1) ⽆参默认构造
explicit set (const key_compare& comp = key_compare(),
			  const allocator_type& alloc = allocator_type());
			  
// range (2) 迭代器区间构造
template <class InputIterator>
	set (InputIterator first, InputIterator last,
		const key_compare& comp = key_compare(),
		const allocator_type& = allocator_type());

// copy (3) 拷⻉构造
set (const set& x);

// initializer list (5) initializer 列表构造
set (initializer_list<value_type> il,
	const key_compare& comp = key_compare(),
	const allocator_type& alloc = allocator_type());
	
// 迭代器是⼀个双向迭代器
iterator -> a bidirectional iterator to const value_type

// 正向迭代器
iterator begin();
iterator end();

// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

4、set的增删查

关注以下几个接口即可:

Member types
key_type -> The first template parameter (T)
value_type -> The first template parameter (T)
// 单个数据插⼊,如果已经存在则插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);

// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数
size_type count (const value_type& val) const;

// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);

// 返回⼤于等val位置的迭代器
iterator lower_bound (const value_type& val) const;
// 返回⼤于val位置的迭代器
iterator upper_bound (const value_type& val) const;

5、insert和迭代器遍历使用样例:

#include<iostream>
#include<set>
using namespace std;
int main()
{
	// 去重+升序排序
	set<int> s;
	// 去重+降序排序(给⼀个⼤于的仿函数)
	//set<int, greater<int>> s;
	s.insert(5);
	s.insert(2);
	s.insert(7);
	s.insert(5);
	
	//set<int>::iterator it = s.begin();
	auto it = s.begin();
	while (it != s.end())
	{
		// error C3892: “it”: 不能给常量赋值
		// *it = 1;
		cout << *it << " ";
		++it;
	} 
	cout << endl;
	
	// 插⼊⼀段initializer_list列表值,已经存在的值插⼊失败
	s.insert({ 2,8,3,9 });
	for (auto e : s)
	{
		cout << e << " ";
	} 
	cout << endl;
	set<string> strset = { "sort", "insert", "add" };
	// 遍历string⽐较ascll码⼤⼩顺序遍历的
	for (auto& e : strset)
	{
		cout << e << " ";
	} 
	cout << endl;
	return 0;
}

6、find和erase使用样例:

#include<iostream>
#include<set>
using namespace std;
int main()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << " ";
	} 
	cout << endl;
	
	// 删除最⼩值
	s.erase(s.begin());
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	
	// 直接删除x
	int x;
	cin >> x;
	int num = s.erase(x);
	if (num == 0)
	{
		cout << x << "不存在!" << endl;
	} 
	for (auto e : s)
	{
		cout << e << " ";
	} 
	cout << endl;
	
	// 直接查找在利⽤迭代器删除x
	cin >> x;
	auto pos = s.find(x);
	if (pos != s.end())
	{
		s.erase(pos);
	} 
	else
	{
		cout << x << "不存在!" << endl;
	} 
	for (auto e : s)
	{
		cout << e << " ";
	} 
	cout << endl;
	
	// 算法库的查找 O(N)
	auto pos1 = find(s.begin(), s.end(), x);
	// set⾃⾝实现的查找 O(logN)
	auto pos2 = s.find(x);
	
	// 利⽤count间接实现快速查找
	cin >> x;
	if (s.count(x))
	{
		cout << x << "在!" << endl;
	} 
	else
	{
		cout << x << "不存在!" << endl;
	} 
	return 0;
}
#include<iostream>
#include<set>
using namespace std;
int main()
{
	std::set<int> myset;
	for (int i = 1; i < 10; i++)
		myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
	for (auto e : myset)
	{
		cout << e << " ";
	} 
	cout << endl;
	
	// 实现查找到的[itlow,itup)包含[30, 60]区间
	
	// 返回 >= 30
	auto itlow = myset.lower_bound(30);
	// 返回 > 60
	auto itup = myset.upper_bound(60);
	
	// 删除这段区间的值
	myset.erase(itlow, itup);
	
	for (auto e : myset)
	{
		cout << e << " ";
	} 
	cout << endl;
	return 0;
}

7、multiset和set的差异

multiset和set的使用基本完全类似,主要区别点在于multiset支持值冗余,那么insert/find/count/erase都围绕着支持值冗余有所差异,具体参看下面的样例代码理解。

#include<iostream>
#include<set>
using namespace std;
int main()
{
	// 相⽐set不同的是,multiset是排序,但是不去重
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	} 
	cout << endl;
	
	// 相⽐set不同的是,x可能会存在多个,find查找中序的第⼀个
	int x;
	cin >> x;
	auto pos = s.find(x);
	while (pos != s.end() && *pos == x)
	{
		cout << *pos << " ";
		++pos;
	} 
	cout << endl;
	
	// 相⽐set不同的是,count会返回x的实际个数
	cout << s.count(x) << endl;
	
	// 相⽐set不同的是,erase给值时会删除所有的x
	s.erase(x);
	for (auto e : s)
	{
		cout << e << " ";
	} 
	cout << endl;
	return 0;
}

三、map系列的使用

1、map和multimap参考文档

https://legacy.cplusplus.com/reference/map/

2、map类的介绍

map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,
set默认要求Key支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。
map底层是用红黑树实现,增删查改效率是 O(logN) ,迭代器遍历是走的中序,所以是按key有序顺序遍历的。

template < class Key, 				// map::key_type
			class T, 				// map::mapped_type
			class Compare = less<Key>, // map::key_compare
			class Alloc = allocator<pair<const Key,T> > //
map::allocator_type
			> class map;

3、pair类型介绍

map底层的红黑树节点中的数据,使用pair<Key, T>存储键值对数据。

typedef pair<const Key, T> value_type;
template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;
	T2 second;
	
	pair()
	: first(T1())
	, second(T2())
	{}
	
	pair(const T1& a, const T2& b)
	: first(a)
	, second(b)
	{}
	
	template<class U, class V>
	pair (const pair<U,V>& pr)
	: first(pr.first)
	, second(pr.second)
	{}
};

template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y)
{
	return ( pair<T1,T2>(x,y) );
}

4、map的构造

map的构造我们关注以下几个接口即可。

map的支持正向和反向迭代遍历,遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。

// empty (1) ⽆参默认构造
explicit map (const key_compare& comp = key_compare(),
			  const allocator_type& alloc = allocator_type());
			  
// range (2) 迭代器区间构造
template <class InputIterator>
	map (InputIterator first, InputIterator last,
		const key_compare& comp = key_compare(),
		const allocator_type& = allocator_type());

// copy (3) 拷⻉构造
map (const map& x);

// initializer list (5) initializer 列表构造
map (initializer_list<value_type> il,
	const key_compare& comp = key_compare(),
	const allocator_type& alloc = allocator_type());

// 迭代器是⼀个双向迭代器
iterator -> a bidirectional iterator to const value_type

// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

5、map的增删查

map的增删查关注以下几个接口即可:

map增接口,插⼊的pair键值对数据,跟set所有不同,但是查和删的接口只用关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value。

Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>

// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);

// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;

// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);

// 返回⼤于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回⼤于k位置的迭代器
const_iterator lower_bound (const key_type& k) const;

6、map的数据修改

前面我提到map支持修改mapped_type 数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。

map第一个支持修改的方式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有一个非常重要的修改接口operator[],但是operator[]不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口。

需要注意从内部实现角度,map这里把我们传统说的value值,给的是T类型,typedef为mapped_type。而value_type是红黑树结点中存储的pair键值对值。日常使用我们还是习惯将这里的T映射值叫做value。

Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>

// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的mapped_type值
iterator find (const key_type& k);

// ⽂档中对insert返回值的说明
// The single element versions (1) return a pair, with its member pair::first set to an iterator pointing to either the newly inserted element or to the element with an equivalent key in the map. The pair::second element in the pair is set to true if a new element was inserted or false if an equivalent key already existed.

// insert插⼊⼀个pair<key, T>对象
// 1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象 first是key所在结点的迭代器,second是false
// 2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象 first是新插⼊key所在结点的迭代器,second是true
// 也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭代器
// 那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现operator[]
// 需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,另⼀个是insert返回值pair<iterator,bool>
pair<iterator,bool> insert (const value_type& val);

mapped_type& operator[] (const key_type& k);

// operator的内部实现
mapped_type& operator[] (const key_type& k)
{
	// 1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储mapped_type值的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊+修改功能
	// 2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找+修改的功能
	pair<iterator, bool> ret = insert({ k, mapped_type() });
	iterator it = ret.first;
	return it->second;
}

7、构造遍历及增删查使用样例

#include<iostream>
#include<map>
using namespace std;
int main()
{
	// initializer_list构造及迭代遍历
	map<string, string> dict = { {"left", "左边"}, {"right", "右边"},
	{"insert", "插⼊"},{ "string", "字符串" } };
	
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		//cout << (*it).first <<":"<<(*it).second << endl;
		// map的迭代基本都使⽤operator->,这⾥省略了⼀个->
		// 第⼀个->是迭代器运算符重载,返回pair*,第⼆个箭头是结构指针解引⽤取pair数据
		//cout << it.operator->()->first << ":" << it.operator->()->second << endl;
		cout << it->first << ":" << it->second << endl;
		++it;
	} 
	cout << endl;
	
	// insert插⼊pair对象的4种⽅式,对⽐之下,最后⼀种最⽅便
	pair<string, string> kv1("first", "第⼀个");
	dict.insert(kv1);
	dict.insert(pair<string, string>("second", "第⼆个"));
	dict.insert(make_pair("sort", "排序"));
	dict.insert({ "auto", "⾃动的" });
	
	// "left"已经存在,插⼊失败
	dict.insert({ "left", "左边,剩余" });
	
	// 范围for遍历
	for (const auto& e : dict)
	{
		cout << e.first << ":" << e.second << endl;
	} 
	cout << endl;
	
	string str;
	while (cin >> str)
	{
		auto ret = dict.find(str);
		if (ret != dict.end())
		{
			cout << "->" << ret->second << endl;
		} 
		else
		{
			cout << "⽆此单词,请重新输⼊" << endl;
		}
	} 
	// erase等接⼝跟set完全类似,这⾥就不演⽰讲解了
	return 0;
}

8、map的迭代器和[]功能样例

#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	// 利⽤find和iterator修改功能,统计⽔果出现的次数
	string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠",
	"苹果", "⾹蕉", "苹果", "⾹蕉" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// 先查找⽔果在不在map中
		// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 1}
		// 2、在,则查找到的节点中⽔果对应的次数++
		auto ret = countMap.find(str);
		if (ret == countMap.end())
		{
			countMap.insert({ str, 1 });
		} 
		else
		{
			ret->second++;
		}
	} 
	for (const auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	return 0;
} 
///
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	// 利⽤[]插⼊+修改功能,巧妙实现统计⽔果出现的次数
	string arr[] = { "苹果", "西⽠", "苹果", "西⽠", "苹果", "苹果", "西⽠",
	"苹果", "⾹蕉", "苹果", "⾹蕉" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// []先查找⽔果在不在map中
		// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 0},同时返回次数的引⽤,++⼀下就变成1次了
		// 2、在,则返回⽔果对应的次数++
		countMap[str]++;
	} 
	for (const auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	} 
	cout << endl;
	return 0;
}
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
	map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));
	// key不存在->插⼊ {"insert", string()}
	dict["insert"];
	
	// 插⼊+修改
	dict["left"] = "左边";
	
	// 修改
	dict["left"] = "左边、剩余";
	
	// key存在->查找
	cout << dict["left"] << endl;
	return 0;
}

9、multimap和map的差异

  1. multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全一样,比如find时,有多个key,返回中序第一个。
  2. 其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。

标签:map,multi,set,const,迭代,--,key,type
From: https://blog.csdn.net/2301_79642159/article/details/143134303

相关文章

  • c语言小结——使电脑关机,输入正确信息取消关机
    一:代码展示 #include<stdio.h>#include<string.h>#include<stdlib.h>intmain(){charinput[20]={0};system("shutdown-s-t60");agin:printf("请输入:我是帅哥,否则电脑将在1分钟后关机\n");scanf("%s",inpu......
  • 网站分享丨UU在线工具
    在日常的工作、学习和生活中,我们常常会遇到各种各样需要借助工具来解决的问题。今天就给大家介绍一个功能强大、涵盖众多实用工具的在线平台——UU在线工具。一、丰富多样的工具分类1.文档处理类:PDF工具:提供了PDF转Word、PDF合并、PDF分割、PDF加密解密等一系......
  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
    文章目录Spring静态注入实际案例Demo为什么这样写有时候RemoteEBRpcInvoker.getEbFormIdUtil是一个NULL???原因1:静态变量初始化顺序问题原因2:Spring生命周期与静态字段解决方案:方法1:移除静态字段(违背了我的初衷)方法2:使用@PostConstruct方法3:使用@Autowire......
  • 3D Gaussion Splatting
    Splatting一种体渲染方法,从3D物体渲染到2D平面也叫抛雪球方法核心选择雪球抛掷,3D投影到2D合成形成最后图像捏雪球(搞定一个核形状)选择3D高斯椭圆仿射后高斯仍闭合3D降2D依然为高斯(沿一个轴积分)3Dgaussian为什么是椭球?v的概率密度函数x的概率密度......
  • 用糊弄学打开yolov8源码之yolov8.yaml
    yolov8源码下载:https://github.com/ultralytics/ultralyticsgithub.com/ultralytics/ultralytics打开源码完全不知道该从哪个文件开始看(……查看一些资料后……)决定先理解一下 yolov8.yaml 所在位置:ultralytics\cfg\models\v8\yolov8.yamlcfg\models文件夹下是各个模型......
  • Maven
    什么是Maven?maven是一个项目构建,依赖管理工具,使用maven工具可以实现自动化构建,测试,打包和发布项目,提高了开发效率项目构建指将源代码、配置文件、资源文件等转化为能够运行或部署的应用程序或库的过程项目构建的过程:清理、编译、测试、报告、打包、部署打包方式:......
  • 【Jmeter】工具安装及使用
    安装JmeterJmeter依赖于JDK,所以必须确保当前计算机上已经安装了JDK,并且配置了环境变量。下载可以ApacheJmeter官网下载,地址:ApacheJMeter-DownloadApacheJMeter解压因为下载的是zip包,解压缩即可使用,目录结构如下:其中的bin目录就是执行的脚本,其中包含启动脚本:......
  • 对软件工程的理解(随笔版)
    软件工程是一门复杂且重要的学科。要做好软件开发,首先要有极强的计划性,软件开发并非是一项可以随意进行的工作,它涉及到众多复杂的环节和众多不同专业背景的人员参与。从最初的需求分析到最终的软件上线及后续维护,每一个阶段都需要精心规划和安排。其次,要合理安排时间和进度,合理......
  • 电动汽车嵌入式软件开发过程中的难题有哪些?
    我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师:屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节能减排。无......
  • 20222401 2024-2025-1 《网络与系统攻防技术》实验二实验报告
    1实验内容1.1实践基本知识1.1.1后门后门就是不经过正常认证流程而访问系统的通道。最早的后门并不是恶意的,而是开发人员为了便于在开发期间调试程序而设置的快捷路径。按照存在位置进行分类,可以分为以下4类:编译器后门操作系统后门应用程序后门伪装成正常程序的后门1.......