当前位置: 首页 > news >正文

C++数据结构 : map和set的使用

C++数据结构 : map和set的使用

目录

  • C++数据结构 : map和set的使用
    • 引言
    • 1. 序列式容器和关联式容器
    • 2. set系列的使用
      • 2.1 set类的介绍
      • 2.2 set的构造和迭代器
      • 2.3 set的增删查
      • 2.4 insert和迭代器遍历的使用样例
      • 2.5 find和erase使用样例
      • 2.6 multiset和set的差异
    • 3. map系列的使用
      • 3.1 map类的介绍
      • 3.2 pair类型的介绍
      • 3.3 map的构造
      • 3.4 map的增删查
      • 3.5 map的数据修改
      • 3.6 构造遍历及增删查使用样例
      • 3.7 map的迭代器和`[]`功能样例
      • 3.8 multimap和map的差异

引言

关联式容器中,mapset是最常用的两种,它们基于红黑树(一种平衡二叉搜索树)实现,提供了高效的增删查改操作,时间复杂度为O(log n)。set是纯关键字(key)的集合,适合需要快速判断元素是否存在的场景;map则是键值对(key-value)的集合,适合需要通过关键字快速查找对应值的场景。此外,multisetmultimap是它们的变体,支持重复关键字的存储。

本文将详细介绍mapset的基本用法,包括构造、遍历、增删查改等操作,并通过丰富的代码示例帮助读者掌握这些容器的核心功能。无论是初学者还是有一定经验的开发者,都能从中获得实用的知识和技巧。


1. 序列式容器和关联式容器

  • 前面我们已经接触过STL中的部分容器,如:stringvectorlistdequearrayforward_list等,这些容器统称为序列式容器,因为它们的逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,它依旧是序列式容器。顺序容器中的元素是按它们在容器中的存储位置来顺序保存和访问的。
  • 关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器的逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,它的存储结构就被破坏了。关联式容器中的元素是按关键字来保存和访问的。常见的关联式容器包括map/set系列和unordered_map/unordered_set系列。
  • 本章节讲解的mapset底层是红黑树,红黑树是一颗平衡二叉搜索树。set是纯key搜索场景的结构,而mapkey/value搜索场景的结构。

2. set系列的使用

  • set和multiset参考文档

2.1 set类的介绍

template < class T, // set::key_type/value_typeclass Compare = less<T>, // set::key_compare/value_compareclass Alloc = allocator<T> // set::allocator_type> class set;
  • set的声明如上:T是set底层关键字的类型。
  • set默认要求T支持小于比较,如果不支持或需要自定义比较规则,可以自行实现仿函数并传给第二个模板参数。
  • set底层存储数据的内存是从空间配置器申请的,若需要可自行实现内存池并传给第三个参数。
  • 一般情况下,我们不需要传递后两个模板参数。
  • set底层采用红黑树实现,增删查效率为O(log n),迭代器遍历按搜索树的中序进行,因此是有序的。

2.2 set的构造和迭代器

set的特性set支持正向和反向迭代遍历,遍历默认按升序顺序(因为底层是二叉搜索树,迭代器遍历走的是中序遍历);支持迭代器就意味着支持范围for循环。需要注意的是,setiteratorconst_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();

2.3 set的增删查

Member types
key_type -> The first template parameter (T)
value_type -> The first template parameter (T)
// 单个数据插⼊,如果已经存在则插⼊失败
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;

2.4 insert和迭代器遍历的使用样例

#include <iostream>
#include <set>  // 包含set容器的头文件
using namespace std;  // 使用标准命名空间int main()
{// ====================== 第一部分:int类型set的基本操作 ======================// 创建一个空的set容器,默认情况下:// 1. 会对元素自动去重(不允许重复值)// 2. 会按照升序排序(从小到大)set<int> s;// 如果要实现降序排序(从大到小),可以这样定义:// set<int, greater<int>> s;// 向set中插入元素s.insert(5);  // 插入5s.insert(2);  // 插入2s.insert(7);  // 插入7s.insert(5);  // 再次插入5(这个操作无效,因为set不允许重复元素)// 遍历set的方式1:使用迭代器// set<int>::iterator it = s.begin();  // 传统写法,获取起始迭代器auto it = s.begin();  // C++11推荐写法,自动类型推导while (it != s.end())  // 遍历到结束迭代器为止{// *it = 1;  // 错误!set的元素是const的,不能修改(会编译错误)cout << *it << " ";  // 输出当前元素++it;  // 移动到下一个元素}cout << endl;  // 换行// ====================== 第二部分:批量插入元素 ======================// 使用initializer_list批量插入多个元素// 注意:已经存在的值(如这里的2)会被自动忽略s.insert({ 2, 8, 3, 9 });  // 实际会插入8,3,9(2已存在)// 遍历set的方式2:使用范围for循环(C++11特性)for (auto e : s)  // 自动遍历所有元素{cout << e << " ";  // 输出元素}cout << endl;  // 换行// ====================== 第三部分:string类型set的演示 ======================// 创建一个string类型的set,并直接初始化// 会按照字典序(ASCII码顺序)自动排序set<string> strset = { "sort", "insert", "add" };// 遍历string类型的set// 使用auto&避免拷贝(虽然string不大,但这是好习惯)for (auto& e : strset){cout << e << " ";  // 输出字符串}cout << endl;  // 换行return 0;  // 程序正常结束
}

