第十三讲 | map和set的使用
map和set的使用
- 一、序列式容器和关联式容器
- 二、set系列的使用
- 三、map系列的使用
-
- 1、map和multimap参考文档
- 2、map类的介绍
- 3、pair类型介绍
- 4、map的构造
- 5、map的增删查
- 6、map的数据修改
- 7、构造遍历及增删查使用样例
- 8、map的迭代器和[]功能样例
- 9、multimap和map的差异
- 10、算法题
一、序列式容器和关联式容器
前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为是逻辑结构为线性序列的数据结构,两个位置存储的值一般没有紧密的关联关系,比如交换一下,它依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。单纯的存储数据。
关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置存储的值有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。不仅仅是存储数据,还要进行搜索。
本章节讲解的map和set底层是红黑树,红黑树是一颗平衡二叉搜索树。set是key搜索场景的结构,map是key/value搜索场景的结构。
二、set系列的使用
1、set和multiset参考文档
2、set类的介绍
-
set的声明如下,T就是set底层关键字的类型。
-
set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模版参数。
-
set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。
-
一般情况下,我们都不需要传后两个模版参数。
-
set底层是用红黑树实现,增删查效率是O(logN),迭代器遍历是走的搜索树的中序,所以是有序的。
-
前面部分我们已经学习了vector/list等容器的使用,STL容器接口设计,高度相似,所以这里我们就不再一个接口一个接口的介绍,而是直接带着大家看文档,挑比较重要的接口进行介绍。
3、set的构造和迭代器
set的构造我们关注以下几个接口即可。
set支持正向和反向迭代遍历,遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,set的iterator和const_iterator都不支持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。
4、set的增删查
set的增删查关注以下几个接口即可:
key_type、value_type因为底层typedef的原因,都表示第一个模板参数T。
5、insert和迭代器遍历使用样例
set严格来说是\"去重+排序\",所以使用set来进行去重的操作是非常方便的。
#include#includeusing namespace std;int main(){// 去重+升序排序set<int> s;// 去重+降序排序(给一个大于的仿函数)//set<int, greater> s;s.insert(5);s.insert(2);s.insert(7);s.insert(5);// auto it = s.begin();set<int>::iterator it = s.begin();while (it != s.end()){// error,set不支持修改//*it = 1;cout << *it << \" \";++it;}cout << endl;// 支持迭代器就支持范围for//for (auto e : s)//{//cout << e << \" \";//}// 插⼊⼀段initializer_list列表值,已经存在的值插⼊失败s.insert({ 2, 3, 6, 8 });for (auto e : s){cout << e << \" \";}cout << endl;set<string> strset = { \"sort\", \"insert\", \"add\" };// 遍历string⽐较ascll码⼤⼩顺序遍历的for (auto& e : strset){cout << e << \" \";}cout << endl;return 0;}
6、find和erase使用样例
#include#includeusing namespace std;int main(){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);// 返回值是整型是为了兼容multiset中erase的用法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),算法库里的find是为了vector、list这样的容器准备的auto pos1 = find(s.begin(), s.end(), x);// set自身实现的查找 O(logN)auto pos2 = s.find(x);// 利用count间接实现快速查找cin >> x;/*auto pos = s.find(x);if (pos != s.end())*/if (s.count(x)){cout << x << \"存在\" << endl;}else{cout << x << \"不存在\" << endl; }return 0;}
把一段闭区间给lower-bound(下限)、upper-bound(上限),它们可以利用迭代器找一段set容器里的左闭右开的区间。
#include#includeusing namespace std;int main(){std::set<int> myset;for (int i = 1; i < 10; i++)myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90for (auto e : myset){cout << e << \" \";}cout << endl;// 实现查找到的[itlow, itup)包含[30, 60]区间// 返回 >= 30auto itlow = myset.lower_bound(30);// 返回 > 60auto itup = myset.upper_bound(60);// 删除这段区间的值myset.erase(itlow, itup);for (auto e : myset){cout << e << \" \";}cout << endl;return 0;}
// 实现查找到的[itlow, itup)包含[35, 65]区间// 返回 >= 35auto itlow = myset.lower_bound(35);// 返回 > 65auto itup = myset.upper_bound(65);// 删除这段区间的值myset.erase(itlow, itup);for (auto e : myset){cout << e << \" \";}cout << endl;
// 实现查找到的[itlow, itup)包含[35, 90]区间// 返回 >= 35auto itlow = myset.lower_bound(35);// 返回 > 90,返回的是end()auto itup = myset.upper_bound(90);// 删除这段区间的值myset.erase(itlow, itup);for (auto e : myset){cout << e << \" \";}cout << endl;
7、multiset和set的差异
multiset和set的使用基本完全类似,主要区别点在于multiset支持值冗余,那么insert/find/count/erase都围绕着支持值冗余有所差异,具体参看下面的样例代码理解。
#include#includeusing namespace std;int main(){// 相⽐set不同的是,multiset是排序,但是不去重multiset<int> s = { 4,2,7,2,4,8,4,5,4,4,9 };auto it = s.begin();while (it != s.end()){cout << *it << \" \";++it;}cout << endl;// 相比set不同的是,x可能会存在多个,find查找中序的第一个xint x;cin >> x;auto pos = s.find(x);while (pos != s.end() && *pos == x){cout << *pos << \" \";++pos;}cout << endl;// 相⽐set不同的是,count会返回x的实际个数cout << s.count(x) << endl;// 删除中序的第一个x/*pos = s.find(x);if (pos != s.end())s.erase(pos);*/// 相⽐set不同的是,erase给值时会删除所有的xs.erase(x);for (auto e : s){cout << e << \" \";}cout << endl;cout << s.erase(5) << endl;cout << s.erase(6) << endl;return 0;}
大量的数据排序用multiset还是不及放到vector里面走sort,虽然时间复杂度是O(logN),但是树形结构、链式结构的空间缓存命中率是不及vector的。
8、算法题
349.两个数组的交集
题解:两个数组的交集
142.环形链表Ⅱ
题解:环形链表Ⅱ