C++ set,multiset与map,multimap的基本使用

news2024/10/1 19:23:00

1. 序列式容器和关联式容器

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

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

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

2. set类的介绍

set类的声明如下代码所示

template<class T,class Compare = less<T>,class Alloc = allocator<T>>

class set;
  • T是set底层关键字的类型。
  • set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数来传给第二个模板参数。
  • set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。

 一般情况下我们都不需要传后两个模板参数。

set底层使用红黑树实现,增删查的效率是O(logN) ,迭代器遍历是走的搜索树的中序,所以是有序的

(1). 构造和迭代器

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

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

构造函数的声明

//无参默认构造
explicit set (const key_compare& comp =key_compare()
            , const allocator_type& alloc = allocator_type() );

//迭代器区间构造
template <class InputIterator>
set(InputIterator first 
    , InputIterator last , const key_compare& comp = key_compare() 
    , const allocator_type& = allocator_type());

//拷贝构造
set (const set& x);

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

C++提供了关键字explicit,用来阻止不应该允许的经过转换构造函数进行的隐式类型转换的发生,声明为explicit的构造函数不能在隐式转换中使用。即只能被明确调用

set的迭代器是一个双向迭代器 bidirectional iterator

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

//反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
(2). 增删查
// 单个数据插⼊,如果已经存在则插⼊失败 
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;

insert插入和迭代器遍历使用代码如下

#include<iostream>
#include<set>
using namespace std;

int main()
{
	set<int> sl;
	set<int, greater<int>> sg;
	sl.insert(5);
	sl.insert(3);
	sl.insert(8);
	sl.insert(3);
	sl.insert(5);

	sg.insert(5);
	sg.insert(3);
	sg.insert(8);

	set<int>::iterator itl = sl.begin();
	set<int>::iterator itg = sg.begin();
	while (itl != sl.end())
	{
		cout << *itl << "  ";
		itl++;
	}
	cout << endl;
	while (itg != sg.end())
	{
		cout << *itg << "  ";
		itg++;
	}

	sl.insert({ 2,8,3,9 });
	cout << endl;
	for (auto e : sl)
	{
		cout << e << "  ";
	}
	cout << endl;
	set<string> strs = { "s","ii","affg" };
	for (auto e : strs)
	{
		cout << e << "  ";
	}
	return 0;
}

输出结果为

 find查找(库里的和set自身的)和erase删除使用代码

#include<iostream>
#include<set>
using namespace std;

int main()
{
	set<int> s = { 4,3,8,3,9,6,10 };
	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 n = s.erase(x);
	if (n == 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;

}

输出结果为(6,1,2是我们选择删除的数x)

 通过lower_bound查找大于等于x1的迭代器,在通过upper_bound查找小于x2位置的迭代器

来删除这一段区间,代码如下

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

输出结果如下

3. multiset与set的差异

multiset和set的使用基本完全类似,主要区别点在于multiset支持值冗余,那么insert/find/count/erase都围绕着支持值冗余有所差异

  1. 相较于set,multiset是排序,但是不去重
  2. 相比set不同的是,x可能会存在多个,find查找中序遍历的第一个
  3. 与set不同的是,multiset的count会返回x的实际个数
  4. 相比set不同的是,multiseterase给值时会删除所有的为x的节点

 4. 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> > > 
 class map;

5. pair类型

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

通过 first与second来输出存储的键值

#include<iostream>
#include<set>
using namespace std;
int main()
{
	pair<int, int> p{ 1,56 };
	cout << p.first << endl;
	cout << p.second << endl;
	return 0;
}

6. map的构造

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

//⽆参默认构造 
explicit map (const key_compare& comp = key_compare(),
             const allocator_type& alloc = allocator_type());
//迭代器区间构造 
template <class InputIterator>
  map (InputIterator first, InputIterator last,
      const key_compare& comp = key_compare(),
      const allocator_type& = allocator_type());
// 拷⻉构造 
map (const map& x);
// initializer 列表构造 
map (initializer_list<value_type> il,
    const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());

迭代器依然是一个双向迭代器与set类似

7. map的增删查

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

// 单个数据插⼊,如果已经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;

基本使用

#include<iostream>
#include<set>
#include<map>
using namespace std;
int main()
{
	map<string, string> dict = { {"dog","狗"}, {"cat","猫"},{"pig","猪"} };
	//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;
		}
	}


	return 0;
}

