C++笔记归纳13:map & set

map & set

目录

[map & set](#map & set)

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

1.1.序列式容器

1.2.关联式容器

二、set系列的使用

2.1.set容器

2.2.set的构造和迭代器

2.3.set的增删查

2.4.multiset和set的差异

2.5.与set有关的试题

试题1:两个数组的交集

试题2:环形链表II

三、map系列的使用

3.1.map容器

3.2.map的插入

3.3.map的迭代器和[]功能

3.4.insert返回值说明

3.5.multimap和map的差异

3.6.与map有关的试题

试题3:随机链表的复制

试题4:前k个高频单词


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

1.1.序列式容器

string,vector,list,deque,array,forward_list...

**逻辑结构:**线性

1.2.关联式容器

map,set,unordered_map,unordered_set

**逻辑结构:**非线性

map和set的底层为红黑树,是一颗平衡二叉搜索树

  • set是key搜索场景的结构
  • map是key/value搜索场景的结构

二、set系列的使用

2.1.set容器

**T:**set底层关键字的类型

**比较方法:**默认T支持小于比较

(注:如果想按自己的需求,可以手动实现仿函数传给第二个模板参数)

**存储数据内存:**从空间适配器中申请

(注:可以自己实现内存池,传给第三个参数)

**底层:**红黑树

**增删查效率:**O(logN)

**迭代器遍历:**搜索树的中序

2.2.set的构造和迭代器

set的构造

无参默认构造

cpp 复制代码
explicit set(const key_compare & comp = key_compare(),const allocator_type & alloc = allocator_type());

迭代器区间构造

cpp 复制代码
template < class InputIterator>
set(InputIterator first, InputIterator last,const key_compare & comp = key_compare(),const allocator_type & = allocator_type());

拷贝构造

cpp 复制代码
set (const set& x);

初始化列表构造

cpp 复制代码
set(initializer_list<value_type> il,const key_compare & comp = key_compare(),const allocator_type & alloc = allocator_type());

set的迭代器

双向迭代器

cpp 复制代码
iterator->a bidirectional iterator to const value_type

正向迭代器

cpp 复制代码
iterator begin();
iterator end();

反向迭代器

cpp 复制代码
reverse_iterator rbegin();
reverse_iterator rend();

2.3.set的增删查

单值插入

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

int main()
{
	set<int> s1;
	//去重+升序
	s1.insert(5);
	s1.insert(2);
	s1.insert(7);
	s1.insert(5);
	s1.insert(3);
	//去重+降序
	set<int, greater<int>> s2;
	s2.insert(5);
	s2.insert(2);
	s2.insert(7);
	s2.insert(5);
	s2.insert(3);
	//升序打印
	set<int>::iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
	//降序打印
	set<int, greater<int>>::iterator it2 = s2.begin();
	while (it2 != s2.end())
	{
		cout << *it2 << " ";
		++it2;
	}
	cout << endl;

	return 0;
}

**注:**如果已经存在则插入失败

列表插入

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

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

	return 0;
}

**注:**已经在容器中存在的值不会插入

字符串插入

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

int main()
{
	set<string> strset = {"sort","insert","add"};
	for (auto& e : strset)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

**注:**按ASCII码进行比较

删除最小值

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

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;
	
	return 0;
}

删除输入值

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

int main()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	//直接删除x
	int x;
	cin >> x;
	int num = s.erase(x);
	if (num == 0)
	{
		cout << x << "不存在!" << endl;
	}
	else
	{
		cout << "删除成功!" << endl;
	}
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

查找+迭代器删除

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

int main()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	//直接查找在利用迭代器删除x
	int 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;
}

**注:**删除后的pos迭代器会失效

两种查找方法

cpp 复制代码
int main()
{
	set<int> s({2,3,5,4,8,7});
	int x;

	//算法库的查找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;
}

区间查找删除

lower_bound

返回大于等于val位置的迭代器

cpp 复制代码
iterator lower_bound(const value_type& val) const;

upper_bound

返回大于val位置的迭代器

