C++ map和set的使用

一、序列式容器 vs 关联式容器

  • 序列式容器 :vector、list、string、deque 等线性结构,元素按存储位置顺序存放,彼此之间没有强关联。

  • 关联式容器 :map /set/unordered_map /unordered_set非线性结构,元素按key(关键字) 存储,key 和 value 之间有强关联。底层是红黑树 (平衡二叉搜索树),因此有序、去重、查找效率 O (logN)

简单对应:

  • setkey 模型(只存 key,用来判断 "在不在")
  • mapkey/value 模型(存键值对,用来 "查找映射关系")

二、set 系列的使用

2.1 set 介绍

  • set 底层是红黑树
  • key 有序(默认升序)
  • key 不重复(自动去重)
  • 迭代器遍历 = 中序遍历
  • 迭代器不支持修改 key(会破坏树结构)
  • 增删查效率:O(logN)
cpp 复制代码
template<class T,class Compare=less<T>,class Alloc=allocator>
class set;

2.2 set 构造函数

cpp 复制代码
//1.空构造
set<int> s1;

//2.迭代区间构造
vector<int> v={1,2,3};
set<int> s2=(v.begin(),v.end());

//3.拷贝构造
set<int> s3=s2;

//4.初始化列表构造
set<int> s4={4,2,7,2,8};

2.3 set 迭代器

  • 双向迭代器
  • 遍历结果升序
  • *it 只读,不能修改
cpp 复制代码
//遍历
for(auto it=s.begin();it!=s.end();++it)
cout<<*it<<" ";

//范围for
for(auto e:s)
cour<<e<<" ";

2.4 set 增删查(核心接口)

1)insert 插入
  • 插入已存在的 key → 插入失败
  • 返回值:pair<iterator, bool>
    • first:指向该 key 的迭代器
    • second:true 插入成功 /false 已存在
cpp 复制代码
s.insert(5);
s.insert(2);
s.insert(7);
s.insert(5); // 重复,插入失败
2)find 查找
  • s.find(key)找到返回迭代器,没找到返回 s.end()
  • 时间复杂度 O(logN) (比算法库 find 快得多)
cpp 复制代码
auto pos=s.find(7);
if(pos!=s.end())
cout<<"找到了"<<endl;
3)count 统计
  • 因为去重,所以结果只有 0 或 1
  • 常用来快速判断存在与否
cpp 复制代码
if (s.count(x))
    cout << x << " 在集合中" << endl;
4)erase 删除

三种用法:

cpp 复制代码
// 1. 按迭代器删
s.erase(s.begin());

// 2. 按值删(返回删除个数:0 或 1)
s.erase(7);

// 3. 删区间
s.erase(itlow, itup);
5)lower_bound / upper_bound
  • lower_bound(val):返回 ≥ val 的第一个迭代器:左闭【
  • upper_bound(val):返回 > val 的第一个迭代器:右开**)**

可用来删除一段区间

cpp 复制代码
set<int> s = {4, 2, 7, 2, 8, 5, 9};
 
// 通过迭代器删除
s.erase(s.begin());  // 删除最小元素
 
// 通过值删除
int num = s.erase(5);  // 返回删除的元素个数
 
// 删除区间
auto it_low = s.lower_bound(3);  // >=3的第一个元素
auto it_up = s.upper_bound(7);   // >7的第一个元素  
s.erase(it_low, it_up);          // 删除[3, 7]区间

2.5 set 完整示例代码

cpp 复制代码
#include <iostream>
#include <set>
using namespace std;

int main() {
    set<int> s;
    s.insert(5);
    s.insert(2);
    s.insert(7);
    s.insert(5);

    // 遍历:2 5 7
    for (auto e : s)
        cout << e << " ";
    cout << endl;

    s.insert({2,8,3,9});
    // 2 3 5 7 8 9
    for (auto e : s)
        cout << e << " ";
    cout << endl;

    set<string> strset = {"sort", "insert", "add"};
    for (auto& e : strset)
        cout << e << " ";
    cout << endl;

    return 0;
}

2.6 multiset 与 set 的区别

multiset

  • 允许 key 重复(不去重)
  • 只排序,不去重
  • find 返回中序遍历第一个匹配值
  • count 返回真实个数
  • erase(key)删除所有等于 key 的节点
cpp 复制代码
multiset<int> s = {4,2,7,2,4,8,4};
// 遍历:2 2 4 4 4 7 8

// find 返回第一个 4
auto pos = s.find(4);

// count 返回 3
cout << s.count(4) << endl;

// erase(4) 会删掉所有 4
s.erase(4);

三、map 系列的使用

3.1 map 介绍

  • 存储 key-value 键值对
  • key 有序、唯一、不可修改
  • value 可以修改
  • 底层红黑树,O(logN)
  • 迭代器遍历 = 中序遍历(按 key 升序)
cpp 复制代码
template <class Key, class T, class Compare = less<Key>>
class map;

3.2 pair类型

map底层存储的是pair<const Key, Value>类型:

cpp 复制代码
template <class T1, class T2>
struct pair {
    typedef T1 first_type;
    typedef T2 second_type;
    
    T1 first;   //之前的key
    T2 second;  //之前的value
    
    pair(): first(T1()), second(T2()) {}
    pair(const T1& a, const T2& b): first(a), second(b) {}
};
 
// 便捷函数
template <class T1, class T2>
inline pair<T1,T2> make_pair(T1 x, T2 y) {
    return pair<T1,T2>(x, y);
}

3.3 map 构造 & 遍历

cpp 复制代码
// 初始化列表构造
map<string, string> dict = {
    {"left", "左边"},
    {"right", "右边"},
    {"insert", "插入"}
};

// 遍历
for (auto& e : dict)
    cout << e.first << ":" << e.second << endl;

// 迭代器 -> 用法
for (auto it = dict.begin(); it != dict.end(); ++it)
    cout << it->first << ":" << it->second << endl;

3.4 map 插入 insert和迭代器使用

cpp 复制代码
#include<iostream>
#include<map>
#include<string>
using namespace std;

int main()
{
	//初始化列表构造
    //类似于:map={pair1,pair2,pair3....};
	map<string, string> dict = { {"left","左边"},{"right","右边"},{"insert","插入"} };
	
	//插入pair的多种方式
	//1.插入一个有名对象
	pair<string, string> kv1("first", "第一个");
	dict.insert(kv1);

	//2.插入一个匿名对象
	dict.insert(pair<string,string>("second","第二个"));

	//3.传函数模板:make_pair
	//make_pair:是一个函数模板,传Key和value会自动推导类型构造一个pair进行返回
	dict.insert(make_pair("sort", "排序"));
	//dict.insert(pair<string,string>("sort", "排序"))跟上面等价
	
	//4.多参数的隐式类型转换   C++11
	dict.insert({ "auto","自动的" });
	dict.insert({ "auto","自动" });//插入时只看key,value不一样不会更新

	//迭代器遍历
	auto it = dict.begin();
	while (it != dict.end())
	{
		//it->first+='x';err
		it->second += 'x';//vlaue支持修改,key不支持修改
		cout << it->first << ":" << it->second << endl;
		++it;
	}

	return 0;
}

map 插入 insert返回值:pair<iterator, bool>

  • key 不存在:插入成功,second = true
  • key 已存在:插入失败,second = false

3.5map的增删查

map的增删查关注以下⼏个接⼝即可: map增接⼝,插⼊的pair键值对数据,跟set所有不同,但是查和删的接⼝只⽤关键字key跟set是完全 类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代 还可以修改value。

cpp 复制代码
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);
const_iterator lower_bound(const key_type& k) const;

3.6map 最重要:operator []

map最重要的特性之一是operator[],它兼具查找、插入、修改功能。但set没有

因为map的[]本质是获取value而不是key,key不能被修改。

cpp 复制代码
//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;
}

如果[ ]访问的元素不存在,就会自动插入新元素。

所以[ ]相当于find和insert的功能,先find,找到了就直接返回value,如果没找到那么就insert。

cpp 复制代码
int main()
{
	map<string, string>dict; 
	dict.insert(make_pair("sort","排序" ));

	//key不存在:插入+修改
	dict["left"];
	dict["right"] = "右边";
	
	//key存在:修改
	dict["left"] = "左边";

	//查找,确定key存在才能这么用,否则就是插入
	cout << dict["right"] << endl;

	//插入,因为red不存在
	cout << dict["red"] << endl;

	for (auto e : dict)
	{
		cout << e.first << " " << e.second << endl;
	}
	return 0;
}

3.7 multimap和map的差异

multimap与map的主要区别:

  • 不管key和value是否相等,只管插入,重复内存不够,否则一定插入成功。
  • 删除key时,将所有相等的key删除。
  • 不支持operator[]。
  • find返回第一个匹配的迭代器。
相关推荐
君鼎2 小时前
C++11 新特性全面总结
c++
格林威2 小时前
ZeroMQ 在视觉系统中的应用
开发语言·人工智能·数码相机·机器学习·计算机视觉·c#·视觉检测
safestar20122 小时前
React 19实战:Action、并发与性能,一次告别“意大利面状态”的升级
开发语言·javascript·vue.js
一只幸运猫.2 小时前
Rust实用工具特型-Clone
开发语言·后端·rust
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 76. 最小覆盖子串 | C++ 滑动窗口题解
c++·算法·leetcode
像素猎人2 小时前
蓝桥杯OJ2049蓝桥勇士【动态规划】【dp[n]不是符合题意的答案,只是以an结尾的子问题的答案】
c++·算法·蓝桥杯·动态规划·区间dp
0xDevNull2 小时前
Java BigDecimal 完全指南:从入门到精通
java·开发语言·后端
桌面运维家2 小时前
交换机环路排查:STP配置实战与网络故障精确定位
开发语言·php
XiYang-DING2 小时前
【Java】从源码深入理解LinkedList
java·开发语言