【C++】20:set和map的理解和使用

目录

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

二、set的使用

[2.1 set和multiset的参考文档](#2.1 set和multiset的参考文档)

[2.2 set类的介绍](#2.2 set类的介绍)

[2.3 set的构造函数和迭代器](#2.3 set的构造函数和迭代器)

[2.3.1 默认构造](#2.3.1 默认构造)

[2.3.2 迭代器区间构造](#2.3.2 迭代器区间构造)

[2.3.3 拷贝构造](#2.3.3 拷贝构造)

[2.3.4 initializer构造](#2.3.4 initializer构造)

[2.3.5 迭代器](#2.3.5 迭代器)

[2.3.6 迭代器的使用](#2.3.6 迭代器的使用)

[2.4 set的增删](#2.4 set的增删)

[2.4.1 insert()函数](#2.4.1 insert()函数)

[2.4.1.1 单个元素插入](#2.4.1.1 单个元素插入)

[2.4.1.2 迭代器区间插入](#2.4.1.2 迭代器区间插入)

[2.4.1.3 initializer插入](#2.4.1.3 initializer插入)

[2.4.2 erase()函数](#2.4.2 erase()函数)

[2.4.2.1 删除迭代器位置的元素](#2.4.2.1 删除迭代器位置的元素)

[2.4.2.2 删除值为val的元素](#2.4.2.2 删除值为val的元素)

[2.4.2.3 删除迭代器区间的元素](#2.4.2.3 删除迭代器区间的元素)

[2.5 find的使用](#2.5 find的使用)

[2.6 count函数的使用](#2.6 count函数的使用)

[2.7 multiset和set的差异](#2.7 multiset和set的差异)

[2.8 两个数组的交集-力扣(LeetCode)](#2.8 两个数组的交集-力扣(LeetCode))

[2.9 142.环形链表Il-力扣(LeetCode)](#2.9 142.环形链表Il-力扣(LeetCode))

三、map的使用

[3.1 map和multimap参考文档](#3.1 map和multimap参考文档)

[3.2 map类的介绍](#3.2 map类的介绍)

[3.3 pair类型介绍](#3.3 pair类型介绍)

[3.4 map的插入函数insert](#3.4 map的插入函数insert)

[3.5 map的遍历](#3.5 map的遍历)

[3.5.1 迭代器方式](#3.5.1 迭代器方式)

[3.5.2 范围for](#3.5.2 范围for)

[3.6 map的构造函数](#3.6 map的构造函数)

[3.6.1 空初始化](#3.6.1 空初始化)

[3.6.2 拷贝构造](#3.6.2 拷贝构造)

[3.6.3 使用initializer初始化](#3.6.3 使用initializer初始化)

[3.7 map的删除函数erase](#3.7 map的删除函数erase)

[3.8 map的查找函数find](#3.8 map的查找函数find)

[3.9 map的数据修改和[] (非常重要)](#3.9 map的数据修改和[] (非常重要))

[3.10 multimap和map的差异](#3.10 multimap和map的差异)

[3.11 138.随机链表的复制-力扣(LeetCode)](#3.11 138.随机链表的复制-力扣(LeetCode))

[3.11.1 不使用map的方法](#3.11.1 不使用map的方法)

[3.11.2 使用map的方法](#3.11.2 使用map的方法)

[3.12 692.前K个高频单词-力扣(LeetCode)](#3.12 692.前K个高频单词-力扣(LeetCode))


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

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

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

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

二、set的使用

使用set需要包set头文件

2.1 set和multiset的参考文档

<set> - C++ Reference

2.2 set类的介绍

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

2.3 set的构造函数和迭代器

set的构造函数如下所示:

现在我们常用的就是1,2,3,5种

2.3.1 默认构造

默认构造就是不需要传入参数的构造,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1;
	return 0;
}

2.3.2 迭代器区间构造

迭代器区间构造就是传入两个迭代器,然后就迭代器区间的内容拷贝到set里面,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
#include<vector>
int main()
{
	vector<int> v1 = { 4,3,2,1,6,7};
	set<int> s1(v1.begin(),v1.end());
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

2.3.3 拷贝构造

拷贝构造就是使用一个已经存在的set来初始化另外一个set,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
#include<vector>
int main()
{
	vector<int> v1 = { 4,3,2,1,6,7};
	set<int> s1(v1.begin(),v1.end());
	set<int> s2(s1);
	for (auto& ch : s2)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

2.3.4 initializer构造

initializer构造就是可以使用一个大括号来初始化set,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7});  //直接调用构造
	set<int> s2={ 4,3,2,1,6,7};  //隐身类型转换
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	for (auto& ch : s2)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

2.3.5 迭代器

set的迭代器是一个双向迭代器,支持++和--;但是不支持+和-;

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

2.3.6 迭代器的使用

我们使用迭代器来遍历set容器,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });  
	set<int>::iterator sit = s1.begin();
	while (sit != s1.end())
	{
		cout << *sit << " ";
		sit++;
	}
	cout << endl;
	return 0;
}

只有支持迭代器,就能够支持范围for,因为范围for的底层结构就是迭代器,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });  
	set<int>::iterator sit = s1.begin();
	/*while (sit != s1.end())
	{
		cout << *sit << " ";
		sit++;
	}
	cout << endl;*/
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

2.4 set的增删

在set容器种,是支持增删查的,但是不允许修改,因为修改会改变底层二叉树的结构,所以不支持修改。

2.4.1 insert()函数

insert函数的原型如下:

它支持插入单个元素,插入一个迭代器区间,和initializer插入。

insert在插入set里面已有的元素时,不会报错,但是也不会插入这个已经有的元素。

2.4.1.1 单个元素插入

代码如下:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });  //直接调用构造
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	s1.insert(50);
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

单个元素的插入会返回一个pair类型的对象,这个对象有两个元素,第一个元素是插入元素的迭代器,第二个元素是一个bool类型,表示是否插入成功。pair类型在下面讲map的时候讲解。

2.4.1.2 迭代器区间插入
cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	int array[] = { 10,20,30,40,0 };
	set<int> s1({ 4,3,2,1,6,7});  //直接调用构造
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	s1.insert(array,array+5);
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}
2.4.1.3 initializer插入
cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });  //直接调用构造
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	s1.insert({ 10,20,0, 70,100 });
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

2.4.2 erase()函数

erase函数原型如下:

它支持删除一个迭代器位置的元素,支持删除一个元素,也支持删除一个迭代器区间的元素

2.4.2.1 删除迭代器位置的元素

他经常搭配find函数一起使用,代码如下:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });
	set<int>::iterator sit = s1.begin();
	sit++;  //指向中序遍历的第二个元素
	s1.erase(sit); //删除第二个元素
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}
2.4.2.2 删除值为val的元素
cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });
	s1.erase(100);  //支持删除不存在的元素,不会报错,返回值是0
	s1.erase(1);  //返回值是1,erase函数的返回值是删除val元素的个数

	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

在set里面erase的返回值是0或者1,如果要删除的元素不存在,返回值就是0;如果要删除的元素存在,返回值就是1;在multiset里面会存在返回值大于1的场景,因为multiset里面可以有相同的元素,set里面不允许相同的元素。

2.4.2.3 删除迭代器区间的元素
cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });
	cout << "删除前:" << endl;
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	set<int>::iterator bit = s1.begin();
	set<int>::iterator eit = s1.end();
	bit++;
	eit--;
	s1.erase(bit, eit); //删除第二个位置和倒数第二个位置之间的元素,不包括倒数第二个,这里的位置是中序的位置
	cout << "删除后:" << endl;
	for (auto& ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

2.5 find的使用

find的函数原型如下:

在set里面查找val,如果val存在就返回这个位置的迭代器,如果不存在就返回set.end();

代码如下:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });
	set<int>::iterator sit = s1.find(3);
	if (sit != s1.end())
	{
		cout << "找到了!" << endl;
	}
	else 
	{
		cout << "找不到!" << endl;
	}
	set<int>::iterator sit1 = s1.find(100);
	if (sit == s1.end())
	{
		cout << "找到了" << endl;
	}
	else
	{
		cout << "找不到!" << endl;
	}
	return 0;
}

2.6 count函数的使用

count函数原型如下:

这个函数的作用是统计set中val的个数,在set里面会返回0或者1,但是在multiset容器会返回大于1的值,因为multiset支持元素重复。代码如下:

cpp 复制代码
#include<iostream>
using namespace std;
#include<set>
int main()
{
	set<int> s1({ 4,3,2,1,6,7 });
	cout << s1.count(2) << endl;
	multiset<int> s2({ 4,3,2,1,6,7,3,3,3,3,3 });
	cout << s2.count(3) << endl;
	return 0;
}

2.7 multiset和set的差异

multiset和set的使用基本完全类似,主要区别点在于multiset支持值冗余,

insert/find/count/erase都围绕着支持值冗余有所差异,具体参看下面的样例代码理解。

cpp 复制代码
#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;
}

2.8 两个数组的交集-力扣(LeetCode)

链接如下:349. 两个数组的交集 - 力扣(LeetCode)

cpp 复制代码
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;
        set<int>::iterator it1=s1.begin();
        set<int>::iterator 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;
    }
};

2.9 142.环形链表Il-力扣(LeetCode)

链接如下:142. 环形链表 II - 力扣(LeetCode)

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        set<ListNode*> s1;
        ListNode* cur=head;
        while(cur!=nullptr)
        {
            if(s1.count(cur)==0)
            {
                s1.insert(cur);
                cur=cur->next;
            }else{
                return cur;
            }
        }
        return nullptr;
    }
};

三、map的使用

使用map需要包map头文件

3.1 map和multimap参考文档

<map> - C++ Reference

3.2 map类的介绍

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

3.3 pair类型介绍

我们再文档里面查看insert插入的数据类型,如下所示:

它插入的数据类型是value_type类型的数据,然后我们再在文档里面查看这个类型是谁typedef出来的,如下所示:

可以看到value_type是一个pair类型的数据,pair里面的类型是Key类型和T类型。

也就是说,在map里面都是把key和value整合成一个类型存放的,而不是单独存放的。

那pair是什么呢?如下所示:

底层代码如下所示:

cpp 复制代码
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)
	{
	}
};

3.4 map的插入函数insert

我们在上面说过,map插入的数据类型是pair类型的数据,下面讲解插入的4种方式

方式一:定义一个有名的pair对象,然后将这个pair对象插入,代码如下:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	pair<string, string> kv1("first", "第一个");
	dict.insert(kv1);
	return 0;
}

方式二:插入一个匿名的pair对象,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	dict.insert(pair<string,string>("first", "第一个"));
	return 0;
}

方式三:调用一个make_pair的函数,创建一个pair,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	dict.insert(make_pair("first", "第一个"));
	return 0;
}

make_pair是一个函数模板,它返回一个pair对象。

方式四:使用隐式类型转换,会将initializer转换为pair类型,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	dict.insert({ "first", "第一个" });
	return 0;
}

插入的时候只关注key相不相等,跟value相不相等没有关系。如下所示:

map<string, string> dict;

dict.insert({ "first", "第一个" });

dict.insert({ "first", "第一个1111" }); //这个会插入失败

我们使用insert函数插入一个pair类型的数据时,无论插入成功都会返回一个pair类型的对象,这个pair对象,第二个参数是一个bool类型,插入成功就是true,插入失败就是false(map里面有这个key就会插入失败)。

第一个参数是一个迭代器:

如果插入成功,这个迭代器指向这个这个新插入的元素,

如果插入失败,就说明map里面已经有这个key了,就指向这个元素、

插入成功的话,返回 pair<新插入值所在的迭代器,true>

插入失败的话,返回 pair<已经存在的跟key相等值迭代器,false>

3.5 map的遍历

我们将上面的四种插入方式联系一下,代码如下:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	pair<string, string> kv1("first", "第一个");
	dict.insert(kv1);
	
	dict.insert(pair<string, string>("auto", "自动的"));

	dict.insert(make_pair("apple", "苹果"));

	dict.insert({ "pear","梨子" });
	return 0;
}

那我们现在应该如何遍历呢?直接解引用这个map类型的迭代器嘛?答案如下:

3.5.1 迭代器方式

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	pair<string, string> kv1("first", "第一个");
	dict.insert(kv1);
	
	dict.insert(pair<string, string>("auto", "自动的"));

	dict.insert(make_pair("apple", "苹果"));

	dict.insert({ "pear","梨子" });

	map<string, string>::iterator dit = dict.begin();
	while (dit != dict.end())
	{
		cout<<(*dit).first<< ":" << (*dit).second << endl;
		cout << dit->first << ":" << dit->second << endl;
		//上面等价于===
		//cout << dit.operator->()->first << ":" << dit.operator->()->second << endl;
		dit++;
	}
	return 0;
}

3.5.2 范围for

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	pair<string, string> kv1("first", "第一个");
	dict.insert(kv1);

	dict.insert(pair<string, string>("auto", "自动的"));

	dict.insert(make_pair("apple", "苹果"));

	dict.insert({ "pear","梨子" });

	for (auto& ele : dict)
	{
		cout << ele.first << ":" << ele.second << endl;
	}
	return 0;
}

3.6 map的构造函数

map的构造函数原型如下:

我们现在经常使用的是1,3,5这四种

3.6.1 空初始化

代码如下:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	return 0;
}

3.6.2 拷贝构造

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict;
	map<string, string> dict1(dict);
	return 0;
}

3.6.3 使用initializer初始化

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>

int main()
{
	map<string, string> dict = { {"apple","苹果"},{"pear","梨子"}, {"first","第一"} };
	return 0;
}

3.7 map的删除函数erase

函数原型如下:

erase删除只跟key有关系,跟value无关;

插入的时候更key value有关,查找的时候也只跟key有关系,

erase可以删除指定迭代器位置的元素,

也可以删除对应key的元素,这个key如果在map中存在,就删除,不存在也不会报错。

3.8 map的查找函数find

查找的时候只跟key有关系,根据key来查找。

返回对应元素的迭代器。

3.9 map的数据修改和[] (非常重要)

现在有一堆水果,让我们来统计水果的个数,代码如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>
#include<string>
int main()
{
	string arr[] = { "苹果","西瓜","苹果","西瓜","苹果","苹果","西瓜","苹果","香蕉","苹果","香蕉"};
	map<string, int> countMap;
	for (auto& ele : arr)
	{
		//首先查找这个水果在不在map中,如果在,就让对应的value++;如果不再就插入{水果,1}
		auto ret = countMap.find(ele);
		if (ret == countMap.end())
		{
			countMap.insert({ ele,1 });
		}
		else {
			ret->second++;
		}
	}
	for (auto& ele : countMap)
	{
		cout << ele.first << ":" << ele.second << endl;
	}
	return 0;
}

其实在实践中,map是不喜欢用这种方法来统计次数的。常用的如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>
#include<string>
int main()
{
	string arr[] = { "苹果","西瓜","苹果","西瓜","苹果","苹果","西瓜","苹果","香蕉","苹果","香蕉" };
	map<string, int> countMap;
	for (auto& ele : arr)
	{
		countMap[ele]++;
	}
	for (auto& ele : countMap)
	{
		cout << ele.first << ":" << ele.second << endl;
	}
	return 0;
}

我们对比这两段代码,[]是怎么做到统计次数的呢?下面给出详细解释:

之前我们使用[]都是像数组一样去访问,这里的[]其实不再是传统意义上的数组访问的[]了。这里使用[]已经改变了原来[]的意思了。

这里的[]指的是:你给我map里面的key,我返回对应的value的引用,原型如下所示:

这个接口综合了插入+查找+修改。

就是说这个接口有插入,查找,修改的功能,非常强大

它的解释如下:

这个函数的功能就等价于这个函数的功能,但是上面这个函数写的非常复杂,我们在上面讲过insert函数的返回值,如下所示:

insert无论插入是否成功,都会返回一个pair类型的对象,意味着这个insert这个函数不但充当着插入的功能,还充当了查找的功能

插入失败的情况下,它返回false和已经存在跟key相等值的迭代器,这不就是查找的功能嘛?

总结如下:

我们上面说过[]的实现等价于

下面我们来把这个实现展开,如下所示:

首先,方括号里面给了一个key以后,不管这个key是否存在,就直接插入一个pair键值对,第一个参数是key,第二个参数是调用对应的默认构造函数。

如果这个key不再map之中,就会插入key和mapped_type的默认值,然后返回mapped_type的引用,那我们就可以通过这个引用来修改,所以[]具备了插入和修改的功能。

如果key在map之中,insert就会插入失败,但是insert会返回这个节点的迭代器和false,然后返回mapped_type的引用,那我们就可以通过这个引用来修改,所以[]具备了查找和修改的功能。

现在我们解释上面的代码:

当"苹果"不再map里面,就会插入苹果,然后返回mapped_type的引用,然后通过这个引用修改

(所以[]具备了插入和修改的功能)

当我们第二次插入"苹果"时,苹果已经在map里面了,就会插入失败,然后返回已经存在的元素的mapped_type的引用,然后通过这个引用修改。

(所以[]具备了查找和修改的功能)

我们再举一个例子,如下所示:

cpp 复制代码
#include<iostream>
using namespace std;
#include<map>
#include<string>
int main()
{
	map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));

	//key不存在->插入{"insert",string()}
	dict["insert"];
	//key不存在->插入+修改
	dict["left"] = "左边";

	//key存在->查找+修改
	dict["left"] = "左边,剩余";

	//查找 使用[]来查找,一定要确保这个key在,如果不再的话就会执行插入功能
	cout << dict["left"] << endl;
	//插入 因为right不存在
	cout << dict["right"] << endl;
	//尽量少用[]来查找,只有确定这个key在,才用[]来查找。
	for (auto& ele : dict)
	{
		cout << ele.first << ":" << ele.second << endl;
	}
	return 0;
}

3.10 multimap和map的差异

multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余

那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全一样,比如find时,有多个key,返回中序第一个。

其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。

3.11 138.随机链表的复制-力扣(LeetCode)

题目链接:138. 随机链表的复制 - 力扣(LeetCode)

数据结构初阶阶段,为了控制随机指针,我们将拷贝结点链接在原节点的后面解决,后面拷贝节点还得解下来链接,非常麻烦。这里我们直接让{原结点,拷贝结点}建立映射关系放到map中,控制随机指针会非常简单方便,这里体现了map在解决一些问题时的价值,完全是降维打击。

3.11.1 不使用map的方法

cpp 复制代码
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head==nullptr)
            return nullptr;
        //拷贝节点
        Node* cur=head;
        while(cur)
        {
            Node* newnode=new Node(cur->val);
            newnode->next=cur->next;
            cur->next=newnode;
            cur=cur->next->next;
        }
        //处理random
        cur=head;
        Node* copycur=cur->next;
        while(cur)
        {
            if(cur->random==nullptr)
            {
                copycur->random=nullptr;
            }else
            {
                copycur->random=cur->random->next;
            }
           
            cur=copycur->next;
            if(cur)
                copycur=cur->next;
        }
        //将拷贝的节点剪下来
        cur=head;
        Node* copyhead=cur->next;
        copycur=cur->next;
        while(cur)
        {
            cur->next=copycur->next;
            cur=cur->next;
            if(cur!=nullptr)
            {
                copycur->next=cur->next;
                copycur=copycur->next;
            }else
            {
                copycur->next=nullptr;
            }  
        }
        return copyhead;
        
    }
};

3.11.2 使用map的方法

cpp 复制代码
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head==nullptr)
            return nullptr;
        Node* cur=head;
        Node* copyhead=nullptr;
        map<Node*,Node*> mymap;
        Node* pre=nullptr;
        while(cur)
        {
            Node* newnode=new Node(cur->val);
            if(copyhead==nullptr)
            {
                copyhead=newnode;
                pre=copyhead;
            }else
            {
                pre->next=newnode;
                pre=pre->next;
            }
             mymap.insert({cur,newnode});
            cur=cur->next;
           
        }
        cur=head;
        Node* copy=copyhead;
        while(cur)
        {
            if(cur->random==nullptr)
            {
                copy->random=nullptr;
            }else{
                copy->random=mymap[cur->random];
            }
            cur=cur->next;
            copy=copy->next;
        }
        return copyhead;
    }
};

