一、序列式容器 vs 关联式容器
-
序列式容器 :vector、list、string、deque 等线性结构,元素按存储位置顺序存放,彼此之间没有强关联。
-
关联式容器 :map /set/unordered_map /unordered_set非线性结构,元素按key(关键字) 存储,key 和 value 之间有强关联。底层是红黑树 (平衡二叉搜索树),因此有序、去重、查找效率 O (logN)。
简单对应:
set→ key 模型(只存 key,用来判断 "在不在")map→ key/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返回第一个匹配的迭代器。