目录
[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