【C++】map和set

接下来我们要介绍四种树形结构的关联式容器:set、map、multiset、multimap

这四种容器的共同点是:使用**平衡搜索树(即红黑树)**作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

因为这四个容器的底层容器比较复杂,会放在其他博客进行细讲,这里仅为它们的使用讲解。

set

set的介绍

std::set 是 C++ 标准库中的一种关联式容器 。它最核心的特点是:存储的元素既是键也是值,且自动排序、唯一

它的核心特性如下:

  1. 唯一性:容器中不允许有重复的元素。插入重复元素时,新元素会被忽略。

  2. 自动排序 :元素始终按照"严格弱排序"标准(默认是 std::less<T>,即升序)自动排列,因此遍历 set 时会得到有序序列。

  3. 元素不可直接修改 :因为元素的位置由其值决定,所以迭代器指向的是 const 元素。若要修改,需先删除原元素,再插入新元素。

  4. 底层实现 :通常是红黑树 (一种平衡二叉搜索树),因此插入、删除、查找的时间复杂度都是 O(log n)

它的常用模板参数如下:

  • Compare:可自定义比较函数,实现降序或按结构体某成员排序。

  • Alloc:一般使用默认值即可。

set的使用

1.set的构造

|--------------------------------------------------------------------------------------------------------------------|------------------------------|
| 函数声明 | 内容介绍 |
| set (const Compare& comp = Compare(), const Allocator& = Allocator() ); | 构造空的set |
| set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator() ); | 用[first, last)区 间中的元素构造 set |
| set ( const set<Key,Compare,Allocator>& x); | set的拷贝构造 |

复制代码
void TestSet1()
{
	//构造空的set
	set<int> set1;

	//迭代区间构造
	vector<int> v = { 4,3,7,1,20,4,37,44,3,2,2,2,2,1 };
	set<int> set2(v.begin(), v.end());
	for (auto& e : set2)
	{
		cout << e << " ";
	}
	cout << endl;//输出结果为:1 2 3 4 7 20 37 44
				 //降重+排序

	//拷贝构造
	set<int> set3(set2);
	for (auto& e : set3)
	{
		cout << e << " ";
	}
	cout << endl;//1 2 3 4 7 20 37 44
}
2.set的迭代器

|----------------------------------------|---------------------------------------|
| 函数声明 | 功能介绍 |
| iterator begin() | 返回set中起始位置元素的迭代器 |
| iterator end() | 返回set中最后一个元素后面的迭代器 |
| const_iterator cbegin() const | 返回set中起始位置元素的const迭代器 |
| const_iterator cend() const | 返回set中最后一个元素后面的const迭代器 |
| reverse_iterator rbegin() | 返回set第一个元素的反向迭代器,即end |
| reverse_iterator rend() | 返回set最后一个元素下一个位置的反向迭代器, 即rbegin |
| const_reverse_iterator crbegin() const | 返回set第一个元素的反向const迭代器,即cend |
| const_reverse_iterator crend() const | 返回set最后一个元素下一个位置的反向const迭 代器,即crbegin |

因为const迭代器与普通迭代的使用方法相似,所以这里就不进行演示const迭代器的使用方法:

复制代码
void TestSet2()
{
	set<int> s = { 1,2,3,4,5,6 };

	//正序遍历 1 2 3 4 5 6
	auto begin = s.begin();
	auto end = s.end();
	while (begin != end)
	{
		cout << *begin << " ";
		begin++;
	}
	cout << endl;

	//逆序遍历 6 5 4 3 2 1
	auto rbegin = s.rbegin();
	auto rend = s.rend();
	while (rbegin != rend)
	{
		cout << *rbegin << " ";
		rbegin++;
	}
	cout << endl;
}
3.set的容量

|------------------------|----------------------------|
| 函数声明 | 功能介绍 |
| bool empty ( ) const | 检测set是否为空,空返回true,否则返回true |
| size_type size() const | 返回set中有效元素的个数 |

