C++【set 和 map 学习及使用】

news2024/12/23 10:45:11

✨个人主页: 北 海
🎉所属专栏: C++修行之路
🎃操作环境: Visual Studio 2019 版本 16.11.17

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、预备知识
      • 1.1、关联式容器
      • 1.2、键值对
      • 1.3、树型结构的关联式容器
    • 2、set
      • 2.1、什么是 set?
      • 2.2、set 的使用
      • 2.3、set 的特点
      • 2.4、multiset
    • 3、map
      • 3.1、什么是 map?
      • 3.2、map 的使用
      • 3.3、map 中的 operator[]
      • 3.4、map 的特点
      • 3.5、multimap
    • 4、相关试题实战
      • 4.1、前K个高频单词
      • 4.2、复杂链表的复制
    • 5、补充:交集与差集
      • 5.1、如何查找交集?
      • 5.2、如何查找差集?
  • 🌆总结


🌇前言

setmapSTL 中的容器之一,不同于普通容器,它俩的查找速度极快,常用来存储各种经常被检索的数据,因为这俩容器的底层是平衡二叉搜索树中的红黑树。除此之外,还可以借助其特殊的性质,解决部分难题

图示


🏙️正文

1、预备知识

在正式学习 setmap 之前,首先要有一些预备知识,否则后面可能看不懂相关操作

1.1、关联式容器

在以往的 STL 容器学习中,我们接触到的都是 序列式容器,比如 stringvectorlistdeque 等,序列式容器的特点就是 底层为线性序列的数据结构,就比如 list,其中的节点是 线性存储 的,一个节点存储一个元素,其中存储的元素都可序,但未必有序

关联式容器 则比较特殊,其中存储的是 <key, value>键值对,这就意味着可以按照 键值大小 key 以某种特定的规则放置于适当的位置,关联式容器 没有首尾的概念,因此没有头插尾插等相关操作,本文中学习的 setmap 就属于 关联式容器

图示
出自《STL源码剖析》

注意: stackqueue 等适配器也属于序列式容器,因为他们的底层是 deque 等容器

1.2、键值对

键值对是 一种用来表示具有一一对应关系的结构,该结构中一般只包含两个成员变量:keyvalue,前者表示 键值,后者表示 实值

关联式容器的实现离不开键值对

因此在标准库中,专门提供了这种结构 pair
定义如下

//SGI 版 STL 中的实现
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) {}

#ifdef __STL_MEMBER_TEMPLATES
  template <class U1, class U2>
  pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
#endif
};

pair 中的 first 表示 键值second 则表示 实值,在给 关联式容器 中插入数据时,可以构建 pair 对象
比如下面就构建了一个 键值 keystring,实值 valueint 的匿名 键值对 pair 对象

pair<string, int>("hehe", 123);

可以将此匿名对象传入 关联式容器 中,当然这样写未免过于麻烦了,于是库中设计了一个函数模板 make_pair,可以根据传入的参数,去调用 pair 构建对象并返回

make_pair("hehe", 123);	//构建出的匿名对象与上面的一致

make_pair 的定义如下所示:

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

该函数实际会被编译器优化为 内联函数,因此不会造成过多消耗,可以放心使用

1.3、树型结构的关联式容器

所以在 C++ 标准中,共提供了四种 树型结构的关联式容器

  • set
  • multiset
  • map
  • multimap

关于 哈希结构的关联式容器 将在 哈希表 中学习

树型结构与哈希结构的关联式容器功能都是一模一样的,不过 哈希结构查找比树型结构快得多 -> O(1)

注:

  • STL 中选择的树型结构为 红黑树 RB-Tree
  • 树型结构中的元素 中序遍历 后有序,而哈希结构中的元素无序

2、set

2.1、什么是 set?

set 其实就是之前在 二叉搜索树key 的模型

图示

set 只包含 实值 value,或者说它的 实值就是键值,键值就是实值

图示

其中的 T 就是 set 的实值(键值),参数2 Compare 为存储依据,默认为升序,即符合 二叉搜索树 中序遍历的结果:升序,参数3 Alloc 是空间配置器,现在不必深究