8. map的数据修改

map支持修改mapped_type数据,不支持修改key数据,因为这样会修改关键字数据,从而破坏了底层搜索树的结构。

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

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

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>

#include<iostream>
#include<algorithm>
#include<iostream>
#include<map>

using namespace std;

int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "葡萄","李","梨" };
	map<string, int> CountMap1;
	map<string, int> CountMap2;
	for (const auto& str : arr)
	{
		auto val = CountMap1.find(str);
		
		//看是否在map中有str水果
		if (val == CountMap1.end())
		{
			CountMap1.insert({ str,1 });//不在就插入水果,将次数设置为1
		}
		else
		{
			val->second++;//在就将查到的节点中的水果对应val值++
		}
	}
	for (const auto& e : CountMap1)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;

	for (const auto& str : arr)
	{
		auto val = str;

		//key第一次出现,就插入这个值并将其val++
		CountMap2[val]++;
	}

	for (const auto& e : CountMap2)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
	return 0;
}

输出结果如下

9. multimap和map的差异

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


这篇就到这里啦ヾ( ̄▽ ̄)Bye~Bye~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2182995.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32器件支持包安装,STLINK/JLINK驱动安装

一、支持包安装 1、离线安装 先下载支持包之后&#xff0c;再进行安装。如下图要安装STM32F1系列&#xff0c;双击 出现如下&#xff0c;会自动锁定安装路径&#xff0c;然后点击下一步&#xff0c;直接安装。 2、在线安装 首先需要电脑联网。如下。先点击第一个红框绿色按钮…

常见的VPS或者独立服务器的控制面板推荐

随着越来越多的企业和个人转向VPS和独立服务器以获得更高的性能和灵活性&#xff0c;选择合适的控制面板变得尤为重要。一个好的控制面板可以大大简化服务器管理&#xff0c;提高工作效率。本篇文章将介绍2024年最值得推荐的VPS控制面板&#xff0c;帮助您做出明智的选择。 1.…

STL容器适配器

欢迎来到本期节目- - - STL容器适配器 适配器模式&#xff1a; 在C中&#xff0c;适配器是一种设计模式&#xff0c;有时也称包装样式&#xff1b; 通过将类自己的接口包裹在一个已存在的类中&#xff0c;使得因接口不兼容而不能在一起工作的类能在一起工作&#xff1b; 也就…

使用VBA快速生成Excel工作表非连续列图片快照

Excel中示例数据如下图所示。 现在需要拷贝A2:A15,D2:D15,J2:J15,L2:L15,R2:R15为图片&#xff0c;然后粘贴到A18单元格&#xff0c;如下图所示。 大家都知道VBA中Range对象有CopyPicture方法可以拷贝为图片&#xff0c;但是如果Range对象为非连续区域&#xff0c;那么将产生10…

详解DHCP服务工作原理及配置案例

一. DHCP概述 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;是一个主机IP简化分配管理的TCP/IP协议&#xff0c;用户通过DHCP服务器动态的分配给客户端IP地址及其他环境的配置工作&#xff0c;包括IP地址、子网掩码、网关和…

【NVIDIA】如何使用nvidia-smi命令管理和监控GPU

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

KPConv: Flexible and Deformable Convolution for Point Clouds

Abstract Kernel Point Convolution&#xff08;KPConv&#xff09;是一种点云卷积方法&#xff0c;它可以直接在点云数据上进行操作&#xff0c;无需任何中间的表示形式。方法的核心在于使用核点来定义卷积权重&#xff0c;核点位于欧几里得空间中&#xff0c;并仅对靠近它们…

Spring DI 笔记

目录 1.什么是DI? 2.依赖注入的三种⽅式 2.1属性注⼊ 2.2构造⽅法注⼊ 2.3Setter 注⼊ 2.4三种注⼊优缺点分析 3.Autowired存在问题 1.什么是DI? DI: 依赖注⼊ 依赖注⼊是⼀个过程&#xff0c;是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源&#xff0c;⽽资源指的…

(JAVA)浅尝关于 “栈” 数据结构