复制代码
void TestSet3()
{
	set<int> set1 = { 1,2,3,4,5,6 };
	set<int> set2;

	cout << set1.empty() << endl;//0
	cout << set2.empty() << endl;//1

	cout << set1.size() << endl;//6
	cout << set2.size() << endl;//0
}
4.set的修改操作

|-------------------------------------------------------|-----------------------------------------------------------------------------------------------------------|
| 函数声明 | 功能介绍 |
| pair<iterator,bool> insert ( const value_type& x ) | 在set中插入元素x,实际插入的是<x, x>构成的 键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经 存在,返回<x在set中的位置,false> |
| oid erase ( iterator position ) | oid erase ( iterator position ) |
| size_type erase ( const key_type& x ) | 删除set中值为x的元素,返回删除的元素的个数 |
| void erase ( iterator first, iterator last ) | 删除set中[first, last)区间中的元素 |
| void clear ( ) | 将set中的元素清空 |
| iterator find ( const key_type& x ) const | 返回set中值为x的元素的位置 |
| size_type count ( const key_type& x ) const | 返回set中值为x的元素的个数 |

此处要注意的是:set的删除会出现迭代器失效的问题。

复制代码
void TestSet4()
{
	set<int> set1 = { 1,2,3,4,5 };

	//输入set内未有的值则插入成功,否则插入失败
	int x;
	cin >> x;
	if (set1.insert(x).second)
	{
		cout << "插入成功!" << endl;
	}
	else
	{
		cout << "插入失败!" << endl;
	}
	for (auto& e : set1)
	{
		cout << e << " ";
	}
	cout << endl;

	//三种删除演示
	//1.迭代器删除,配合find使用
	int tmp = 3;
	auto find = set1.find(3);
	set1.erase(find);
	for (auto& e : set1)
	{
		cout << e << " ";
	}
	cout << endl;

	//但这样删除操作太冗余了,不如直接使用下面的值删除
	//2.删除某个值
	set1.erase(5);
	for (auto& e : set1)
	{
		cout << e << " ";
	}
	cout << endl;

	//3.迭代区间删除
	set1.erase(--set1.end(), set1.end());
	for (auto& e : set1)
	{
		cout << e << " ";
	}
	cout << endl;

	//count在set中用来实现快速查找
	//判断1在不在set中
	if (set1.count(1))
	{
		cout << "1在set1中" << endl;
	}

	//clear,一键清空
	set1.clear();
	for (auto& e : set1)
	{
		cout << e << " ";
	}
	cout << endl;
}

multiset

multiset的介绍

std::multisetstd::set 非常相似,核心区别在于它允许存储重复的元素

它的核心特性如下:

特性 说明
允许重复键 这是与 set 最本质的区别。多个元素可以拥有相同的值。
自动排序 元素依然按照 Compare 准则(默认升序)自动排序。
元素不可直接修改 迭代器同样指向 const 元素,修改需先删后插。
底层实现 通常也是红黑树 ,插入、删除、查找时间复杂度 O(log n)

总的来说,multiset 可以看作"允许重复的 set",在需要有序存储且不强制唯一性的场景下非常实用。

multiset的使用

此处只简单介绍演示set与multiset的不同,其他接口接口与set相同,可参考set。

  1. 插入 (insert)

    总是成功,返回指向新插入元素的迭代器(set 返回 pair<iterator,bool> 以指示是否插入成功)。

  2. 计数 (count)

    返回值可能 大于 1,表示有多少个元素等于给定键。

  3. 删除 (erase)

    • erase(key):会删除 所有 等于该键的元素,返回删除的个数。

    • erase(pos):删除指定迭代器位置的单个元素。

  4. 查找 (find)

    若存在多个相同键,find 返回指向其中 任意一个 的迭代器(通常是第一个)。

  5. 区间操作
    lower_boundupper_boundequal_range 对于处理重复键尤其有用。

    • equal_range(key) 返回一个 pair,包含区间 [first, last),该区间覆盖所有等于 key 的元素。