作为 STL 中的容器,set 当然少不了迭代器,树型关联式容器迭代器的遍历结果为有序,所以迭代器遍历的本质是 中序遍历,同时 set 的迭代器还是一个 双向迭代器,支持 ++-- 操作

图示
下面来看看 set 的相关操作

2.2、set 的使用

set 的构造函数如下图所示:

图示
可以直接创建一个空 set 使用,也可以根据迭代器区间创建 set

注意: 创建时需要指定实值的类型

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

int main()
{
	vector<int> arr = { 8,5,6,7,3,1,1,3 };

	set<int> s1;	//创建一个空的 set
	set<int> s2(arr.begin(), arr.end());	//创建包含数据的 set

	cout << "s1: ";
	for (auto e : s1)
		cout << e << " ";
	cout << endl;

	cout << "s2: ";
	for (auto e : s2)
		cout << e << " ";
	cout << endl;

	return 0;
}

图示
就像 二叉搜索树 一样,set 是不支持数据冗余的,如果出现冗余的数据插入时,会失败,如果想存储冗余的数据,可以使用 multiset

set 中的常用功能

功能用途
迭代器遍历容器
empty判断容器是否为空
size当前容器中的元素数
max_size容器的最大容量
insert元素插入,根据特定条件插入至合适位置
erase删除指定元素
swap交换两个容器
clear清空容器中的所有元素
find查找实值是否存在并返回迭代器位置
count统计容器中指定键值的数量

下面这段代码演示了上述功能的实际效果

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

int main()
{
	vector<int> arr = { 7,3,6,9,3,1,6,2 };
	set<int> s1(arr.begin(), arr.end());

	//迭代器遍历
	cout << "迭代器遍历结果: ";
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//判空、求大小
	cout << "===================" << endl;
	cout << "empty(): " << s1.empty() << endl;
	cout << "size(): " << s1.size() << endl;
	cout << "max_size(): " << s1.max_size() << endl;

	//插入元素
	cout << "===================" << endl;
	cout << "insert(5): ";
	s1.insert(5);
	for (auto e : s1) cout << e << " ";
	cout << endl;

	//删除元素
	cout << "===================" << endl;
	cout << "erase(6): ";
	s1.erase(6);
	for (auto e : s1) cout << e << " ";
	cout << endl;

	//交换、查找、清理
	cout << "===================" << endl;
	set<int> s2(arr.begin() + 5, arr.end());
	s1.swap(s2);
	cout << "s1: ";
	for (auto e : s1) cout << e << " ";
	cout << endl;

	cout << "s2: ";
	for (auto e : s2) cout << e << " ";
	cout << endl;

	cout << "s1.find(9): ";
	cout << (s1.find(9) != s1.end()) << endl;

	cout << "s2.clear(): " << endl;
	s2.clear();

	cout << "s1: ";
	for (auto e : s1) cout << e << " ";
	cout << endl;

	cout << "s2: ";
	for (auto e : s2) cout << e << " ";
	cout << endl;

	return 0;
}

图示

至于 count 也可以用来查找元素是否存在,对于 set 来说,键值 key 就是 实值 value,并且因为不允许冗余,所以只有一个 键值count 统计 键值 数量不就相当于 查找 吗?

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


int main()
{
	vector<int> arr = { 7,3,6,9,3,1,6,2 };
	set<int> s1(arr.begin(), arr.end());

	for (int i = 0; i < 10; i++)
	{
		if (s1.count(i))
			cout << i << " 在 set 中" << endl;
		else
			cout << i << " 不在 set 中" << endl;
	}

	return 0;
}

结果

可以通过改变 set 模板参数2的方式,改变其中的顺序为 降序

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

int main()
{
	vector<int> arr = { 7,3,6,9,3,1,6,2 };
	set<int, greater<int>> s1(arr.begin(), arr.end());

	for (auto e : s1)
		cout << e << " ";

	return 0;
}

图示

注意: 键值 key 是不允许改变的,如果改变了,会破坏二叉搜索树的原则,因此即使是 set 中的普通迭代器,本质上也是 const 迭代器,非常神奇

图示

2.3、set 的特点

set 具有以下特点:

图示

