【C++】—— set 与 multiset

news2024/12/27 7:42:54

【C++】—— map 与 set

  • 1 序列式容器和关联式容器
  • 2 set 系列的使用
    • 2.1 set 和 multiset 参考文档
    • 2.2 set 类的介绍
    • 2.3 set 的迭代器和构造
    • 2.4 set的增删查
      • 2.4.1 insert
      • 2.4.2 find 与 erase
      • 2.4.3 count
    • 2.5 lower_bound 与 upper_bound
    • 2.6 multiset 与 set 的差异
      • 2.6.1 不再去重
      • 2.6.2 find 返回中序的第一个
      • 2.6.3 erase 删除所有的 x
      • 2.6.4 count 个数

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

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

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

  本文讲解的 m a p map map s e t set set 底层是红黑树,红黑树是一颗平衡二叉搜索树。 s e t set set k e y key key 搜索场景的结构, m a p map map k e y key key / v a l u e value value 搜索场景的结构
  
  

2 set 系列的使用

2.1 set 和 multiset 参考文档

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

  

2.2 set 类的介绍

set 的声明如下:

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;

s e t set set 的声明如上,T 就是 s e t set set 底层关键字的类型
  
s e t set set 默认要求 T 支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第⼆个模版参数
  
s e t set set 底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。
  
• ⼀般情况下,我们都不需要传后两个模版参数。
  
s e t set set 底层是用红黑树实现,增删查效率是 O(logN),迭代器遍历是⾛的搜索树的中序,所以是有序的。
  
• 前面部分我们已经学习了 v e c t o r vector vector / l i s t list list 等容器的使用,STL 容器接口设计,高度相似,所以这里我们就不再⼀个接口⼀个接口的介绍,而是直接带着大家看文档,挑比较重要的接口进行介绍。

  这里库中的模板参数用的是 T,个人认为这里设计的不够好,既然是搜索,那用 Key 更好,我们可以将其当成是 Key,虽然库中没用这个名字。
  

2.3 set 的迭代器和构造

   s e t set set 的迭代器是一个双向迭代器

在这里插入图片描述

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

  

s e t set set 主要构造方式如下:

  • 无参构造
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& alloc = allocator_type());
  • 拷贝构造
set(const set& x);
set(const set& x, const allocator_type& alloc);
  • 列表构造
set(initializer_list<value_type> il,
    const key_compare& comp = key_compare(),
    const allocator_type& alloc = allocator_type());

  

2.4 set的增删查

  首先要注意的是: s e t set set 是不支持改的,因为 s e t set set 属于关联式容器,对齐进行数据修改会破坏它的存储结构
  

2.4.1 insert

// 单个数据插⼊,如果已经存在则插⼊失败
pair<iterator, bool> insert(const value_type& val);

// 列表插⼊,已经在容器中存在的值不会插⼊
void insert(initializer_list<value_type> il);

// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert(InputIterator first, InputIterator last);

  这里 v a l u e value value_ t y p e type type 就是 T,即 Key;此外 k e y key key_ t y p e type type 也是 T。这里这么设计是为了和后面的 m a p map map 保持一致

在这里插入图片描述

  返回值 pair<iterator, bool> 这里还不需要用到它,暂不解释,在后面的 m a p map map 部分会详细介绍。
  
举个栗子:

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();
	while (it != s.end())
	{
		// error C3892: “it”: 不能给常量赋值
		// *it = 1;

		cout << *it << " ";
		++it;
	}
	cout << endl;return 0;
}

运行结果:

在这里插入图片描述

  可以看到, s e t set set 是不允许两个相同的值进行插入的, s e t set set 有去重功能。并且 s e t set set 是不允许修改的*it = 1 进行修改会报错。
  默认 s e t set set 走的是升序,如果想走降序,可以传递一个大于的仿函数。迭代器的底层走的是一个中序遍历

   s e t set set 支持列表插入

int main()
{
	set<int> s;
	s.insert({ 2,8,3,9,2 });
	for (auto e : s)
	{
		cout << e << " ";
	} 
	cout << endl;
	return 0;
}

  
   s e t set set 除了支持整型,其他任意类型都是可以的(如果该类型不支持比较,需自己传递仿函数),我们以 s t r i n g string string 为例

int main()
{
	// void insert(initializer_list<value_type> il);
	// 构造出临时对象再拷贝构造,优化成直接构造
	set<string> strset = { "sort", "insert", "add" };
	
	// 遍历string⽐较ascll码⼤⼩顺序遍历的
	for (auto& e : strset)
	{
		cout << e << " ";
	} 
	return 0;
}

  

2.4.2 find 与 erase