3.12 692.前K个高频单词-力扣(LeetCode)

题目链接:692. 前K个高频单词 - 力扣(LeetCode)

方法一:使用map加稳定排序

cpp 复制代码
struct mycompare{
    bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2)
    {
        return kv1.second>kv2.second;
    }
};
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> mymap;
        for(auto& ele:words)
        {
            mymap[ele]++;
        }
        vector<pair<string,int>> myvector(mymap.begin(),mymap.end());
        stable_sort(myvector.begin(),myvector.end(),mycompare());
        int i=0;
        vector<string> ret;
        while(i<k)
        {
            ret.push_back(myvector[i].first);
            i++;
        }
        return ret;
    }
};

方法二:使用map加不稳定排序,然后调整仿函数

cpp 复制代码
struct mycompare{
    bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2)
    {
        return kv1.second>kv2.second || (kv1.second==kv2.second)&&kv1.first<kv2.first;
    }
};
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> mymap;
        for(auto& ele:words)
        {
            mymap[ele]++;
        }
        vector<pair<string,int>> myvector(mymap.begin(),mymap.end());
        sort(myvector.begin(),myvector.end(),mycompare());
        int i=0;
        vector<string> ret;
        while(i<k)
        {
            ret.push_back(myvector[i].first);
            i++;
        }
        return ret;
    }
};