set 还有一个亲兄弟:multiset,它允许数据冗余,即数据插入一定是成功的

2.4、multiset

multisetset 的另一个版本,对于 multiset 来说,插入冗余数据时,并不会失败

图示
除此之外,multisetset 的操作没什么区别,一模一样

这里就不再赘述,而是单独演示一下允许数据冗余的效果

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

int main()
{
	vector<int> arr = { 3,5,3,4,5,9,2,3 };
	multiset<int> ms1(arr.begin(), arr.end());

	for (auto e : ms1)
		cout << e << " ";
	cout << endl;

	return 0;
}

结果

值得一提的是,当在 multiset 中查找冗余的数据时,返回的是 中序遍历中,第一次出现的元素

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

int main()
{
	vector<int> arr = { 3,5,3,4,5,9,2,3 };
	multiset<int> ms1(arr.begin(), arr.end());

	auto it = ms1.begin();
	while (it != ms1.end())
	{
		cout << *it << " | " << &*(it) << endl;
		++it;
	}

	cout << "================" << endl;

	cout << "ms1.find(3): " << &*(ms1.find(3)) << endl;

	return 0;
}

结果

所以,multiset 才是真正的排序,set 则是去重 + 排序

统计 键值countmultiset 中可以发挥真正效果

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


int main()
{
	vector<int> arr = { 3,5,3,4,5,9,2,3 };
	multiset<int> ms1(arr.begin(), arr.end());

	for (int i = 0; i < 10; i++)
		cout << i << "在 multiset 中的数量: " << ms1.count(i) << endl;

	return 0;
}

图解

在实际中,multiset 用的比较少,重点掌握 set 即可


3、map

3.1、什么是 map?

map二叉搜索树 改造后的 key / value 模型,是一个真正意义上的 键值对,应用场景如下:

图示
map 的定义如下

图示
其中包含两个模板参数:

  1. Key 就是键值对中的 键值
  2. T 则是键值对中的 实值

map 中会用到前面提到过的 pair 结构,其中 first 表示键值,second 表示实值

map 也有迭代器,也是 双向迭代器

图示

3.2、map 的使用

构造 map 有以下几种方法

图示

#include <iostream>
#include <vector>
#include <map>
using namespace std;

int main()
{
	vector<pair<string, int>> arr = { make_pair("G", 71), make_pair("A", 65), make_pair("F", 70) };

	map<string, int> m1;	//创建一个空的 map
	map<string, int> m2(arr.begin(), arr.end());	//创建包含数据的 map

	cout << "m1: " << endl;
	for (auto e : m1)
		cout << e.first << " | " << e.second << endl;
	cout << "========================" << endl;
	cout << "m2: " << endl;
	for (auto e : m2)
		cout << e.first << " | " << e.second << endl;

	return 0;
}

结果

注意: 在访问 map 中的 键值 和 实值 时,需要通过 pair 对象指定访问,比如 e.first

map 中的常用功能

功能用途
迭代器遍历容器
empty判断容器是否为空
size当前容器中的元素数
max_size容器的最大容量
operator[]按照键值,访问实值,如果没有,则新插入
insert元素插入,根据特定条件插入至合适位置
erase删除指定元素
swap交换两个容器
clear清空容器中的所有元素
find查找实值是否存在并返回迭代器位置
count统计容器中指定键值的数量

除了新增了一个 operator[] 以及部分函数返回值不一样外,与 set 没啥区别

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;