cpp 复制代码
iterator upper_bound(const value_type val) const;

**示例:**删除某个区间的一些值

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>


int main()
{
	std::set<int> myset;
	for (int i = 1; i < 10; i++)
	{
		myset.insert(i * 10);
	}
	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;

	//删除[30,50]值

	//返回 >= 30
	//auto itlow = myset.lower_bound(30);
	//返回 > 50
	//auto itup = myset.upper_bound(50);

	//删除[25,55]值
	//返回 >= 25
	auto itlow = myset.lower_bound(25);
	//返回 > 55
	auto itup = myset.upper_bound(55);

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

2.4.multiset和set的差异

multiset和set的使用基本完全类似

**主要区别:**multiset支持值沉余

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

int main()
{
	//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;
	return 0;
}

迭代器查找

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

int main()
{
	//multiset:排序,但是不去重
	multiset<int> s = { 4,2,7,2,4,8,4,5,4,9 };
	//multiset:x可能会存在多个,find查找中序的第一个
	int x;
	cin >> x;
	auto pos = s.find(x);
	while (pos != s.end() && *pos == x)
	{
		cout << *pos << " ";
		++pos;
	}
	cout << endl;
	return 0;
}

迭代器查找+删除

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

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;

	/*pos = s.find(x);
	while (pos != s.end() && *pos == x)
	{
		pos = s.erase(pos);
	}*/
	s.erase(x);
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

count计算每个值出现次数

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <set>

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;
	s.erase(x);
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	cout << s.count(2) << endl;
	return 0;
}

2.5.与set有关的试题

试题1:两个数组的交集

题目内容:

给定两个数组nums1和nums2,返回它们的交集

输出结构中每个元素一定是唯一的

示例:

输入:nums1 = [1,2,2,1],num2 = [2,2]

输出:[2]

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> ret;
        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
            {
                ret.push_back(*it1);
                it1++;
                it2++;
            }
        }
        return ret;
    }
};

**交集:**相同存数据,同时++,不同小的++

**差集:**相同同时++,不同小的存数据后++

同步算法(叉集与并集)

假设以云相册为主

将相册(照片A,B,C)分给手机端,电脑端,平板端

当手机端输入一个新的照片D时,会和云相册作差集,将D上传到云相册

平板端与电脑端再与云相册比对,ABC是交集,而D是差集,所以会从云相册中下载照片D

当对手机端的C照片进行修改后,因为最后修改时间不同

会以时间最新的照片为准,同时其他的客户端也会同步更新

试题2:环形链表II

题目内容:

给定一个链表的头节点 head

返回链表开始入环的第一个节点

如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环

示例:

输入:head = [3,2,0,-4],pos = 1

输出:返回索引为1的链表节点

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*> s;
        ListNode* cur = head;
        while(cur)
        {
            if(s.count(cur))
            {
                return cur;
            }
            else
            {
                s.insert(cur);
            }
            cur = cur->next;
        }    
        return nullptr;
    }
};

三、map系列的使用

3.1.map容器

**Key:**key

**T:**value

3.2.map的插入

pair类类型:

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

**make_pair函数模板:**自动判断参数类型,并且返回

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

int main()
{
    //默认无参构造
	//map<string, string> dict;
	//initializer_list列表构造
	map<string, string>dict = { {"left","左边"},{"right","右边"},{"insert","插入"},{"string","字符串"} };

	//法1:
	pair<string, string> kv1("first", "第一个");
	dict.insert(kv1);

	//法2:
	dict.insert(pair<string, string>("second", "第二个"));

	//法3:
	dict.insert(make_pair("sort", "排序"));

	//法4:(C++11,多参数隐式类型转接)
	dict.insert({ "auto","自动的" });

	//遍历打印
	map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		//可以修改value,不支持修改key
		//it->first += 'x';
		it->second += 'x';
		cout << (*it).first << (*it).second << endl;
		cout << it->first << it->second << endl;
		cout << it.operator->()->first << it.operator->()->second << endl;
		++it;
	}
	cout << endl;

	return 0;
}