1. 栈的概述&#xff1a; 1.1 生活中的栈 存储货物或供旅客住宿的地方&#xff0c;可引申为仓库、中转站。例如酒店&#xff0c;在古时候叫客栈&#xff0c;是供旅客休息的地方&#xff0c;旅客可以进客栈休息&#xff0c;休息完毕后就离开客栈 1.2计算机中的栈 将生活中的…

第1 章 第一节:基础语法

第1 章 第一节&#xff1a;基础语法 1.1书写规则 1.1.1关键字 在Java语言中&#xff0c;已经定义好的&#xff0c;具有一定的功能和作用的英文单词。所有的关键字都是小写的 在Java中总共有51个关键字&#xff0c;还有两个保留字const\goto. 常见的关键字&#xff1a; if…

User-Agent在WebMagic爬虫中的重要性

对于需要从网站上抓取数据的开发者来说&#xff0c;WebMagic是一个强大的工具。它是一个简单灵活的Java爬虫框架&#xff0c;用于抓取网页数据。在爬虫技术中&#xff0c;User-Agent&#xff08;用户代理&#xff09;是一个关键的HTTP请求头&#xff0c;它告诉服务器关于客户端…

中九无科研无竞赛保研经验帖——上交软院、中科大计算机、复旦工程硕、南大工程硕、浙大软件

本人bg: 学校&#xff1a;中九软件工程rk&#xff1a;夏令营5%&#xff0c;预推免3%&#xff08;都是写的预估排名&#xff09;六级&#xff1a;480&#xff0c; 四级&#xff1a;540科研&#xff1a;无竞赛&#xff1a;美赛M&#xff0c;以及水赛国三、省二若干 保研前期没有…

jenkins项目发布基础

随着软件开发需求及复杂度的不断提高,团队开发成员之间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题。Jenkins 自动化部署可以解决集成、测试、部署等重复性的工作,工具集成的效率明显高于人工操作;并且持续集成可以更早的获取代码变更的信息,…

向日葵远程控制怎么下载?推荐4个远程控制工具网站。

从官网下载的软件质量有保障&#xff0c;安全性和可信度也比较好。所以不管需要使用什么样的软件&#xff0c;最好是到官网下载。如果是有远程控制的需求&#xff0c;我可以推荐几个安全可靠的网站给大家。 &#xff11;、向日葵远程控制大师 直达链接&#xff1a;https://dow…

【STM32单片机_(HAL库)】4-3【定时器TIM】定时器输出PWM实现呼吸灯实验

1.硬件 STM32单片机最小系统LED灯模块 2.软件 pwm驱动文件添加定时器HAL驱动层文件添加GPIO常用函数定时器输出PWM配置步骤main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "pwm.h"int main(void) {HA…

运用循环单链表实现约瑟夫问题

代码&#xff1a; #include <iostream> using namespace std; struct node {int id;node *next; }; int len0; //存现在链表的长度int main() {node*head,*temp,*tail;headnew node;head->next head;tailhead;int A,B;cin>>A>>B;lenA;int num1;while…

unity一键注释日志和反注释日志

开发背景&#xff1a;游戏中日志也是很大的开销&#xff0c;虽然有些日志不打印但是毕竟有字符串的开销&#xff0c;甚至有字符串拼接的开销&#xff0c;有些还有装箱和拆箱的开销&#xff0c;比如Debug.Log(1) 这种 因此需要注释掉&#xff0c;当然还需要提供反注释的功能&am…

数据结构-链表笔记

移除节点 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListN…

windows上安装mingw教程及mingw64国内下载地址汇总

​ 首先进去官方官网地址&#xff1a;https://www.mingw-w64.org&#xff0c;注意如果下载不了或者下载慢可以使用下面提供国内下载地址&#xff0c;可以满速下载。 进入官网后&#xff0c;直接在左侧点击Downloads即可。 点击Sources选项   点击Downloads后&#xff0c;在右…

qfluentwidgets组件库的配置与使用

文章目录 前言一、安装1 安装conda环境2 配置designer和pyuic3 查看是否成功二、简单使用前言 这篇博客用来记录qfluentwidgets组件库的基本使用。 如果你愿意花200块钱去官网github地址买一份作者的组件库,添加到了designer中了。那么本博客前面的所有配置对你都是没有用的,…