int main()
{
	vector<pair<string, int>> arr{make_pair("z", 122), make_pair("a", 97),  make_pair("K", 75), make_pair("h", 104), make_pair("B", 66)};

	map<string, int> m1(arr.begin(), arr.end());

	//迭代器遍历
	cout << "迭代器遍历结果: ";
	map<string, int>::iterator it = m1.begin();
	while (it != m1.end())
	{
		cout << "<" << it->first << ":" << it->second << "> ";
		++it;
	}
	cout << endl;

	//判空、求大小、解引用
	cout << "===================" << endl;
	cout << "empty(): " << m1.empty() << endl;
	cout << "size(): " << m1.size() << endl;
	cout << "max_size(): " << m1.max_size() << endl;
	cout << "m1[""a""]: " << m1["a"] << endl;

	//插入元素
	cout << "===================" << endl;
	cout << "insert(""a"", 5): ";
	m1.insert(make_pair("a", 5));
	for (auto e : m1) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	//删除元素
	cout << "===================" << endl;
	cout << "erase(""a""): ";
	m1.erase("a");
	for (auto e : m1) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	//交换、查找、清理
	cout << "===================" << endl;
	map<string, int> m2(arr.begin() + 2, arr.end());
	m1.swap(m2);
	cout << "m1.swap(m2)" << endl;
	cout << "m1: ";
	for (auto e : m1) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	cout << "m2: ";
	for (auto e : m2) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	cout << "m1.find(""B""): ";
	cout << (m1.find("B") != m1.end()) << endl;

	cout << "m2.clear()" << endl;
	m2.clear();

	cout << "m1: ";
	for (auto e : m1) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	cout << "m2: " << endl;
	for (auto e : m2) cout << "<" << e.first << ":" << e.second << "> ";
	cout << endl;

	return 0;
}

结果
同样的,map 不允许数据冗余,如果想插入重复的数据,可以使用 multimap

map 插入的返回值比 set 略微复杂,因为 既要表示是否成功,也要返回插入成功的迭代器,所以返回值是一个 pair

图示

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;

int main()
{
	map<string, int> m1;
	auto ret = m1.insert(make_pair("a", 97));
	cout << "<" << ret.first->first << ":" << ret.first->second << ">" << " | " << ret.second << endl;

	ret = m1.insert(make_pair("a", 100));
	cout << "<" << ret.first->first << ":" << ret.first->second << ">" << " | " << ret.second << endl;

	return 0;
}

结果

至于 findcountset 中的一样,可以用来判断元素是否存在,不过 find 返回的是 迭代器count 返回的则是 键值数

map 是支持修改 实值 value 的,因此 可以根据普通迭代器修改 实值

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;

int main()
{
	map<string, int> m1;
	m1.insert(make_pair("a", 97));
	auto it = m1.find("a");
	cout << "<" << it->first << ":" << it->second << ">" << endl;

	it->second = 668;
	cout << "<" << it->first << ":" << it->second << ">" << endl;

	return 0;
}

结果

使用 map 来实现水果统计的代码

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;

int main()
{
	vector<string> word = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
	map<string, int> table;

	for (auto& e : word)
	{
		auto ret = table.find(e);
		if (ret == table.end())
			table.insert(make_pair(e, 1));
		else
			ret->second++;
	}

	for (auto e : table)
		cout << "<" << e.first << ":" << e.second << ">" << endl;

	return 0;
}

结果

可以实现统计,但这种写法太麻烦了,实际不会这么写,可以使用 operator[] 实现更高级的写法

3.3、map 中的 operator[]

operator[] 返回的是当前 键值 对应的 实值,如果当前 键值 不存在,则会插入新的 键值对

借助此特性,可把代码优化为

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;

int main()
{
	vector<string> word = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
	map<string, int> table;

	for (auto& e : word)
		table[e]++;

	for (auto e : table)
		cout << "<" << e.first << ":" << e.second << ">" << endl;

	return 0;
}

结果

显然,map 中的 operator[] 是一个非常强大的功能

图示

operator[] 的返回值为 mapped_type,即 实值 value 的引用,参数 key_type键值 key

重点在于 operator[] 的实现:如何凭借 键值 返回对应的 实值,并且做到新键值对的插入

(*((this->insert(make_pair(k, mapped_type()))).first)).second

图示

总的来说,operator[] 返回时需要经历以下步骤:

  • 插入一个新的键值对 this->insert( make_pair(k, mapped_type()) )
  • 获取 insert 返回值中的 键值 返回值.first 即迭代器 iterator
  • 最后通过迭代器获取 实值 (*iterator).second

只需三步,即可获取 实值

其实上面那样定义还复杂了,可以优化为下面这个样子

( (this->insert( make_pair(k, mapped_type()) )).first )->second

所以一个 operator[] 兼顾了这几种功能:插入、修改、插入+修改、查找