const_iterator find(const value_type& val) const;
iterator       find(const value_type& val);

   f i n d find find 返回的是对应位置的迭代器,如果没找到就返回 end()

  • 迭代器删除
iterator  erase(const_iterator position);
  • Key删除
size_type erase(const value_type& val);

   s i z e size size_ t y p e type type 是一个无符号整型,即 u n s i g n e d unsigned unsigned i n t int int,返回的是删除元素的个数
  如果删除成功,表示删除了一个值,返回 1;删除失败,表示没有删除值,返回 0

  为什么这里不用 b o o l bool bool 呢?这里是为了兼容 m u l t i s e t multiset multiset
   m u l t i s e t multiset multiset 中允许相同数插入的,可能 e r a s e erase erase 多个相同的值,这时就不能用 b o o l bool bool

  • 迭代器区间删除
iterator  erase(const_iterator first, const_iterator last);

  

栗子

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;

	return 0;
}

  
   s e t set set 进行删除同样会导致 迭代器失效

i )
在这里插入图片描述
  
  此时删除的是叶子结点 1,删除后迭代器中的指针为野指针迭代器失效

  

ii )

在这里插入图片描述
   删除 6 节点。它要与 5 或 7 节点进行交换,删除进行删除。虽然此时迭代器中的指针并不是野指针,但它原来的意义已经改变了,我们也认为是迭代器失效
  
  而且从我们的角度,我们不知道这个节点是直接删除还是替代法删除

p s ps ps:对二叉搜索树的删除有不理解的小伙伴可移步至:【C++】—— 二叉搜索树

  

2.4.3 count

   c o u n t count count 的功能是返回 v a l u e value value_ t y p e type type 在容器中的个数

size_type count (const value_type& val) const;

   c o u n t count count 是给 m u l t i s t multist multist 设计的,因为对 s e t set set 而言 c o u n t count count 要么返回 1 要么返回 0,并没有什么意义。但对于 m u l t i s e t multiset multiset 就不一样, m u l t i s e t multiset multiset 是允许多个相同值存在的

  我们可以 c o u n t count count 来判断一个 K e y Key Key 在不在,在的话返回的是 1,不在返回 0。
  而且用 c o u n t count count 来判断往往比 f i n d find find 更方便,因为 f i n d find find 返回的是迭代器,迭代器 != end() 才是找到了

int main()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	
	// 利⽤count间接实现快速查找
	int x;
	cin >> x;
	if (s.count(x))
	{
		cout << x << "在!" << endl;
	} 
	else
	{
		cout << x << "不存在!" << endl;
	} 
	return 0;
}

  

2.5 lower_bound 与 upper_bound

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

   l o w e r lower lower_ b o u n d bound bound u p p e r upper upper_ b o u n d bound bound 是为了方便查找一段区间
  我们曾经说过,在 STL 里面,只要是迭代器区间必须是 左闭右开,这时就可以用到 l o w e r lower lower_ b o u n d bound bound u p p e r upper upper_ b o u n d bound bound

举个栗子:


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(这里即返回30)
	//auto itlow = myset.lower_bound(30);
	 返回 > 60(这里即返回70)
	//auto itup = myset.upper_bound(60);

	// 实现查找到的[itlow,itup)包含[25, 55]区间
	//对应别人来说,并不知道容器里面是否有 25 和 55,但是查找这段区间的方法与查找30-60的方法一样的
	// 返回 >= 25(这里即返回30)
	auto itlow = myset.lower_bound(25);
	// 返回 > 55(这里即返回60)
	auto itup = myset.upper_bound(55);

	// 删除这段区间的值
	myset.erase(itlow, itup);
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

  

2.6 multiset 与 set 的差异

   m u l t i s e t multiset multiset s e t set set 的使用基本完全类似,主要区别点在于 m u l t i s e t multiset multiset 支持值冗余,那么 i n s e r t insert insert / f i n d find find / c o u n t count count / e r a s e erase erase 都围绕着支持值冗余有所差异,具体参看下面的样例代码理解。

2.6.1 不再去重

  • 相比 s e t set set 不同的是, m u l t i s e t multiset multiset 是排序,但是不去重
int main()
{
	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;
	return 0;
}

  

2.6.2 find 返回中序的第一个

  • 相比 s e t set set 不同的是, x x x 可能会存在多个, f i n d find find 查找中序遍历的第一个
