目录
[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,mapkey的行为分为两种情况:
- 当key已存在时,返回key对应value的引用,可以直接读取或修改。
- 当key不存在时,会自动插入一个新的键值对 ,其中key为传入的参数,value为其类型的默认构造值(如int为0,string为空串,自定义类需有默认构造函数),返回这个新value的引用。
注意: 的副作用,访问不存在的key时会自动插入,若仅需"查询"而不希望插入,应使用find或at。
int main()
{
map<string, int> 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<string, int> 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<string, int> 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<int, string> 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<string, int> 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<string, string> dict;
dict.insert({ "auto","自动的" });
dict.insert({ "first", "第一个" });
dict.insert({ "second", "第二个" });
dict.insert({ "sort", "排序" });
map<string, string>::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<string, string> mm;
// 插入相同key的多个键值对
mm.insert({ "数学", "高数1" });
mm.insert({ "数学", "高数2" });
mm.insert(make_pair("数学", "线性代数"));
mm.insert(pair<string, string>("数学", "概率论"));
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<string, int> 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<iterator, iterator>,表示所有key匹配的元素范围([first, second))。
int main()
{
multimap<string, int> 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;
}