map 中最强大的功能

3.4、map 的特点

归纳总结后,map 的特点如下图所示

图示

注意: 无论是查找、插入、删除还是排序,都只看 键值 key,至于 实值 value 的内容是什么,无所谓,它只不过是 键值 额外携带的一个信息包而已

multimap 允许出现键值冗余

3.5、multimap

图示

multimap 中允许出现多个 重复的键值,这就意味着 operator[] 无法确认调用者的意图 -> 不知道要返回哪个 键值 对应的 实质

所以 multimap 中没有提供 operator[]

图示

图示

除了 允许键值冗余没有 operator[] 这个两个特点外,multimapmap 在操作上没有区别

当然,查找 find 时,返回的是中序遍历中第一次出现元素的迭代器;计数 count 返回的则是当前 键值 的数量

multiset 一样,multimap 用的也比较少,重点掌握 setmap 即可


4、相关试题实战

学会使用 setmap 后,可以将其用于实战,比如在下面这两个题中,这两个容器可以让我们事半功倍

4.1、前K个高频单词

题目链接:692. 前K个高频单词

题目

题目分析:题目很短,就是在一个字符串数组中,找出前 k 个出现频率最高的单词

注意: 如果出现次数相同,则按字典序排序

这道题有很多种解法

解法一:map + 快排

利用 map 建立 <string, int> 的映射关系,在按照字典序排序的同时统计出每个单词的出现频率,再通过快排依照数量进行二次排序,选择前 k 个高频单词即可

因为基础版快排 不稳定,可能会导致频率相同的单词顺序出问题,即违背题目要求:如果出现频率相同,则按字典序排序

所以这里需要使用 稳定版快排 stable_sort,如果频率相同,保持原有顺序

//map + stable_sort
class Solution {
public:
    struct Compare
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const
        {
            return kv1.second > kv2.second;
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) {
        //统计每个单词出现的频率,同时先按照字典序排序
        map<string, int> table;
        for(auto e : words)
            table[e]++;

        //将处理好的数据存入数组中
        vector<pair<string, int>> vTable(table.begin(), table.end());

        //按照出现频率进行二次排序
        stable_sort(vTable.begin(), vTable.end(), Compare());

        //取出前 k 个高频单词
        vector<string> vs;
        for(int i = 0; i < k; i++)
            vs.push_back(vTable[i].first);
        
        return vs;
    }
};

结果

注意: 此时使用快排进行排序时,单个元素是 pair,需要自己写出仿函数进行排序,仿函数十分强大

难道基础版快排无法完成任务吗?
当然可以,只需要将 仿函数进行设计即可:优先按照出现频率排序,如果频率相同,则按照字典序排序即可

具体代码如下(用了一点 C++11 中的知识)

//map + sort
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        //统计每个单词出现的频率,同时先按照字典序排序
        map<string, int> table;
        for(auto e : words)
            table[e]++;

        //将处理好的数据存入数组中
        vector<pair<string, int>> vTable(table.begin(), table.end());

        //按照出现频率进行二次排序
        sort(vTable.begin(), vTable.end(), 
        [](const pair<string, int>& kv1, const pair<string, int>& kv2)->bool
        {
            return kv1.second == kv2.second ? kv1.first < kv2.first : kv1.second > kv2.second;
        });

        //取出前 k 个高频单词
        vector<string> vs;
        for(int i = 0; i < k; i++)
            vs.push_back(vTable[i].first);
        
        return vs;
    }
};

结果

C++11 中的 lambda 表达式还是很香的

注意: 优先按照出现频率进行排序,如果频率相同时,就按字典序排序,所以写成 kv1.first < kv2.first (小的单词排在前面,就是字典序)

解法二:map + set