**注:**插入时,如果key相同,value不相等时,数据不会更新

3.3.map的迭代器和[]功能

示例1:

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

int main()
{
	//利用find和iterator修改功能,统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;

	for (const auto& str : arr)
	{
		//先查找水果在不在map中
		//不在:说明水果第一次出现,则插入{水果, 1}
		//在:则查找到的节点中水果对应的次数++
		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;
}

示例2:

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

int main()
{
	//利用find和iterator修改功能,统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		//[]先查找水果在不在map中
		//不在:说明水果第一次出现,则插入{水果, 0},同时返回次数的引用,++一下就变成1次了
		//在:则返回水果对应的次数++
		 countMap[str]++;
	}
	
	for (const auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;

	return 0;
}

示例3:

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

int main()
{
	map<string, string> dict;
	dict.insert(make_pair("sort", "排序"));

	//插入{"insert", string()} (key不在)
	dict["insert"];
	
	//插入+修改
	dict["left"] = "左边";
	
	//修改
	dict["left"] = "左边、剩余";
	
	//查找(key在)
	cout << dict["left"] << endl;

	//插入{"right",string()} (key不在)
	cout << dict["right"] << endl;
	
	return 0;
}

注:

**str不存在时[]的功能:**插入+修改

**str存在时[]的功能:**查找+修改

3.4.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>对象

如果key在map中,插入失败,则返回一个pair<iterator,bool>对象

返回pair对象:first是key所在结点的迭代器,second是false

如果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>

operator[]的内部实现

cpp 复制代码
// operator的内部实现
mapped_type& operator[] (const key_type& k)
{
	//如果k不在map中:
	//insert会插入k和mapped_type默认值,同时[]返回结点中存储mapped_type值的引用
	//可以通过引用修改返映射值,所以[]具备了插入 + 修改功能

	//如果k在map中:
	//insert会插入失败,但是insert返回pair对象的first是指向key结点的迭代器
	//返回值同时[]返回结点中存储mapped_type值的引用,所以[]具备了查找 + 修改功能

	pair<iterator, bool> ret = insert({ k, mapped_type() });
	iterator it = ret.first;
	return it->second;
}

3.5.multimap和map的差异

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

int main()
{
	multimap<string, string> dict;
	dict.insert({ "sort","排序" });
	dict.insert({ "sort","排序" });
	dict.insert({ "sort","排序1" });
	dict.insert({ "sort","排序2" });
	dict.insert({ "sort","排序3" });
	dict.insert({ "string","字符串" });

	for (auto e : dict)
	{
		cout << e.first <<  ":" << e.second << endl;
	}

	cout << endl;
	dict.erase("sort");

	for (auto e : dict)
	{
		cout << e.first << ":" << e.second << endl;
	}

	return 0;
}

3.6.与map有关的试题

试题3:随机链表的复制

题目内容:

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针random

该指针可以指向链表中的任何节点或空节点,构造这个链表的深拷贝

深拷贝应该正好由 n 个全新节点组成

每个新节点的值都设为其对应的原节点的值

新节点的 next 指针和 random 指针也都应指向复制链表中的新节点

并使原链表和复制链表中的这些指针能够表示相同的链表状态

复制链表中的指针都不应指向原链表中的节点

示例:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]

输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

cpp 复制代码
class Solution {
public:
	Node * copyRandomList(Node * head) 
	{
		map<Node*, Node*> nodeMap;
		Node * copyhead = nullptr, *copytail = nullptr;
		Node * cur = head;
		while (cur)
		{
			if (copytail == nullptr)
			{
				copyhead = copytail = new Node(cur->val);
			}
			else
			{
				copytail->next = new Node(cur->val);
				copytail = copytail->next;
			}
			
			//原节点和拷⻉节点map kv存储
			nodeMap[cur] = copytail;
			
			cur = cur->next;
		}
		
		//处理random
		cur = head;
		Node * copy = copyhead;
		while (cur)
		{
			if (cur->random == nullptr)
			{
				copy->random = nullptr;
			}
			else
			{
				copy->random = nodeMap[cur->random];
			}
			
			cur = cur->next;
			copy = copy->next;
		}
		return copyhead;
	}
};
试题4:前k个高频单词