int main()
{
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	int x;
	cin >> x;
	auto pos = s.find(x);
	while (pos != s.end() && *pos == x)
	{
		cout << *pos << " ";
		++pos;
	} 
	cout << endl;
	return 0;
}

  简单讲一下他是怎么找到中序的第一个的。
  查找依然是 O(logN) 算法的查找,并不是通过中序的方式遍历一遍,这样的话二叉搜索树就失去其意义。
  中序的查找规则是:左子树 -> 根 -> 右子树
  假设要找的值是 5 :如果找到了一个 5 ,就再往其左子树找,因为中序第一个 5 一定是在左树;直到找到了某一个 5 ,并且其左子树没有 5 ,表面当前 5 就是中序的第一个 5。
  
  为什么这里要找中序的第一个呢? 因为找中序的第一个 x,就可以用迭代器不断++,就可以找到所有的 x
  

2.6.3 erase 删除所有的 x

  • 相比 s e t set set 不同的是, e r a s e erase erase 给值时会删除所有的 x
int main()
{
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	for (auto e : s)
	{
		cout << e << " ";
	} 
	cout << endl;

	int x;
	cin >> x;
	s.erase(x);
	for (auto e : s)
	{
		cout << e << " ";
	} 
	cout << endl;
	return 0;
}

  

2.6.4 count 个数

  • 相比 s e t set set 不同的是, c o u n t count count 会返回 x x x实际个数
int main()
{
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	int x;
	cin >> x;
	cout << s.count(x) << endl;
	return 0;
}

  
  
  
  
  


  好啦,本期关于 s e t set set m u l t i s e t multiset multiset 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 C++ 的学习路上一起进步!

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

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

相关文章