同样的,先使用 map 统计单词出现频率,此时已经按照字典序进行了排序,然后将 pair 看作一个 键值 存入 set 中,改变 set 中的比较逻辑(先按出现频率排序,如果相关就按照字典序排序

整体思路与 map + sort 没啥区别,不过此时是直接使用 set 进行排序,没必要借助 vector

//map + set
class Solution {
public:
    struct Compare
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const
        {
            return kv1.second == kv2.second ?
                   kv1.first < kv2.first :  //如果两个频率相等,比较字典序
                   kv1.second > kv2.second ;    //不相等比较频率
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string, int> table;
        for(auto e : words)
            table[e]++;
        
        set<pair<string, int>, Compare> sortSet(table.begin(), table.end());

        vector<string> vs;
        auto it = sortSet.begin(); 
        for(int i = 0; i < k; ++it, ++i)
            vs.push_back(it->first);

        return vs;
    }
};

结果

解法三:map + multimap

这个解法就有点狠了,直接使用 mapmultimap 互导,完成排序

map 按照字典序排序,并统计出频率
multimap map 的基础上,按照 频率 排序

注意: 需要使用 multimap,避免相同频率的单词丢失

//map + multimap
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        //先统计出现频率,同时按照字典序排序
        map<string, int> mapTbale;
        for(auto e : words)
            mapTbale[e]++;
        
        //将 map 中的数据存入 multimap 中,按照频率排序
        multimap<int, string, greater<int>> multimapTable;
        for(auto &e : mapTbale)
            multimapTable.insert(make_pair(e.second, e.first));
        
        //取出前k个高频单词
        vector<string> vs;
        auto it = multimapTable.begin();
        for(int i = 0; i < k; ++it, ++i)
            vs.push_back(it->second);
        
        return vs;
    }
};

图示

这种写法十分巧妙,代码也很简洁,完美体现了 mapmultimap 的价值

关于这道题还有其他解法,比如 利用优先级队列解决 Tok-K,感兴趣的同学可以自己下去研究,这里就不再展开叙述

4.2、复杂链表的复制

题目链接:剑指 Offer 35. 复杂链表的复制

题目

题目分析:复杂链表的深度拷贝,将题目给定的链表进行复制,这个链表比较特殊,不仅指向下一个节点,还随机指向空或其他节点

之前的解法是在两个节点新增节点,然后更改链接关系,比较麻烦,现在可以借助 map 建立映射关系,直接照着原链表更改链接关系即可

图示

//剑指 Offer 35. 复杂链表的复制
//https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        map<Node*, Node*> copyNodeMap;  //存放原来的链表节点,及新的链表节点
        Node* cur = head;
        Node* copyHead = nullptr;
        Node* copyTail = nullptr;

        //先拷贝出链表
        while(cur)
        {
            Node* copy = new Node(cur->val);
            copyNodeMap[cur] = copy;

            if(copyHead == nullptr)
            {
                copyHead = copyTail = copy;
            }
            else
            {
                copyTail->next = copy;
                copyTail = copyTail->next;
            }

            cur = cur->next;
        }

        //初步拷贝已完成,进行随机指针的拷贝
        cur = head;
        while(cur)
        {
            //非常重要的一步
            copyNodeMap[cur]->random = copyNodeMap[cur->random];
            cur = cur->next;
        }

        return copyHead;
    }
};

结果

map 在这种场景中是非常强大的!使得 原链表节点和新链表节点之间形成了一种羁绊关系,但 两者之间互不影响


5、补充:交集与差集

下面是一些补充知识,主要是关于 交集和差集

5.1、如何查找交集?

交集,指两个数组中相同的元素所构成的集合

求交集的步骤如下:

  1. 先将两个数组 排序 + 去重
  2. 遍历两个数组
  3. 如果不相等,小的 ++
  4. 相等就是交集,记录下来
  5. 其中一方走完,所有交集就查找完了

排序 + 去重,这就不就是 set 吗?

题目链接:349. 两个数组的交集

题目

直接上代码

//349. 两个数组的交集
//https://leetcode.cn/problems/intersection-of-two-arrays/description/

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //排序 + 去重
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());

        //查找交集
        vector<int> v;
        auto it1 = s1.begin();
        auto it2 = s2.begin();

        while(it1 != s1.end() && it2 != s2.end())
        {
            if(*it1 < *it2)
                ++it1;
            else if(*it1 > *it2)
                ++it2;
            else
            {
                v.push_back(*it1);
                ++it1;
                ++it2;
            }
        }

        return v;
    }
};

图示

5.2、如何查找差集?