2.5 find和erase使用样例

#include <iostream>
#include <set>  // 包含set容器的头文件
using namespace std;int main() {// 1. 初始化一个set容器,并插入初始值// set的特性:自动排序 + 去重// 初始值 {4,2,7,2,8,5,9} 插入后会变成 {2,4,5,7,8,9}set<int> s = {4, 2, 7, 2, 8, 5, 9};// 2. 遍历并打印set中的所有元素cout << "初始set中的元素: ";for (auto e : s) {cout << e << " ";  // 输出:2 4 5 7 8 9 }cout << endl;// 3. 删除最小值(set是有序的,begin()指向最小元素)s.erase(s.begin());  // 删除第一个元素2cout << "删除最小值后的set: ";for (auto e : s) {cout << e << " ";  // 输出:4 5 7 8 9}cout << endl;// 4. 直接通过值删除元素int x;cout << "请输入要删除的值: ";cin >> x;int num = s.erase(x);  // erase返回删除的元素个数(0或1)if (num == 0) {cout << x << "不存在!" << endl;}cout << "删除操作后的set: ";for (auto e : s) {cout << e << " ";}cout << endl;// 5. 先查找再删除(更安全的删除方式)cout << "请输入要查找并删除的值: ";cin >> x;auto pos = s.find(x);  // 使用set自带的find方法查找(O(logN)效率)if (pos != s.end()) {  // 如果找到s.erase(pos);      // 通过迭代器删除} else {cout << x << "不存在!" << endl;}cout << "查找删除后的set: ";for (auto e : s) {cout << e << " ";}cout << endl;// 6. 两种查找方式的比较// 方法1:算法库的find(线性查找O(N),不推荐用于set)auto pos1 = find(s.begin(), s.end(), x);// 方法2:set自带的find(二分查找O(logN),推荐使用)auto pos2 = s.find(x);// 7. 使用count方法检查元素是否存在// count返回0或1(因为set元素唯一)cout << "请输入要查找的值: ";cin >> x;if (s.count(x)) {  // 等价于 if(s.count(x) > 0)cout << x << "在set中!" << endl;} else {cout << x << "不存在!" << endl;}return 0;
}
// 引入必要的头文件
#include <iostream>   // 用于输入输出操作
#include <set>       // 包含set容器的定义// 使用标准命名空间,避免每次都要写std::
using namespace std;int main()
{// 创建一个空的set容器,用于存储int类型的数据// set的特性:自动排序、不允许重复元素std::set<int> myset;// 向set中插入元素(10,20,...,90)// 循环从1到9,每次插入i*10for (int i = 1; i < 10; i++) {myset.insert(i * 10);  // 插入10的倍数:10,20,30,40,50,60,70,80,90}// 打印当前set中的所有元素(自动排序)cout << "初始set中的元素: ";for (auto e : myset) {cout << e << " ";  // 输出:10 20 30 40 50 60 70 80 90}cout << endl;/* 实现查找到的[itlow,itup)区间包含[30,60] */// lower_bound返回第一个 >= 30的元素的迭代器// 这里会指向30这个元素auto itlow = myset.lower_bound(30);// upper_bound返回第一个 > 60的元素的迭代器// 这里会指向70这个元素auto itup = myset.upper_bound(60);// 删除[itlow, itup)区间的元素(左闭右开区间)// 这将删除30,40,50,60四个元素myset.erase(itlow, itup);// 打印删除后的set元素cout << "删除[30,60]后的元素: ";for (auto e : myset) {cout << e << " ";  // 输出:10 20 70 80 90}cout << endl;// 程序正常结束return 0;
}

2.6 multiset和set的差异

multisetset的使用基本完全类似,主要区别点在于multiset支持值冗余,因此insertfindcounterase等操作都围绕支持值冗余有所差异,具体可参考下面的样例代码理解。

