【C++】set、map 容器的使用
文章目录
- 1. set 和 multiset 的使用
- 1.1 set类的介绍
- 1.2 set的构造和迭代器
- 1.3 set 的增删查
- 1.4 insert和迭代器调用示例
- 1.5 find和erase使用示例
- 1.6 multiset和set的差异
- 2. map 和 multimap 的使用
- 2.1 map 类的介绍
- 2.2 pair 类型介绍
- 2.3 map 的构造和迭代器
- 2.4 map 的增删查
- 2.5 map 的数据修改
- 2.6 构造遍历及增删查使用示例
- 2.7 map 的迭代器和 [] 功能示例
- 2.8 multimap和map的差异
1. set 和 multiset 的使用
1.1 set类的介绍
- set的声明如下,T就是set底层关键字的类型
- set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数
- set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。
- 一般情况下,我们都不需要传后两个模版参数。
- set底层是用红黑树实现,增删查效率是O(logN),迭代器遍历是走的搜索树的中序,所以是有序的。
- 前面部分我们已经学习了vector/list等容器的使用,STL容器接口设计,高度相似,所以这里我们就不再一个接口一个接口的介绍。
template < class T, // set::key_type/value_typeclass Compare = less<T>, // set::key_compare/value_compareclass Alloc = allocator<T> // set::allocator_type> class set;
1.2 set的构造和迭代器
set的构造我们关注以下几个接口即可。
set的支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,set的iterator和const_iterator都不支持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。
// empty (1) 无参默认构造
explicit set(const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
set(InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type & = allocator_type());
// copy (3) 拷贝构造
set(const set& x);
// initializer list (5) initializer 列表构造
set(initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
// 迭代器是⼀个双向迭代器
iterator->a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
1.3 set 的增删查
常用的几个接口:
// 单个数据插入,如果已经存在则插入失败
pair<iterator, bool> insert(const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert(initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert(InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find(const value_type& val);
// 查找val,返回Val的个数
size_type count(const value_type& val) const;
// 删除一个迭代器位置的值
iterator erase(const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase(const value_type& val);
// 删除一段迭代器区间的值
iterator erase(const_iterator first, const_iterator last);
// 返回大于等val位置的迭代器
iterator lower_bound(const value_type& val) const;
// 返回大于val位置的迭代器
iterator upper_bound(const value_type& val) const;
1.4 insert和迭代器调用示例
void test1()
{// 去重+升序排序set<int> s;// 去重+降序排序(给一个大于的仿函数)//set<int, greater<int>> s;s.insert(5);s.insert(2);s.insert(7);s.insert(5);//set<int>::iterator it = s.begin();auto it = s.begin();while (it != s.end()){// error C3892: “it”: 不能给常量赋值// *it = 1;cout << *it << " ";++it;} cout << endl;// 插入一段initializer_list列表值,已经存在的值插入失败s.insert({ 2,8,3,9 });for (auto e : s){cout << e << " ";} cout << endl;set<string> strset = { "sort", "insert", "add" };// 遍历string比较ascll码大小顺序遍历的for (auto& e : strset){cout << e << " ";} cout << endl;
}
1.5 find和erase使用示例
void test2()
{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;// 直接删除xint x;cin >> x;int num = s.erase(x);if (num == 0){cout << x << "不存在!" << endl;} for (auto e : s){cout << e << " ";} cout << endl;// 直接查找在利用迭代器删除xcin >> 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;// 算法库的查找 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;}
}
1.6 multiset和set的差异
multiset和set的使用基本完全类似,主要区别点在于 multiset 的 键 允许重复,可存储多个相同值的元素,而 set 的 键 唯一,不允许重复元素。
,具体参看下面的样例代码理解。
#include <iostream>
#include <set>int main()
{std::set<int> s = {1, 2, 2, 3}; // 实际存储 {1, 2, 3}std::multiset<int> ms = {1, 2, 2, 3}; // 存储 {1, 2, 2, 3}std::cout << "set size: " << s.size() << "\n"; // 输出 3std::cout << "multiset size: " << ms.size() << "\n";// 输出 4
}
同样的,在删除时,multiset 也会删除所有匹配 key 的元素,而 set 只会删除一个匹配元素。
std::multiset<int> ms = {2, 2, 3};
ms.erase(2); // 删除所有2 → 剩余 {3}std::set<int> s = {2, 3};
s.erase(2); // 删除2 → 剩余 {3}
在查找时 multiset 返回键的出现次数(可能 >1),而 set 返回 0 或 1。
std::multiset<int> ms = {1, 2, 2, 3};
std::cout << ms.count(2); // 输出 2std::set<int> s = {1, 2, 3};
std::cout << s.count(2); // 输出 1
迭代器遍历时,multiset 的相同值是连续出现的
std::multiset<int> ms = {3, 1, 2, 2};
for (int num : ms)
{std::cout << num << " "; // 输出: 1 2 2 3
}std::set<int> s = {3, 1, 2, 2};
for (int num : s)
{std::cout << num << " "; // 输出: 1 2 3
}
multiset 的特殊方法
multiset 提供额外方法处理重复键:
- equal_range(key):返回一个迭代器对,表示所有匹配 key 的元素范围。
- lower_bound(key)/upper_bound(key):配合范围操作。
std::multiset<int> ms = {1, 2, 2, 3};
auto [first, last] = ms.equal_range(2);
for (auto it = first; it != last; ++it)
{std::cout << *it << " "; // 输出: 2 2
}
总结
特性 | set | multiset |
---|---|---|
键唯一性 | 唯一 | 允许重复 |
插入行为 | 忽略重复 | 接受重复 |
删除行为 | 删除单个匹配元素 | 删除所有匹配元素 |
查找结果 | 存在性检查(0或1) | 统计出现次数(>=0) |
典型应用 | 唯一键管理 | 允许重复的有序数据场景 |
2. map 和 multimap 的使用
2.1 map 类的介绍
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。map底层是用红黑树实现,增删查改效率是O(logN),迭代器遍历是走的中序,所以是按key有序顺序遍历的。
template < class Key, // map::key_typeclass T, // map::mapped_typeclass Compare = less<Key>, // map::key_compareclass Alloc = allocator<pair<const Key, T> > // map::allocator_type> class map;
2.2 pair 类型介绍
map底层的红黑树节点中的数据,使⽤pair<Key, T>存储键值对数据。
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){}
};
template <class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y)
{return (pair<T1, T2>(x, y));
}
2.3 map 的构造和迭代器
// empty (1) 无参默认构造
explicit map(const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
map(InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type & = allocator_type());
// copy (3) 拷⻉构造
map(const map& x);
// initializer list (5) initializer 列表构造
map(initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
// 迭代器是一个双向迭代器
iterator->a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();
2.4 map 的增删查
常用的几个接口:
// 单个数据插入,如果已经key存在则插入失败,key存在相等value不相等也会插入失败
pair<iterator, bool> insert(const value_type& val);
// 列表插入,已经在容器中存在的值不会插入
void insert(initializer_list<value_type> il);
// 迭代器区间插入,已经在容器中存在的值不会插入
template <class InputIterator>
void insert(InputIterator first, InputIterator last);
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find(const key_type& k);
// 查找k,返回k的个数
size_type count(const key_type& k) const;
// 删除一个迭代器位置的值
iterator erase(const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase(const key_type& k);
// 删除一段迭代器区间的值
iterator erase(const_iterator first, const_iterator last);
// 返回大于等k位置的迭代器
iterator lower_bound(const key_type& k);
// 返回大于k位置的迭代器
const_iterator lower_bound(const key_type& k) const;
2.5 map 的数据修改
前面的 set 不支持修改,是因为它要修改只能修改 key,而它的 key 一旦修改,就可能破坏底层数据结构红黑树。map 可以修改,值的是能够修改它的 value,它的 key 同样也不能修改,若修改会破坏底层数据结构。
map第一个支持修改的方式是通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map 还有一个非常重要的修改接口operator[],但是operator[]不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口
需要注意从内部实现角度,map这里把我们传统说的value值,给的是T类型,typedef为 mapped_type。而 value_type是红黑树结点中存储的pair键值对值。日常使用我们还是习惯将这里的T映射值叫做value。
Member types
key_type->The first template parameter(Key)
mapped_type->The second template parameter(T)
value_type->pair<const key_type, mapped_type>
// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的mapped_type值
iterator find(const key_type& k);// insert插入⼀个pair<key, T>对象
// 1、如果key已经在map中,插入失败,则返回⼀个pair<iterator,bool>对象,返回pair对象
// first是key所在结点的迭代器,second是false
// 2、如果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>
pair<iterator, bool> insert(const value_type& val);
mapped_type& operator[] (const key_type& k);
// operator的内部实现
mapped_type& operator[] (const key_type& k)
{// 1、如果k不在map中,insert会插入k和mapped_type默认值,同时[]返回结点中存储// mapped_type值的引用,那么我们可以通过引用修改返映射值。所以[]具备了插入 + 修改功能// 2、如果k在map中,insert会插入失败,但是insert返回pair对象的first是指向key结点的// 迭代器,返回值同时[]返回结点中存储mapped_type值的引用,所以[]具备了查找 + 修改的功能pair<iterator, bool> ret = insert({ k, mapped_type() });iterator it = ret.first;return it->second;
}
2.6 构造遍历及增删查使用示例
void test3()
{// initializer_list构造及迭代遍历map<string, string> dict = { {"left", "左边"}, {"right", "右边"},{"insert", "插入"},{ "string", "字符串" } };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){//cout << (*it).first <<":"<<(*it).second << endl;// map的迭代基本都使用operator->,这里省略了一个->// 第一个->是迭代器运算符重载,返回pair*,第二个箭头是结构指针解引用取pair数据//cout << it.operator->()->first << ":" << it.operator->()-> second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;// insert插入pair对象的4种方式,对⽐之下,最后⼀种最方便pair<string, string> kv1("first", "第一个");dict.insert(kv1);dict.insert(pair<string, string>("second", "第二个"));dict.insert(make_pair("sort", "排序"));dict.insert({ "auto", "自动的" });// "left"已经存在,插入失败dict.insert({ "left", "左边,剩余" });// 范围for遍历for (const auto& e : dict){cout << e.first << ":" << e.second << endl;}cout << endl;string str;while (cin >> str){auto ret = dict.find(str);if (ret != dict.end()){cout << "->" << ret->second << endl;}else{cout << "无此单词,请重新输入" << endl;}}
}
2.7 map 的迭代器和 [] 功能示例
void test4()
{// 利用find和iterator修改功能,统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (const auto& str : arr){// 先查找水果在不在map中// 1、不在,说明水果第一次出现,则插入{水果, 1}// 2、在,则查找到的节点中水果对应的次数++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;
}
void test5()
{// 利用[]插入+修改功能,巧妙实现统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (const auto& str : arr){// []先查找水果在不在map中// 1、不在,说明⽔果第⼀次出现,则插入{⽔果, 0},同时返回次数的引用,++一下就变成1次了// 2、在,则返回水果对应的次数++countMap[str]++;} for (const auto & e : countMap){cout << e.first << ":" << e.second << endl;} cout << endl;
}
void test6()
{map<string, string> dict;dict.insert(make_pair("sort", "排序"));// key不存在->插入 {"insert", string()}dict["insert"];// 插入+修改dict["left"] = "左边";// 修改dict["left"] = "左边、剩余";// key存在->查找cout << dict["left"] << endl;
}
2.8 multimap和map的差异
multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multiset完全一样,比如find时,有多个key,返回中序第一个。其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。