`pnpm` 不是内部或外部命令,也不是可运行的程序或批处理文件(问题已解决,2024/12/3

主打一个有用 只需要加一个环境变量 直接安装NodeJS的情况使用NVM安装NodeJS的情况 本篇博客主要针对第二种情况&#xff0c;第一种也可参考做法&#xff0c;当然眨眼睛建议都换成第二种 默认情况下的解决方法&#xff1a;⭐⭐⭐ 先找到node的位置&#xff0c;默认文件夹名字…

JavaScript 键盘控制移动

如果你想通过 JavaScript 实现键盘控制对象&#xff08;比如一个方块&#xff09;的移动&#xff0c;下面是一个简单的示例&#xff0c;展示如何监听键盘事件并根据按下的键来移动一个元素。 HTML 和 CSS&#xff1a; <!DOCTYPE html> <html lang"en">…

【串口助手开发】visual studio 使用C#开发串口助手,生成在其他电脑上可执行文件,可运行的程序

1、改成Release&#xff0c;生成解决方案 串口助手调试成功后&#xff0c;将Debug改为Release&#xff0c;点击生成解决方案 2、运行exe文件 生成解决方案后&#xff0c;在bin文件夹下&#xff0c; Release文件夹下&#xff0c;生成相关文件 复制一整个Release文件夹&#xf…

通过HTML Canvas 在图片上绘制文字

目录 前言 一、HTML Canvas 简介 二、准备工作 三、绘制图片 四、绘制文字 五、完整代码 效果演示&#xff1a; 前言 HTML canvas 为我们提供了无限的创意可能性。今天&#xff0c;我们就来探索一下如何通过 HTML canvas 将图片和文字绘制到图片上&#xff0c;创造出独特…

Android ION Buffer

目录 背景介绍 ION内存管理机制主要解决了以下几个关键问题&#xff1a; ION的实际应用场景 背景介绍 ION是Android 4.0 ICS(Ice Cream Sandwich)引入的一个通用内存管理器&#xff0c;用于解决不同Android设备之间内存管理接口碎片化的问题。至少有三个或者更多类似PMEMM接…

qt程序开发环境部署

安装 sudo apt install qt5-default sudo apt install qtcreator sudo apt install g直接安装&#xff0c;linux的源里一般都有&#xff0c;如果没有&#xff0c;那就辛苦找下源了。。。 设置kit 然后启动qtcreator&#xff0c;构建套件&#xff0c;选择合适的编译器&#…

聊聊JVM G1(Garbage First)垃圾收集器

CMS的垃圾回收机制&#xff0c;为什么分为四步https://blog.csdn.net/genffe880915/article/details/144205658说完CMS垃圾回收器&#xff0c;必定要说到目前一般应用项目中都推荐的G1。G1在JDK1.7 update4时引入&#xff0c;在JDK9时取代CMS成为默认的垃圾收集器。它是HotSpot…

Vue框架开发一个简单的购物车(Vue.js)

让我们利用所学知识来开发一个简单的购物车 &#xff08;记得暴露属性和方法&#xff01;&#xff01;&#xff01;&#xff09; 首先来看一下最基本的一个html框架 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"&…

【机器学习】机器学习的基本分类-监督学习-决策树-C4.5 算法

C4.5 是由 Ross Quinlan 提出的决策树算法&#xff0c;是对 ID3 算法的改进版本。它在 ID3 的基础上&#xff0c;解决了以下问题&#xff1a; 处理连续型数据&#xff1a;支持连续型特征&#xff0c;能够通过划分点将连续特征离散化。处理缺失值&#xff1a;能够在特征值缺失的…

Qt 安装Qt Serial Port

最近要用Qt写个串口上位机软件&#xff0c;发现Qt的串口库用不了&#xff0c;上网找了一下资料&#xff0c;找到一种解决办法&#xff0c;具体操作如下&#xff1a; 参考文章&#xff1a;https 目录 一、找到QT安装路径&#xff0c;并运行Qt Maintenance Tool二、选择 添加或移…

【Go 基础】channel

Go 基础 channel 什么是channel&#xff0c;为什么它可以做到线程安全 Go 的设计思想就是&#xff1a;不要通过共享内存来通信&#xff0c;而是通过通信来共享内存。 前者就是传统的加锁&#xff0c;后者就是 channel。也即&#xff0c;channel 的主要目的就是在多任务间传递…

cin/cout的性能优化和缓冲区同步问题

目录 背景导入 问题 1.1ios::sync_with_stdio(false) 1.2为什么要解除C/C IO流同步? 1.3使用场景 2.1cin和cout的绑定关系 2.2为什么要解除绑定关系? 2.3注意事项 背景导入 大家可以先看一下这段背景知识;后面我会谈谈自己的理解; 1.在C中&#xff0c;标准输⼊输出流…

C# 动态类型 Dynamic

文章目录 前言1. 什么是 Dynamic&#xff1f;2. 声明 Dynamic 变量3. Dynamic 的运行时类型检查4. 动态类型与反射的对比5. 使用 Dynamic 进行动态方法调用6. Dynamic 与 原生类型的兼容性7. 动态与 LINQ 的结合8. 结合 DLR 特性9. 动态类型的性能考虑10. 何时使用 Dynamic&…

PDF文件页面转换成图片怎么弄-免费PDF编辑工具分享

>>更多PDF文件处理应用技巧请前往 96缔盟PDF处理器 主页 查阅&#xff01; —————————————————————————————————————— 序言 我之前的文章也有介绍过如何使用96缔盟PDF处理器对PDF文件转换成图片&#xff0c;但是当时是使用DMPDFU…

“放弃Redis Desktop Manager使用Redis Insight”:日常使用教程(Redis可视化工具)

文章目录 更新Redis Insight连接页面基础解释自动更新key汉化暂时没有找到方法&#xff0c; Redis Desktop Manager在连接上右键在数据库上右键在key上右键1、添加连接2、key过期时间 参考文章 更新 (TωT)&#xff89;~~~ β&#xff59;ё β&#xff59;ё~ 现在在维护另一…

【AI模型对比】Kimi与ChatGPT的差距:真实对比它们在六大题型中的全面表现!

文章目录 Moss前沿AI语义理解文学知识数学计算天文学知识物理学知识英语阅读理解详细对比列表总结与建议 Moss前沿AI 【OpenAI】获取OpenAI API Key的多种方式全攻略&#xff1a;从入门到精通&#xff0c;再到详解教程&#xff01;&#xff01; 【VScode】VSCode中的智能AI-G…

在Node.js局域网调试https的Vue项目

需求&#xff1a; 最近在测试在网页端&#xff08;HTML5&#xff09;调用移动设备的定位等权限功能&#xff0c;发现某些功能是必须保证域名在https下的否则会出现不正常现象。 解决&#xff1a; 1.在线生成和证书 访问&#xff1a;CSR文件生成工具-中国数字证书CHINASSL …

【采样率、采样定理、同步和异步采样】

内容来源&#xff1a;【数据采集卡的【采样率】【采样定理】【同步采样】【异步采样】的相关说明】 此篇文章仅作笔记分享。 前言 模拟信号需要通过采样、储存、量化、编码这几个步骤转换成数字信号&#xff0c;本篇文章将会对采样进行一个更详细的说明。 采样 采样就是将一…

深度学习常用指标

1. 混淆矩阵&#xff08;误差矩阵&#xff09; 2. 准确率&#xff08;overall accuracy&#xff09; 代表了所有预测正确的样本占所有预测样本总数的比例 这里分类正确代表了正样本被正确分类为正样本&#xff0c;负样本被正确分类为负样本 3. 平均精度&#xff08;average…

基于频谱处理的音频分离方法

基于频谱处理的音频分离方法 在音频处理领域&#xff0c;音频分离是一个重要的任务&#xff0c;尤其是在语音识别、音乐制作和通信等应用中。音频分离的目标是从混合信号中提取出单独的音频源。通过频谱处理进行音频分离是一种有效的方法&#xff0c;本文将介绍其基本原理、公…