#include <iostream>
#include <set>          // 包含multiset容器的头文件
using namespace std;int main() {// 初始化一个multiset容器,允许重复元素,元素会自动排序但不去重// 初始元素:4,2,7,2,4,8,4,5,4,9multiset<int> s = { 4, 2, 7, 2, 4, 8, 4, 5, 4, 9 };// 使用迭代器遍历并打印multiset中的所有元素auto it = s.begin();    // 获取指向第一个元素的迭代器while (it != s.end()) { // 遍历直到结束cout << *it << " "; // 解引用迭代器获取当前元素值++it;              // 移动到下一个元素}cout << endl;           // 换行// 查找用户输入的值x在multiset中的出现情况int x;cin >> x;               // 从用户输入读取要查找的值x// 查找x的第一个出现位置(multiset是有序的,返回中序遍历的第一个x)auto pos = s.find(x);   // 从找到的位置开始,连续输出所有等于x的元素while (pos != s.end() && *pos == x) {cout << *pos << " "; // 输出当前x++pos;              // 移动到下一个元素}cout << endl;           // 换行// 统计x在multiset中出现的总次数cout << "Count of " << x << ": " << s.count(x) << endl;// 删除multiset中所有的x(与set不同,set只删除一个)s.erase(x);             // 打印删除x后的multiset内容cout << "After erasing " << x << ": ";for (auto e : s) {      // 范围for循环遍历cout << e << " ";   // 输出每个元素}cout << endl;           // 换行return 0;               // 程序正常结束
}

3. map系列的使用

  • map和multimap参考文档

3.1 map类的介绍

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;

map的声明如上Key是map底层关键字的类型,T是map底层value的类型。set默认要求Key支持小于比较,如果不支持或需要自定义比较规则,可以自行实现仿函数传给第二个模板参数。map底层存储数据的内存是从空间配置器申请的,一般情况下不需要传递后两个模板参数。map底层使用红黑树实现,增删查改的时间复杂度为O(logN),迭代器遍历采用中序遍历,因此是按key有序顺序遍历的。


3.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) );
}

3.3 map的构造

map的特性map支持正向和反向迭代遍历,遍历默认按key的升序顺序(底层是二叉搜索树,迭代器走的是中序遍历)。支持迭代器意味着支持范围for循环。map允许修改value数据,但禁止修改key数据,因为修改关键字会破坏底层搜索树的结构。

// 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();

3.4 map的增删查

map的增接口:插入的pair键值对数据与set不同,但查找删除接口仅需关键字key,与set完全类似。不同之处在于,find返回的是iterator,不仅可以确认key是否存在,还能找到key映射的value,且通过迭代器可进一步修改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>
// 单个数据插⼊,如果已经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;

3.5 map的数据修改

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返回值的说明
// The single element versions (1) return a pair, with its member pair::firstset to an iterator pointing to either the newly inserted element or to theelement with an equivalent key in the map. The pair::second element in the pairis set to true if a new element was inserted or false if an equivalent keyalready existed.
// 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;
}

3.6 构造遍历及增删查使用样例

#include <iostream>
#include <map>      // 包含map头文件,用于使用map容器
#include <string>   // 包含string头文件,用于使用string类using namespace std; // 使用标准命名空间int main()
{// ================ 第一部分:map的初始化和遍历 ================// 使用initializer_list初始化map容器// key类型为string,value类型为string// 这里存储了四个中英文对照的键值对map<string, string> dict = { {"left", "左边"}, {"right", "右边"},{"insert", "插入"}, {"string", "字符串"} };// 获取map的迭代器,auto自动推导类型为map<string, string>::iteratorauto it = dict.begin();  // 指向第一个元素// 使用while循环遍历mapwhile (it != dict.end()){// 三种访问元素的方式(注释中展示了前两种,实际使用第三种):// 1. 解引用迭代器后使用.运算符访问成员//    cout << (*it).first << ":" << (*it).second << endl;// 2. 显式调用operator->运算符//    cout << it.operator->()->first << ":" << it.operator->()->second << endl;// 3. 直接使用->运算符(最简洁常用)cout << it->first << ":" << it->second << endl;++it;  // 迭代器移动到下一个元素}cout << endl;  // 输出空行分隔不同部分// ================ 第二部分:map的插入操作 ================// 方法1:先构造pair对象再插入pair<string, string> kv1("first", "第一个");dict.insert(kv1);// 方法2:插入匿名pair对象dict.insert(pair<string, string>("second", "第二个"));// 方法3:使用make_pair函数模板自动推导类型dict.insert(make_pair("sort", "排序"));// 方法4:使用花括号初始化(C++11起最简洁的方式)dict.insert({ "auto", "自动的" });// 尝试插入已存在的key("left"已存在),插入失败,不影响原有值dict.insert({ "left", "左边,剩余" });  // 这个插入不会生效// 使用范围for循环遍历map(C++11特性)for (const auto& e : dict){// e是pair<const string, string>的引用cout << e.first << ":" << e.second << endl;}cout << endl;  // 输出空行分隔不同部分// ================ 第三部分:map的查找操作 ================string str;  // 存储用户输入的字符串while (cin >> str)  // 循环读取用户输入{// 在map中查找keyauto ret = dict.find(str);if (ret != dict.end())  // 如果找到{cout << "->" << ret->second << endl;  // 输出对应的value}else  // 如果没找到{cout << "无此单词,请重新输入" << endl;}}// 注意:erase等接口与set类似,这里没有演示return 0;  // 程序正常结束
}