至于差集的查找,思路和交集差不多

求差集的步骤如下:

  1. 先将两个数组 排序 + 去重
  2. 遍历两个数组
  3. 如果相等,同时 ++
  4. 不相等,小的一方记录后,再 ++
  5. 其中一方走完,再遍历另一方,此时其中的所有元素都是差集

🌆总结

以上就是本次关于 C++【set 和 map 学习和使用】的全部内容了,在这篇文章中我们先学习了 关联式容器相关知识,然后学习了 setmultisetmap 以及 multimap 的使用,最后通过一些题目见识到了 setmap 的强大之处,希望你在阅读本文后,能够收获相关知识


星辰大海

相关文章推荐

C++ 进阶知识

C++【二叉搜索树】

C++【多态】

C++【继承】

STL 之 泛型思想

C++【模板进阶】

C++【模板初阶】

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

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

相关文章

网工内推 | 网络运维专场,弹性工作,14薪

01 南凌科技股份有限公司 招聘岗位&#xff1a;网络运维工程师 职责描述&#xff1a; 1、负责及时响应客户需求、做好客户报障接收&#xff0c;受理与记录工作&#xff0c;及时做好值班记录与故障交接&#xff1b; 2、通过网管平台实时监控客户线路及机房设备的运行状态、性能…

mNGS 02:SnakeMake流程简介

<~生~信~交~流~与~合~作~请~关~注~公~众~号生信探索> 流程代码在&#xff1a;https://jihulab.com/BioQuest/SnakeMake-mNGS 或https://github.com/BioQuestX/SnakeMake-mNGS 教程链接在&#xff1a;https://doc.bioquest.cn/mngs mNGS Pipeline summary Metagenomic nex…

我的世界(MC) Forge 1.20.1 服务端搭建教程

Debian系统使用MCSManager9面板搭建Minecraft Java版MOD服务器的教程&#xff0c;本教程用的Forge1.20.1服务端&#xff0c;用其他服务端的也可以参考一下。 本教程使用Docker来运行mc服&#xff0c;可以方便切换不同Java版本&#xff0c;方便安装多个mc服版本。 视频教程&am…

【网络技术】什么是DNS及常见问题

序言 域名服务器&#xff08;Domain Name Server&#xff0c;DNS&#xff09;是一种用于存储和管理域名解析信息的服务器。它们负责将易于记忆的域名&#xff08;例如 www.example.com&#xff09;转换为与之关联的 IP 地址&#xff08;例如 192.0.2.1&#xff09;&#xff0c;…

基于M300仿地飞行,D2Pros “房地一体”免像控验证

引言 目前&#xff0c;倾斜摄影技术被广泛运用于“房地一体”项目。但在云贵川某些地形起伏较大的地区&#xff0c;运用这项技术还存在一些困难。地形高差太大导致建模精度不够&#xff0c;这是让很多客户感到头疼的问题。 同时&#xff0c;采用分层飞行或者参考最高点加大重…

2023全球数字经济大会——开放原子全球开源峰会观后感及总结

目录 前言 什么是开源&#xff1f; 主会场院士及企业领导的主要观点 展台街采环节互动&#xff08;仅代表个人观点&#xff09; 软硬协同开源分论坛精华观点 1.加速迈入云原生时代-英特尔携手合作伙伴的技术创新与实践 2.英特尔Linux操作系统及12种解决方案示例 3.英特尔基础软…

深度学习--常见激活函数的实现

常见激活函数 简介激活函数的初衷激活函数必须是非线性函数 常见的激活函数与实现Step跃阶函数公式优点缺点应用场景代码实现效果图 Sigmoid函数与代码实现公式Sigmoid函数优点Sigmoid函数缺点代码实现效果图 ReLu公式优点缺点代码效果图 LeakyReLU公式优点缺点代码效果图 tanh…

快速构建机器学习Web应用的神器:Gradio

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

FreeRTOS实时操作系统(一)RTOS的基本概念

文章目录 前言操作系统分类编程风格纠正FreeRTOS介绍任务调度方式任务状态 总结 前言 最近买了把75系列的机械键盘&#xff0c;没有数字区域&#xff0c;想起来稚辉君曾经做过一把客制化键盘&#xff0c;于是下载了资料准备学一学&#xff0c;网上很多开源的都是用的ATMEGA32U…