复制代码
void TestMultiSet1()
{
	multiset<int> ms = { 1, 3, 2, 1, 2 };// 允许重复
	ms.insert(2);// 插入重复值

	for (int x : ms) 
	{
		cout << x << " ";//1 1 2 2 2 3
	}
	cout << endl;
	cout << "2的个数为: " << ms.count(2) << endl; // 输出 3

	// 删除所有值为 2 的元素
	ms.erase(2);
	cout << "删除之后,ms的大小为:" << ms.size() << endl; // 输出 2 (两个1和一个3)

map

map的介绍

std::map 是 C++ 标准库中一个有序的键值对容器 。它的核心特点是:存储的元素是键值对(pair<const Key, T>),键唯一且自动排序

它的核心特性如下:

特性 说明
键值分离 元素是 pair<const Key, T>Key 是键(用于索引),T 是值(存储的数据)。
键唯一 不允许两个元素拥有相同的键。插入重复键时,新元素会被忽略。
自动排序 元素按照键的顺序排列,排序准则由 Compare(默认 std::less<Key>)定义,即升序。
键不可修改 因为键用于确定元素位置,所以键是 const 的。若要修改,需删除原元素再插入新键值对。
值可修改 键对应的值(mapped_type)可以直接修改。
底层实现 通常是红黑树 (平衡二叉搜索树),插入、删除、查找的时间复杂度均为 O(log n)

它的常用模板参数如下:

  • Key:键的类型。

  • T:值的类型。

  • Compare:排序准则,默认升序。可自定义实现降序或按特定规则排序。

map的使用

1.map的构造

|-------|-----------|
| 函数声明 | 功能介绍 |
| map() | 构造一个空的map |

复制代码
void TestMap1()
{
	map<int, int> m;
}
2.map的迭代器

|-------------------|-----------------------------------------------------------|
| 函数声明 | 功能介绍 |
| begin()和end() | begin:首元素的位置,end最后一个元素的下一个位置 |
| cbegin()和cend() | 与begin和end意义相同,但cbegin和cend所指向的元素不 能修改 |
| rbegin()和rend() | 反向迭代器,rbegin在end位置,rend在begin位置,其 ++和--操作与begin和end操作移动相反 |
| crbegin()和crend() | 与rbegin和rend位置相同,操作相同,但crbegin和crend所 指向的元素不能修改 |

同样的,因为const迭代器与普通迭代的使用方法相似,所以这里就不进行演示const迭代器的使用方法:

复制代码
void TestMap2()
{
	map<int, int> m = { {7,7},{5,5},{3,3},{4,4},{1,1},{5,5} };
	auto begin = m.begin();
	auto end = m.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
		/*1:1
		3 : 3
		4 : 4
		5 : 5
		7 : 7*/
	auto rbegin = m.rbegin();
	auto rend = m.rend();
	while (rbegin != rend)
	{
		cout << rbegin->first << ":" << rbegin->second << endl;
		rbegin++;
	}
		/*7:7
		5 : 5
		4 : 4
		3 : 3
		1 : 1*/
}
3.map的容量与元素访问

|-------------------------------------------------|----------------------------------|
| 函数声明 | 功能介绍 |
| bool empty ( ) const | 检测map中的元素是否为空,是返回 true,否则返回false |
| size_type size() const | 返回map中有效元素的个数 |
| mapped_type& operator[] (const key_type& k) | 返回去key对应的value |

要注意:operator[] 会插入默认值, 如果键不存在,map[key] 会自动插入一个值初始化后的元素。所以operator[ ]在map中有查找、修改和插入的功能:

复制代码
void TestMap3()
{
	map<string, string> m1;
	map<int, int> m = { {7,7},{5,5},{3,3},{4,4},{1,1},{5,5} };

	cout << m1.empty() << endl; //1;
	cout << m.empty() << endl; //0;

	cout << m1.size() << endl;//0
	cout << m.size() << endl;//5

	//[]的插入功能
	m1["string"];
	cout << m1.size() << endl;//此时m1的size变为1,证明已经插入

	//[]的插入加修改功能
	m1["insert"] = "插入";
	cout << m1.size() << endl;
	auto begin = m1.begin();
	auto end = m1.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
	//insert:插入
	//string :

	//[]的修改功能
	m1["insert"] = "我是修改值";
	begin = m1.begin();
	end = m1.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
	//insert:我是修改值
	//string :
}
4.map的修改操作

|--------------------------------------------------|--------------------------------------------------------------------------------|
| 函数声明 | 功能介绍 |
| pair insert ( const value_type& x ) | 在map中插入键值对x,注意x是一个键值对,返回值也是键值对:iterator代表新插入元素的位置,bool代表释放插入成功 |
| void erase ( iterator position ) | 删除position位置上的元素 |
| size_type erase ( const key_type& x ) | 删除键值为x的元素 |
| void erase ( iterator first, iterator last ) | 删除[first, last)区间中的元素 |
| void swap ( map& mp ) | 交换两个map中的元素 |
| void clear ( ) | 将map中的元素清空 |
| iterator find ( const key_type& x ) | 在map中插入key为x的元素,找到返回该元素的位置的迭代器,否则返回end |
| const_iterator find ( const key_type& x ) const | 在map中插入key为x的元素,找到返回该元素的位置的const迭代器,否则返回cend |
| size_type count ( const key_type& x ) const | 返回key为x的键值在map中的个数,注意 map中key是唯一的,因此该函数的返回值 要么为0,要么为1,因此也可以用该函数来检测一个key是否在map中 |

复制代码
void TestMap4()
{
	map<string, string> m1;
	map<string, string> m2;

	m1.insert({ "left", "左边" });
	m1.insert({ "right", "右边" });
	m1.insert({ "string", "字符串" });
	m1.insert({ "insert","插入" });
	m1.insert({ "erase","删除" });
	m1.insert({ "find", "查找" });

	auto begin = m1.begin();
	auto end = m1.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
	cout << endl;

	//用find找到位置后删除
	auto tmp = m1.find("left");
	m1.erase(tmp);

	begin = m1.begin();
	end = m1.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
	cout << endl;

	//直接删除某值
	m1.erase("string");
	begin = m1.begin();
	end = m1.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
	cout << endl;

	//删除find和right这个区间的所有值
	auto begin1 = m1.find("find");
	auto end1 = m1.find("right");

	m1.erase(begin1, end1);

	begin = m1.begin();
	end = m1.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
	cout << endl;

	//交换两个map
	m1.swap(m2);

	begin = m1.begin();
	end = m1.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
	cout << endl;

	//count用来查找
	if (m2.count("right"))
	{
		cout << "找到了" << endl;
	}

	//clear清除m2
	m2.clear();
	begin = m1.begin();
	end = m1.end();
	while (begin != end)
	{
		cout << begin->first << ":" << begin->second << endl;
		begin++;
	}
	cout << endl;
}

multimap

multimap的介绍

std::multimap 是 C++ 标准库中的有序键值对容器 ,它与 std::map 非常相似,核心区别在于允许存储重复的键

它的核心特性如下:

特性 说明
键值分离 元素是 pair<const Key, T>Key 是键(用于索引),T 是值(存储的数据)。
键可重复 这是与 map 最本质的区别。允许多个元素拥有相同的键。
自动排序 元素按照键的顺序排列,排序准则由 Compare(默认 std::less<Key>)定义,即升序。
键不可修改 因为键用于确定元素位置,所以键是 const 的。若要修改,需删除原元素再插入新键值对。
值可修改 键对应的值(mapped_type)可以直接修改(通过迭代器)。
底层实现 通常是红黑树 (平衡二叉搜索树),插入、删除、查找的时间复杂度均为 O(log n)

std::multimap 可以看作"允许重复键的 map",在需要存储一对多关系(如一个学生多门课程成绩、一个作者多本书籍)且希望按键有序排列时非常实用。

multimap的使用

同样的,此处只简单介绍演示map与multimap的不同,其他接口接口与map相同,可参考map。

操作 map multimap
插入 (insert) 返回 pair<iterator, bool>bool 指示是否成功(键唯一) 返回 iterator(指向新插入元素),总是成功
下标访问 (operator[]) 支持,键不存在时插入默认值 不支持(因为无法确定返回哪个值)
元素访问 (at) 支持 不支持
计数 (count) 返回 0 或 1 返回 可能大于 1(等于该键的元素个数)
删除 (erase(key)) 删除该键的元素(最多一个) 删除 所有 匹配该键的元素,返回删除个数
查找 (find) 返回指向该键的迭代器 若存在多个相同键,返回指向 其中任意一个 的迭代器(通常是第一个)
复制代码
void TestMap5()
{
	multimap<std::string, int> scores;

	// 插入元素(可以重复键)
	scores.insert({ "Alice", 90 });
	scores.insert({ "Bob", 85 });
	scores.insert({ "Alice", 95 });  // 允许相同键
	scores.insert({ "Bob", 88 });

	for (const auto& e : scores) {
		std::cout << e.first << ": " << e.second << std::endl;
	}
	// Alice: 90
	// Alice: 95
	// Bob: 85
	// Bob: 88

	// 统计键的个数
	std::cout << "Alice 出现次数: " << scores.count("Alice") << std::endl; // 输出 2

	// 删除所有匹配键的元素
	scores.erase("Alice");
	std::cout << "删除 Alice 后 size: " << scores.size() << std::endl; // 输出 2
}

oj当中的使用

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

这题思路很简单,就是利用map的性质来统计单词列表words中每个单词出现的个数后,再利用vector进行排序。这里要注意自己写一个比较的仿函数,因为pair的比较规则为:先比较第一个元素(first),如果 first 不相等,则结果由 first 决定;只有当 first 相等时,才比较第二个元素(second)。而这并不是我们想要的,所以需要我们自己编写。

然后注意,对vector进行排序时要使用stable_sort,因为题目要求我们如果不同的单词有相同出现频率, 按字典顺序 排序。而map自身的性质已经做到了这一点。所以我们只需要保证后续的排序不破坏它的顺序即可。即使用稳定的排序:stable_sort可以做到这一点。

复制代码
class Solution {
public:
    struct Compare
    {
        bool operator()(const pair<string, int>& a, const pair<string, int>& b)
        {
            return a.second > b.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(), Compare());

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

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

这题比较简单,利用set的去重+排序的性质就非常好做。

复制代码
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        set<int> set1(nums1.begin(), nums1.end());
        set<int> set2(nums2.begin(), nums2.end());

        auto begin1 = set1.begin();
        auto begin2 = set2.begin();

        vector<int> ans;

        while(begin1 != set1.end() && begin2 != set2.end())
        {
            if(*begin1 < *begin2)
            {
                ++begin1;
            }
            else if(*begin1 > *begin2)
            {
                ++begin2;
            }
            else
            {
                ans.push_back(*begin1);
                begin1++;
                begin2++;
            }
        }
        return ans;
    }
};
相关推荐
keep intensify2 小时前
单源最短路径
数据结构·c++·算法
2401_873544922 小时前
分布式缓存一致性
开发语言·c++·算法
kyriewen112 小时前
为什么我的代码在测试环境跑得好好的,一到用户电脑就崩?原来凶手躲在地址栏旁边
开发语言·前端·javascript·chrome·ecmascript·html5
sheji34162 小时前
【开题答辩全过程】以 基于Java的饮品店管理系统的实现为例,包含答辩的问题和答案
java·开发语言
暴躁小师兄数据学院2 小时前
【WEB3.0零基础转行笔记】go编程篇-第12讲:go-zero入门实战
开发语言·笔记·golang·web3·区块链
小北方城市网2 小时前
JavaScript 实战 —— 实现一个简易的 TodoList(适合前端入门 / 进阶)
开发语言·前端·javascript
向上_503582912 小时前
配置Protobuf输出Java文件或kotlin文件
android·java·开发语言·kotlin
njidf2 小时前
C++中的观察者模式
开发语言·c++·算法
IAUTOMOBILE2 小时前
C++ 入门基础:开启编程新世界的大门
java·jvm·c++