3.7 map的迭代器和[]功能样例

#include <iostream>   // 包含输入输出流库
#include <map>       // 包含map容器库
#include <string>    // 包含字符串库
using namespace std;  // 使用标准命名空间// 方法1:使用find和iterator实现水果计数
int main()
{// 定义一个包含多种水果的数组string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };// 创建一个map用于存储水果及其出现次数// key是string类型表示水果名称,value是int类型表示出现次数map<string, int> countMap;// 遍历数组中的每个水果for (const auto& str : arr){// 使用find函数查找当前水果是否已在map中auto ret = countMap.find(str);// 判断查找结果if (ret == countMap.end())  // 如果没找到该水果{// 插入新的键值对,水果第一次出现,次数设为1countMap.insert({ str, 1 });}else  // 如果找到了该水果{// 通过迭代器访问并增加该水果的计数ret->second++;}}// 遍历并打印map中的所有水果及其出现次数for (const auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;return 0;  // 程序正常结束
}// 方法2:使用[]运算符简化实现
int main()
{// 同样的水果数组string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };// 创建map存储水果计数map<string, int> countMap;// 遍历数组中的每个水果for (const auto& str : arr){// 使用[]运算符实现更简洁的计数逻辑:// 1. 如果水果不存在,会自动插入{str, 0},然后++// 2. 如果水果存在,直接返回引用并++countMap[str]++;}// 遍历并打印结果for (const auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;return 0;  // 程序正常结束
}
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{map<string, string> dict;dict.insert(make_pair("sort", "排序"));// key不存在->插⼊ {"insert", string()}dict["insert"];// 插⼊+修改dict["left"] = "左边";// 修改dict["left"] = "左边、剩余";// key存在->查找cout << dict["left"] << endl;return 0;
}

3.8 multimap和map的差异

multimapmap的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,因此insert/find/count/erase等操作都围绕支持key冗余有所差异,这一点与setmultiset完全一致。例如,find时如果存在多个相同key,会返回中序遍历的第一个匹配项。此外,multimap不支持operator[],因为允许key冗余会导致operator[]无法明确是修改还是插入操作。

相关文章:

  • docker环境搭建与常用指令
  • docker 搭建php 开发环境 添加扩展redis、swoole、xdebug(1)
  • 如何用Spring Cache实现对Redis的抽象
  • Oracle 正则表达式匹配(Oracle 11g)
  • 威联通QNAP替换docker源
  • 高频面试--redis
  • Python打卡 DAY 38
  • Docker 挂载卷并保存为容器
  • LeetCode 2894.分类求和并作差:数学O(1)一行解决
  • 大语言模型 21 - MCP 自动操作 Figma+Cursor 实现自动原型开发!
  • 利用 MkDocs 和 GitHub 部署个人博客网页
  • 基于 SpringBoot 与 VueJS 的智慧就业服务平台构建:技术融合与实践创新
  • AI赋能引爆短剧全球化风潮,腾讯云媒体处理助力短剧平台出海吸金
  • proteus8.4 安装包下载地址与安装教程
  • Web通信协议全景解析:从HTTP到WebService的技术演进与对比
  • NGINX HTTP/2 全面指南开启、调优与实战
  • Windows版本的postgres安装插件http
  • 恶意npm与VS Code包窃取数据及加密货币资产
  • FastMoss 国际电商Tiktok数据分析 JS 逆向 | MD5加密
  • Pytorch
  • 重庆有网站公司/网站优化推广方法
  • 平度疫情最新数据消息/东莞seo建站哪家好
  • 沈阳网站制作费用/营销最好的方法
  • 外贸出口网站建设/今天的最新消息新闻
  • dede 网站地图样式/如何在手机上开自己的网站
  • wordpress企业官网主题/seo查询百科