面试专题:Redis

1.redis简介 简单来说 redis 就是一个数据库&#xff0c;不过与传统数据库不同的是 redis 的数据是存在内存中的&#xff0c;所以存写速度非常快&#xff0c; 因此 redis 被广泛应用于缓存方向。另外&#xff0c;redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不…

Redis入门 - Redis Stream

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis入门 - Redis Stream | CoderMast编程桅杆Redis入门 - Redis Stream Redis Stream 是 Redis 5.0 版本新增加的数据结构。 Redis Stream 主要用于消息队列&#xff08;MQ&#xff0c;Message Queue&#xff09;&#xf…

【Spring】— Spring MVC入门

目录 Spring MVC入门1.Spring MVC概述2.案例——第一个Spring MVC应用1.创建项目&#xff0c;引入JAR包2.配置前端控制器3.创建Controller类4.创建Spring MVC的配置文件&#xff0c;配置控制器映射信息5.创建视图&#xff08;View&#xff09;页面6.启动项目&#xff0c;测试应…

Axios异步调用

promise 主要解决异步深层嵌套的问题 promise 提供了简洁的API 使得异步操作更加容易 1. Promise 基本API //实例方法 .then() //得到异步任务正确的结果 .catch() //获取异常信息 .finally() //成功与否都会执行&#xff08;不是正式标准&#xff09; 2. axios基本使用 …

window服务器环境将springboot项目 jar包注册成一个window服务自启动

目录 1.下载WinSW工具 2.新建一个Window Service信息的xml文件 3.将xml和exe重命名 4.安装卸载服务 5.修改配置文件 6.常用命令(注意winsw是exe名字 1.下载WinSW工具 下载winswhttps://github.com/winsw/winsw/releases 2.新建一个Window Service信息的xml文件 <!--…

OpenCV 笔记_3

文章目录 笔记_3直方图匹配(直方图规定化) 主要针对单通道图像模板匹配matchTemplate 模板匹配函数 图像卷积filter2D 卷积函数 过滤器图像噪声的产生cvflann::rand_double 产生随机浮点数在&#xff08;0~1&#xff09;之间cvflann::rand_int 产生随机整数在&#xff08;0~RAN…

最受欢迎的项目管理软件大揭秘!

项目管理软件是现代化项目管理的重要工具。这种软件可以帮助管理项目进度、资源、预算等方面的事项&#xff0c;以及项目团队之间的沟通和协作。目前市面上有很多不同的项目管理软件&#xff0c;如&#xff1a;Zoho Projects、Wrike、Asana、Trello、Basecamp、Jira等等。然而&…

操作系统-文件管理-文件系统基础

目录 一、文件的概念 文件地属性 文件的基本操作 二、文件的逻辑结构 2.1顺序文件 2.2索引文件 2.3索引顺序文件 2.4文件的目录 2.4.1文件控制块FCB 2.4.2目录结构 2.4.3索引结点(FCB改进) 三、文件保护 3.1口令保护 3.2加密保护 3.3访问控制 四、物理结构 4.1连…

操作系统-文件管理-文件系统管理和结构

目录 一、文件存储空间管理 存储空间的划分和初始化 1.1空闲表法 1.2空闲链表法 1.3位示图法 1.4成组链接法 二、文件共享 2.1基于索引结点的共享方式(硬链接) 2.2基于符号链的共享方式(软链接) 三、文件系统的层次结构 四、文件系统的全局结构 五、虚拟文件系统 文件…

自定义阿里云OSS上传文件的start依赖

说明&#xff1a;SpringBoot项目之所以开发起来很方便&#xff0c;是因为SpringBoot项目在启动时自动为我们装配了很多Bean对象&#xff08;参考&#xff1a;http://t.csdn.cn/MddMO&#xff09;&#xff0c;这取决于我们是否在pom.xml文件添加对应的依赖&#xff0c;称为起步依…

【ARIMA-LSTM】合差分自回归移动平均方法-长短期记忆神经网络研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…