方法三:使用优先级队列

cpp 复制代码
struct mycompare{
    bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2)
    {
        return kv1.second<kv2.second || (kv1.second==kv2.second)&&kv1.first>kv2.first;
    }
};
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> mymap;
        for(auto& ele:words)
        {
            mymap[ele]++;
        }
        priority_queue<pair<string,int>,vector<pair<string,int>>,mycompare> mypriority(mymap.begin(),mymap.end());
        vector<string> ret;
        for(int i=0;i<k;i++)
        {
            ret.push_back(mypriority.top().first);
            mypriority.pop();
        }
        return ret;
    }
};
相关推荐
-To be number.wan1 分钟前
算法学习日记 | 模拟
c++·学习·算法
EQ-雪梨蛋花汤2 分钟前
【问题反馈】JNI 开发:为什么 C++ 在 Debug 正常,Release 却返回 NaN?
开发语言·c++
王老师青少年编程2 分钟前
2023信奥赛C++提高组csp-s复赛真题及题解:密码锁
c++·真题·csp·密码锁·信奥赛·csp-s·提高组
naruto_lnq3 分钟前
高性能消息队列实现
开发语言·c++·算法
charlie1145141913 分钟前
malloc 在多线程下为什么慢?——从原理到实测
开发语言·c++·笔记·学习·工程实践
D_evil__5 分钟前
【Effective Modern C++】第四章 智能指针:18. 使用独占指针管理具备专属所有权的资源
c++
王老师青少年编程6 分钟前
2023信奥赛C++提高组csp-s复赛真题及题解:消消乐
c++·真题·csp·信奥赛·消消乐·csp-s·提高组
草莓熊Lotso7 分钟前
从零手搓实现 Linux 简易 Shell:内建命令 + 环境变量 + 程序替换全解析
linux·运维·服务器·数据库·c++·人工智能
进击的荆棘1 小时前
优选算法——滑动窗口
c++·算法·leetcode
_F_y8 小时前
MySQL用C/C++连接
c语言·c++·mysql