【C++】map和set的使用

目录

[1. set(集合)](#1. set(集合))

[1.1 插入元素(insert)](#1.1 插入元素(insert))

[1.2 删除元素(erase)](#1.2 删除元素(erase))

[1.3 查找元素(find/count)](#1.3 查找元素(find/count))

[1.4 边界查找(lower_bound/upper_bound)](#1.4 边界查找(lower_bound/upper_bound))

[1.5 遍历元素](#1.5 遍历元素)

[2. multiset](#2. multiset)

[2.1 查找元素(find)](#2.1 查找元素(find))

[2.2 删除元素(erase)](#2.2 删除元素(erase))

[3. map(映射)](#3. map(映射))

[3.1 构造与初始化](#3.1 构造与初始化)

[3.2 插入键值对(insert)](#3.2 插入键值对(insert))

[3.3 访问与修改值([ ] / at / 迭代器)](#3.3 访问与修改值([ ] / at / 迭代器))

[3.4 查找操作(find/count/边界函数)](#3.4 查找操作(find/count/边界函数))

[3.5 删除键值对(erase)](#3.5 删除键值对(erase))

[3.6 遍历元素](#3.6 遍历元素)

[4. multimap](#4. multimap)

[4.1 插入元素(insert)](#4.1 插入元素(insert))

[4.2 删除元素(erase)](#4.2 删除元素(erase))

[4.3 查找元素(find/equal_range)](#4.3 查找元素(find/equal_range))

[5. 练习](#5. 练习)


序列容器和关联容器

在 C++ 标准库中,关联容器是一类以键(key)为核心来存储和访问元素的容器,与序列容器(如 vector、list deque 等是线性存储,访问元素通过位置索引)不同,关联容器的元素存储和访问不依赖位置,而是通过键的特性(如排序规则或哈希值)来管理,因此支持高效的查找、插入和删除操作。
标准库中的关联容器主要分为两类:有序关联容器无序关联容器。有序的比如 map、set、multimap、multiset,它们基于红黑树,元素按键排序;无序的比如 unordered_map、unordered_set 等,基于哈希表,元素无序,但查找更快(平均 O (1))。

1. set(集合)

set 是一种存储唯一元素 的容器,并且元素是有序的(默认升序),且不允许有重复值,重复插入元素会被忽略。

1.1 插入元素(insert)

insert用于向set中添加元素,重复元素会被忽略。其返回值为pair<iterator,bool>:

  • 第一个成员(迭代器):指向插入的元素(或已存在的重复元素);
  • 第二个成员(bool):true表示插入成功,false表示元素已存在;
复制代码
int main() 
{
    set<int> s;

    // 插入单个元素
    auto ret1 = s.insert(3);
    cout << "插入3:" << (ret1.second ? "成功" : "失败") << endl; // 成功

    auto ret2 = s.insert(3);
    cout << "再次插入3:" << (ret2.second ? "成功" : "失败") << endl; // 失败(重复)

    // 插入多个元素
    s.insert({ 1, 2, 4 }); //插入初始化列表

    // 范围插入(从数组插入)
    int arr[] = { 5, 6 };
    s.insert(arr, arr + 2);

    // 遍历结果(自动排序):1 2 3 4 5 6
    for (int x : s) 
        cout << x << " ";

    cout << endl;
    return 0;
}

set的迭代器:全程只读

set 的元素不能修改,因为修改会破坏有序性,set的iterator本质是const_iterator,无论声明为iterator还是const_iterator,都不能修改元素。因为set的元素本身就是排序的"键",修改元素会直接破坏红黑树的有序结构,因此标准库强制迭代器为只读属性。

如需修改set元素,必须先erase旧元素,再insert新元素(相当于重新插入排序)。

1.2 删除元素(erase)

erase用于删除set中的元素,有 3 种常用形式,需注意删除后迭代器会失效:

  • iterator erase(const_iterator position); 删除迭代器pos指向的元素,返回下一个有效迭代器。
  • size_type erase(const value_type& val); 删除值为val的元素,返回删除的元素个数。
  • iterator erase(const_iterator first, const_iterator last); 删除[first, last)范围内的元素,返回下一个有效迭代器。
复制代码
int main()
{
	set<int> s = { 1, 2, 3, 4, 5 };

	size_t del_count = s.erase(3);
	cout << "删除3的个数:" << del_count << endl; // 1(删除成功)

	auto pos = s.find(4);
	if (pos != s.end()) 
	{
		pos = s.erase(pos); //删除4,并更新迭代器(指向5)
		cout << "删除后下一个元素:" << *pos << endl; // 5
	}

	return 0;
}

1.3 查找元素(find/count)

  • iterator find(const value_type& val); 查找值为val的元素,返回指向该元素的迭代器,若不存在,返回end()。
  • size_type count(const value_type& val) const; 返回值为val的元素个数(set中只能是0或1,可用于判断元素是否存在,但find更高效)。
复制代码
int main()
{
	set<int> s = { 10, 20, 30 };

	// 查找元素
	auto it = s.find(20);
	if (it != s.end()) 
	{
		cout << "找到元素:" << *it << endl; // 20
	}
	else 
	{
		cout << "未找到元素" << endl;
	}

	// 判断元素是否存在(count版本)
	if (s.count(30)) 
	{
		cout << "30存在" << endl; // 存在
	}
	if (s.count(40) == 0) 
	{
		cout << "40不存在" << endl; // 不存在
	}

	return 0;
}

int main()
{
	set<int> s = { 1, 2, 3, 4, 5 };

	auto pos1 = find(s.begin(), s.end(), 5); //算法库查找O(n)
	auto pos2 = s.find(5); //set自身实现的查找 O(logn)

	cout << *pos1 << " " << *pos2 << endl;
	return 0;
}

算法库的find,来自头文件<algorithm>头文件的通用算法,它只认识迭代器,不知道容器的底层实现,因此只能做"傻瓜式"的线性遍历。它从begin()到end()逐个比较元素,找到第一个与目标值相等的元素即返回迭代器,否则返回end(),时间复杂度为O(n)。

set自身的find成员函数,是为set的内部结构量身设计的,利用set底层红黑树特性进行查找,时间复杂度为O(logn)。

对set进行查找时,必须优先使用set::find,充分利用其O(logn)的高效特性。

1.4 边界查找(lower_bound/upper_bound)

  • lower_bound(val):返回第一个大于等于val位置的迭代器。
  • upper_bound(val):返回第一个大于val位置的迭代器。
  • equal_range(val):返回pair<lower_bound(val),upper_bound(val)>,表示与val相等的元素范围(set中最多一个元素)。
复制代码
int main()
{
	set<int> myset;
	for (int i = 1; i < 10; i++)
	{
		myset.insert(i * 10); //10 20 30 40 50 60 70 80 90 
	}

	//将25--55之间的值删除	

	//返回第一个 >=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;

    // equal_range:[70,80)
    auto range = myset.equal_range(70);
    cout << "equal_range的范围:";
    for (auto it = range.first; it != range.second; ++it) 
    {
	    cout << *it << " "; // 70
    }

	return 0;
}

1.5 遍历元素

复制代码
int main()
{
	set<int> s = { 3,1,2,4 }; //默认升序
    //set<int,greater<int>> s = { 3,1,2,4 }; //自定义比较器(降序)

	//1.迭代器遍历
	set<int>::iterator it = s.begin(); //auto it = s.begin();	
	while (it != s.end())
	{
		//*it = 1; 错误!C3892"it":不能给常量赋值
		cout << *it << " ";   //1 2 3 4
		++it;
	}
	cout << endl;

	//2.范围for遍历(底层依赖迭代器)
	for (auto e : s)
	{
		cout << e << " "; //1 2 3 4
	}
	cout << endl;
}

2. multiset

multiset是C++标准库中一种有序关联容器,与set类似,底层基于红黑树实现(保证元素有序),但核心区别是:运行存储重复元素。

插入、删除、查找的时间复杂度均为 O(log n)。

2.1 查找元素(find)

find(val):返回第一个值为val的元素的迭代器(即中序第一个),若不存在,返回end();

复制代码
int main()
{
	multiset<int> ms = { 1, 2, 2, 3, 3, 3 };

	//查找第一个3
	auto it = ms.find(3);
	if (it != ms.end()) 
	{
		cout << "第一个3的位置:" << *it << endl; // 3
	}

	//查找所有3的范围(equal_range)
	auto range = ms.equal_range(3);
	cout << "所有3的元素:";
	for (auto i = range.first; i != range.second; ++i) 
	{
		cout << *i << " "; // 3 3 3
	}
	cout << endl;

	return 0;
}

2.2 删除元素(erase)

erase(const T& val)会删除所有值为val的元素,返回删除的元素个数;

复制代码
int main()
{
	multiset<int> s = { 1, 2, 2, 3, 3, 3 };
	s.erase(3);//删除所有的3
	for (auto e : s)
	{
		cout << e << " "; //1 2 2
	}
	cout << endl;

	return 0;
}

3. map(映射)

在 C++ 标准库中,map 是一种有序关联容器 ,专门用于存储键值对(key-value) 数据。它的核心特点是通过 "键(key)" 快速索引 "值(value)",且键具有唯一性(不可重复)容器会自动按键的规则排序。底层通常基于红黑树(平衡二叉搜索树)实现, 因此插入、删除、查找操作的时间复杂度均为 O(log n)
键值对结构:每个元素是 pair<const Key, Value>类型(key是常量,不可修改,value可修改)。

3.1 构造与初始化

map支持多种初始化方式,最常用的包括:

复制代码
int main() 
{
	// 1. 默认构造
	map<string, int> m1;

	// 2. 初始化列表构造(C++11+)
	map<string, int> m2 = { {"apple", 5}, {"banana", 3}, {"orange", 2} };

	// 3. 拷贝构造
	map<string, int> m3(m2);

	// 4. 自定义比较器
	map<string, int, greater<string>> m4 = { {"a", 1}, {"b", 2} }; // 按key降序:"b" -> "a"

	return 0;
}

3.2 插入键值对(insert)

常见插入形式:

复制代码
int main()
{
	map<string, string> dict;

	// 方式1:显式构造pair对象
	pair<string, string> kv("first", "第一个");
	dict.insert(kv);

	// 方式2:直接构造临时pair对象,插入后临时对象销毁
	dict.insert(pair<string, string>("second", "第二个"));

	// 方式3:make_pair是C++标准库的一个模板函数,可以自动推导参数类型,返回一个对应的pair对象
	dict.insert(make_pair("sort", "排序"));

	// 方式4:初始化列表(C++11+,推荐)
	dict.insert({ "auto", "自动的" });

	return 0;
}

insert用于向map中插入键值对,仅当key,不存在时插入成功;若key已存在,则插入失败(不会覆盖原有value)。

函数原型:

复制代码
// 函数原型(简化)
pair<iterator, bool> insert(const pair<const Key, Value>& value);

参数:单个键值对(pair对象--> pair<const Key, Value>类型);

返回值:pair<iterator, bool>

iterator:若插入成功,指向插入的键值对,若插入失败,指向已存在的同key键值对;

bool:true表示插入成功,false表示插入失败(key已存在);

复制代码
int main() 
{
    map<string, int> m;

    // 插入新键值对(成功)
    auto res1 = m.insert(pair<string, int>("apple", 5));
    if (res1.second) 
    {
        cout << "插入成功:" << res1.first->first << " -> " << res1.first->second << endl; //apple -> 5
    }

    // 插入重复key(失败)
    auto res2 = m.insert(make_pair("apple", 10)); //make_pair构造pair
    if (!res2.second) 
    {
        cout << "插入失败(key已存在):" << res2.first->first << " 当前值:" << res2.first->second << endl; //apple 当前值:5(未被覆盖)
    }

    return 0;
}

3.3 访问与修改值([ ] / at / 迭代器)

\]运算符的作用是通过key访问对应的value,map\[key\]的行为分为两种情况: > 1. 当key已存在时,**返回key对应value的引用,可以直接读取或修改。** > 2. 当key不存在时,**会自动插入一个新的键值对** ,其中key为传入的参数,value为其类型的默认构造值(如int为0,string为空串,自定义类需有默认构造函数),**返回这个新value的引用。** > **注意:\[ \] 的副作用,访问不存在的key时会自动插入,若仅需"查询"而不希望插入,应使用find或at。** int main() { map m = { {"apple", 5}, {"banana", 3} }; // 访问已存在的key cout << m["apple"] << endl; // 输出:5 // 修改已存在的value(通过引用) m["banana"] = 10; cout << m["banana"] << endl; // 输出:10 // 访问不存在的key cout << m["orange"] << endl; // 输出:0(int的默认值) // 此时map中已自动插入键值对:{"orange", 0} cout << m.size() << endl; // 输出:3 return 0; } > \[ \]最适合的场景是:需要便捷地访问并可能修改value,且允许自动插入新键值对。例如:统计频率。 int main() { string arr[] = { "苹果","西瓜","苹果","西瓜","橘子","香蕉","西瓜" }; map countMap; for (const auto& str : arr) { countMap[str]++; } for (const auto& e : countMap) { cout << e.first << ":" << e.second << endl; } cout << endl; return 0; } > at(key):访问key对应的值。若key不在,会抛出out_of_range异常(更安全)。 > > 迭代器:通过it-\>first访问key,it-\>second访问/修改value。 int main() { map m = { {"apple", 5}, {"banana", 3} }; // 用at访问(更安全) try { cout << m.at("apple") << endl; // 5 m.at("pear") = 2; //抛出异常(pear不存在) } catch (const out_of_range& e) { cout << e.what() << endl; //输出异常信息 } //用迭代器修改value auto it = m.find("apple"); if (it != m.end()) { it->second = 20; // 修改value为20 cout << it->second << endl; // 20 } return 0; } ### 3.4 查找操作(find/count/边界函数) > find(key):查找key,返回指向对应键值对的迭代器;若不存在,返回end()。 > > count(key):返回key出现的次数(map中只能是0或1,用于判断key是否存在)。 > > 边界函数:lower_bound(key)(第一个\>=key的键值对)、upper_bound(key)(第一个\>key的键值对)equal_range(key)(返回 \[lower_bound,upper_bound\] 范围)。 int main() { map m = { {1, "one"}, {3, "three"}, {5, "five"} }; //find查找 auto it = m.find(3); if (it != m.end()) { cout << "找到:" << it->first << " -> " << it->second << endl; // 3 -> three } //count判断存在性 if (m.count(5)) { cout << "5存在" << endl; } if (m.count(2) == 0) { cout << "2不存在" << endl; } // 边界函数 auto lb = m.lower_bound(3); // 第一个>=3的元素(3) cout << "lower_bound(3):" << lb->first << endl; // 3 auto ub = m.upper_bound(3); // 第一个>3的元素(5) cout << "upper_bound(3):" << ub->first << endl; // 5 auto range = m.equal_range(3); // [3,5) cout << "equal_range范围内的key:"; for (auto i = range.first; i != range.second; ++i) { cout << i->first << " "; // 3 } return 0; } ### 3.5 删除键值对(erase) > 删除元素时,只有被删除元素的迭代器失效,其他迭代器有效。 > * erase(key):删除key对应的键值对,返回删除的个数(map中只能是0或1)。 > * erase(iterator pos):删除迭代器pos指向的键值对,返回下一个有效迭代器。 > * erase(iterator first, iterator last):删除\[first, last\]范围内的键值对,返回下一个有效迭代器。 int main() { map m = { {"a", 1}, {"b", 2}, {"c", 3}, {"d", 4} }; //按key删除 int del_count = m.erase("b"); cout << "删除b的个数:" << del_count << endl; // 1 //按迭代器删除 auto it = m.find("c"); if (it != m.end()) { it = m.erase(it); // 删除c,返回下一个迭代器(指向d) cout << "删除后下一个key:" << it->first << endl; // d } //范围删除(删除a和d) auto start = m.find("a"); auto end = m.end(); m.erase(start, end); // 删除[a, end)范围内的元素 //此时m为空 return 0; } ### 3.6 遍历元素 int main() { map dict; dict.insert({ "auto","自动的" }); dict.insert({ "first", "第一个" }); dict.insert({ "second", "第二个" }); dict.insert({ "sort", "排序" }); map::iterator it = dict.begin(); while (it != dict.end()) { cout << it->first << ":" << it->second << endl; ++it; } cout << endl; return 0; } ## 4. multimap > multimap与map类似,用于存储键值对,并按key自动排序,**核心区别是:允许键值冗余。** > 无\[ \]运算符:由于key不唯一,无法通过\[ \]确定访问哪个value,因此multimap没有\[ \] 运算符。 > > 高效操作:底层基于红黑树实现,插入、删除、查找的时间复杂度均为 **O(log n)**(n为元素总数)。 ### 4.1 插入元素(insert) > multimap的insert**总是成功**(因为允许重复key),返回值为指向新插入元素的迭代器。 int main() { multimap mm; // 插入相同key的多个键值对 mm.insert({ "数学", "高数1" }); mm.insert({ "数学", "高数2" }); mm.insert(make_pair("数学", "线性代数")); mm.insert(pair("数学", "概率论")); mm.insert({ "计算机", "C++编程" }); mm.insert({ "计算机", "数据结构" }); // 遍历(按key升序,相同key相邻) for (auto& p : mm) { cout << p.first << ":" << p.second << endl; } return 0; } ### 4.2 删除元素(erase) > erase(const Key\& key):删除所有key对应的元素,返回删除的元素个数; int main() { multimap m = { {"a", 1}, {"a", 2}, {"a", 3}, {"b", 4},{"c", 5} }; //按key删除 int del_count = m.erase("a"); cout << del_count << endl; //3 return 0; } ### 4.3 查找元素(find/equal_range) > find(key):返回第一个key匹配的元素的迭代器(中序第一个),若不存在,返回end()。 > > equal_range(key):返回pair\,表示所有key匹配的元素范围(\[first, second))。 int main() { multimap m = { {"a", 1}, {"a", 2}, {"a", 3}, {"b", 4},{"c", 5} }; auto it = m.find("a");//查找第一个a if (it != m.end()) { cout << it->second << endl; //1 } cout << endl; auto range = m.equal_range("a"); for (auto i = range.first; i != range.second; ++i) { cout << i->second << endl; //1 2 3 } return 0; } ## 5. 练习 [【两个数组的交集】](https://leetcode.cn/problems/intersection-of-two-arrays/description/ "【两个数组的交集】") [【环形链表II】](https://leetcode.cn/problems/linked-list-cycle-ii/description/ "【环形链表II】") [【随机链表的复制】](https://leetcode.cn/problems/copy-list-with-random-pointer/description/ "【随机链表的复制】") [【前k个高频单词】](https://leetcode.cn/problems/top-k-frequent-words/submissions/677692276/ "【前k个高频单词】")

相关推荐
纵有疾風起2 小时前
C++—vector:vecor使用及模拟实现
开发语言·c++·经验分享·开源·stl·vector
爱凤的小光6 小时前
图漾GM461-E1相机专栏
c++
qwepoilkjasd6 小时前
C++智能指针介绍
c++
·白小白7 小时前
力扣(LeetCode) ——43.字符串相乘(C++)
c++·leetcode
咬_咬7 小时前
C++仿muduo库高并发服务器项目:Poller模块
服务器·开发语言·c++·epoll·muduo
FMRbpm7 小时前
链表5--------删除
数据结构·c++·算法·链表·新手入门
Kimser8 小时前
QT C++ QWebEngine与Web JS之间通信
javascript·c++·qt
QT 小鲜肉8 小时前
【QT/C++】Qt样式设置之CSS知识(系统性概括)
linux·开发语言·css·c++·笔记·qt
Elias不吃糖8 小时前
NebulaChat 框架学习笔记:深入理解 Reactor 与多线程同步机制
linux·c++·笔记·多线程