题目内容:

给定一个单词列表 words 和一个整数 k

返回前 k 个出现次数最多的单词

返回的答案应该按单词出现频率由高到低排序

如果不同的单词有相同出现频率, 按字典顺序排序

示例:

输入:words = ["i", "love", "leetcode", "i", "love", "coding"],k = 2

输出:["i", "love"]

解析:"i" 和 "love" 为出现次数最多的两个单词,均为2次

注意:按字母顺序 "i" 在 "love" 之前。

**法1:**使用stable_sort

cpp 复制代码
class Solution 
{
public:
    struct kvCompare
    {
        bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2)
        {
            return kv1.second > kv2.second;
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        map<string,int> countMap;
        for(auto& e : words)
        {
            countMap[e]++;
        }

        vector<pair<string,int>> v(countMap.begin(),countMap.end());
        stable_sort(v.begin(),v.end(),kvCompare());

        vector<string> strV;
        for(int i = 0;i < k;i++)
        {
            strV.push_back(v[i].first);
        }

        return strV;
    }
};

注: sort底层是快排,不稳定,使用stable_sort更加稳定

**法2:**完善仿函数

cpp 复制代码
class Solution 
{
public:
    struct kvCompare
    {
        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);
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        map<string,int> countMap;
        for(auto& e : words)
        {
            countMap[e]++;
        }

        vector<pair<string,int>> v(countMap.begin(),countMap.end());
        sort(v.begin(),v.end(),kvCompare());

        vector<string> strV;
        for(int i = 0;i < k;i++)
        {
            strV.push_back(v[i].first);
        }

        return strV;
    }
};

**法3:**使用优先级队列

cpp 复制代码
class Solution 
{
public:
    struct Compare
    {
        bool operator()(const pair<string, int>& x, const pair<string, int>& y) const
        {
            //要注意优先级队列底层是反的
            //大堆要实现小于比较,所以这里次数相等
            //想要字典序小的在前面要比较字典序大的为真
            return x.second < y.second || (x.second == y.second && x.first > y.first);
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        map<string, int> countMap;
        for(auto& e : words)
        {
            countMap[e]++;
        }   
        //将map中的<单词,次数>放到priority_queue中
        //仿函数控制大堆,次数相同按照字典序规则排序
        priority_queue<pair<string, int>, vector<pair<string, int>>, Compare>
        p(countMap.begin(), countMap.end());
        vector<string> strV;
        for(int i = 0; i < k; ++i)
        {
            strV.push_back(p.top().first);
            p.pop();
        }
        return strV;
    }
};
相关推荐
xiangpanf2 小时前
PHP与Vue:前后端技术深度对比
开发语言·vue.js·php
1104.北光c°3 小时前
滑动窗口HotKey探测机制:让你的缓存TTL更智能
java·开发语言·笔记·程序人生·算法·滑动窗口·hotkey
for_ever_love__4 小时前
Objective-C学习 NSSet 和 NSMutableSet 功能详解
开发语言·学习·ios·objective-c
仰泳的熊猫7 小时前
题目2570:蓝桥杯2020年第十一届省赛真题-成绩分析
数据结构·c++·算法·蓝桥杯
似水明俊德10 小时前
02-C#.Net-反射-面试题
开发语言·面试·职场和发展·c#·.net
无极低码10 小时前
ecGlypher新手安装分步指南(标准化流程)
人工智能·算法·自然语言处理·大模型·rag
软件算法开发10 小时前
基于海象优化算法的LSTM网络模型(WOA-LSTM)的一维时间序列预测matlab仿真
算法·matlab·lstm·一维时间序列预测·woa-lstm·海象优化
Thera77710 小时前
C++ 高性能时间轮定时器:从单例设计到 Linux timerfd 深度优化
linux·开发语言·c++