目录
前言
正文
1.预备知识
1.1序列式容器
1.2关联式容器
1.3键值对
2.set
2.1概念
编辑 2.2set的使用
2.3set总结
2.4multiset
3.map
3.1概念
3.2、map的使用
3.3map中operator[]
3.4map总结
3.5multimap
前言
s
et
和
map
是
STL
中的容器之一,不同于普通容器,它俩的查找速度极快,常用来存储各种经常被检索的数据,因为这俩容器的底层是平衡二叉搜索树中的红黑树。
根据应用场景的不同,
STL
总共实现了两种不同结构的管理式容器:树型结构与哈希结构。
树型结
构的关联式容器主要有四种:
map
、
set
、
multimap
、
multiset。这四种容器的共同点是:使 用平衡搜索树
(
即红黑树
)作为其底层结果,容器中的元素是一个有序的序列。哈希结构的容器也有四种 unordered_map、unordered_set、unordered_multimap、unordered_multiset,容器中的元素是无序的序列。如下图所示:
正文
1.预备知识
在进行map和set容器的学习之前我们先对关联式容器和序列式容器做个知识补充。
1.1序列式容器
序列式容器 比如 string
、vector
、list
、deque
等,序列式容器的特点就是底层为线性序列的数据结构,就比如 list
,其中的节点是 线性存储的,一个节点存储一个元素,其中存储的元素都可序,但未必有序。
1.2关联式容器
关联式容器 则比较特殊,其中存储的是 <key, value>
的 键值对,这就意味着可以按照 键值大小 key
以某种特定的规则放置于适当的位置,关联式容器没有首尾的概念,因此没有头插尾插等相关操作,本文中学习的 set
和 map
就属于 关联式容器
1.3键值对
在标准库中(SGI) 专门提供了这种结构pair
//SGI 版 STL 中的实现
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) {}
#ifdef __STL_MEMBER_TEMPLATES
template <class U1, class U2>
pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
#endif
};
pair
中的 first
表示 键值,second
则表示 实值,在给 关联式容器 中插入数据时,可以构建 pair
对象比如下面就构建了一个 键值 key
为 string
,实值 value
为 int
的匿名 键值对 pair
对象
pair<string, int>("apple", 4);
可以将此匿名对象传入 关联式容器 中,当然这样写未免过于麻烦了,于是库中设计了一个函数模板 make_pair
,可以根据传入的参数,去调用 pair
构建对象并返回
make_pair("apple", 4); //构建出的匿名对象与上面的一致
make_pair
的定义如下所示:
template <class T1,class T2>
pair<T1,T2> make_pair (T1 x, T2 y)
{
return ( pair<T1,T2>(x,y) );
}
注:该函数实际会被编译器优化为 内联函数,因此不会造成过多消耗,可以放心使用
2.set
2.1概念
- set是按照一定次序存储元素的容器
- set在底层是用二叉搜索树(红黑树)实现的
其中的 T
就是 set
的实值(键值),参数2 Compare
为存储依据,默认为升序,即符合 二叉搜索树 中序遍历的结果:升序,参数3 Alloc
是空间配置器
作为 STL
中的容器,set
当然少不了迭代器,树型关联式容器迭代器的遍历结果为有序,所以迭代器遍历的本质是 中序遍历,同时 set
的迭代器还是一个 双向迭代器,支持 ++
和 --
操作
2.2set的使用
set的构造函数
可以直接创建一个空 set
使用,也可以根据迭代器区间创建 set
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main()
{
vector<int> arr = { 8,5,6,7,3,1,1,3 };
set<int> s1; //默认构造
set<int> s2(arr.begin(), arr.end()); //迭代器区间构造
cout << "s1: ";
for (auto e : s1)
cout << e << " ";
cout << endl;
cout << "s2: ";
for (auto e : s2)
cout << e << " ";
cout << endl;
return 0;
}
set
中的常用功能
count
也可以用来查找元素是否存在,对于 set
来说,键值 key
就是 实值 value
,并且因为不允许冗余,所以只有一个 键值,count
统计 键值 数量不就相当于 查找
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main()
{
vector<int> arr = { 7,3,6,9,3,1,6,2 };
set<int> s1(arr.begin(), arr.end());
for (int i = 0; i < 10; i++)
{
if (s1.count(i))
cout << i << ":[存在]" << endl;
else
cout << i << ":[不存在] " << endl;
}
return 0;
}
同时,也可以通过修改模板参数中的compare 改变默认排序顺序
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main()
{
vector<int> arr = { 7,3,6,9,3,1,6,2 };
set<int, greater<int>> s1(arr.begin(), arr.end());
for (auto e : s1)
cout << e << " ";
return 0;
}
2.3set总结
- 只有键值,键值就是实值,所以传参只需要传递key值
- 自带去重机制,不允许数据冗余
- 迭代器遍历默认为升序(底层平衡搜索树中序)
- 不可以修改键值(const保护)
2.4multiset
除此之外,multiset
和 set
的操作没什么区别,一模一样
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main()
{
vector<int> arr = { 3,5,3,4,5,9,2,3 };
multiset<int> ms1(arr.begin(), arr.end());
for (auto e : ms1)
cout << e << " ";
cout << endl;
return 0;
}
值得一提的是,当在 multiset
中查找冗余的数据时,返回的是 中序遍历中,第一次出现的元素
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main()
{
vector<int> arr = { 3,5,3,4,5,9,2,3 };
multiset<int> ms1(arr.begin(), arr.end());
auto it = ms1.begin();
while (it != ms1.end())
{
cout << *it << " | " << &*(it) << endl;
++it;
}
cout << "================" << endl;
cout << "ms1.find(3): " << &*(ms1.find(3)) << endl;
return 0;
}
count在multiset中起到了作用
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main()
{
vector<int> arr = { 3,5,3,4,5,9,2,3 };
multiset<int> ms1(arr.begin(), arr.end());
for (int i = 0; i < 10; i++)
cout << i << "在 multiset 中的数量: " << ms1.count(i) << endl;
return 0;
}
3.map
3.1概念
- map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元
- 素。
- 在内部,map中的元素总是按照键值key进行比较排序的。
- map支持下标访问符,即在[]中放入key,就可以找到与key对应的value
- map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
其中两个模版参数:
Key
就是键值对中的 键值T
则是键值对中的 实值
在 map
中会用到前面提到过的 pair
结构,其中 first
表示键值,second
表示实值map
也有迭代器,也是 双向迭代器。
3.2、map的使用
构造函数
#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main()
{
vector<pair<string, int>> arr = { make_pair("apple", 4), make_pair("origine", 7), make_pair("banna", 5) };
map<string, int> m1; //创建一个空的 map
map<string, int> m2(arr.begin(), arr.end()); //创建包含数据的 map
cout << "a: " << endl;
for (auto &a : m1)
cout << a.first << ":" << a.second << endl;
cout << "b: " << endl;
for (auto b : m2)
cout << b.first << ":" << b.second << endl;
return 0;
}
map常用功能
map插入的返回值比set略微复杂,要完成是否插入成功,同时还要表示插入成功的迭代器,所以需要返回一个pair
#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{
map<string, int> m1;
auto ret = m1.insert(make_pair("a", 97));
cout << "<" << ret.first->first << ":" << ret.first->second << ">" << " | " << ret.second << endl;
ret = m1.insert(make_pair("a", 100));
cout << "<" << ret.first->first << ":" << ret.first->second << ">" << " | " << ret.second << endl;
return 0;
}
find
和 count
跟 set
中的一样,可以用来判断元素是否存在,不过 find
返回的是 迭代器,count
返回的则是 键值数
map
是支持修改 实值 value
的,因此 可以根据普通迭代器修改 实值
#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{
map<string, int> m1;
m1.insert(make_pair("a", 97));
auto it = m1.find("a");
cout << "<" << it->first << ":" << it->second << ">" << endl;
it->second = 668;
cout << "<" << it->first << ":" << it->second << ">" << endl;
return 0;
}
3.3map中operator[]
operator[]
返回的是当前 键值 对应的 实值,如果当前 键值 不存在,则会插入新的 键值对
#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{
vector<string> word = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
map<string, int> table;
for (auto& e : word)
table[e]++;
for (auto e : table)
cout << "<" << e.first << ":" << e.second << ">" << endl;
return 0;
}
显然,map
中的 operator[]
是一个非常强大的功能
operator[]
的返回值为 mapped_type
,即 实值 value
的引用,参数 key_type
是 键值 key
重点在于 operator[]
的实现:如何凭借 键值 返回对应的 实值,并且做到新键值对的插入
(*((this->insert(make_pair(k, mapped_type()))).first)).second
注意:
operator的三步骤:
- 插入一个新的键值对
this->insert( make_pair(k, mapped_type()) )
- 获取
insert
返回值中的 键值返回值.first
即迭代器iterator
- 最后通过迭代器获取 实值
(*iterator).second
3.4map总结
- 既包含键值,也包含实值,插入时候,需要传递键值对:pair对象(make_pair)
- 自动去重,不允数据冗余
- 迭代器遍历默认升序
- 普通迭代器允许修改实值,不允许修改键值
3.5multimap
multimap
中允许出现多个 重复的键值,这就意味着 operator[]
无法确认调用者的意图 -> 不知道要返回哪个 键值 对应的 实质
所以 multimap
中没有提供 operator[]
除了 允许键值冗余 和 没有 operator[]
这个两个特点外,multimap
和 map
在操作上没有区别
当然,查找 find
时,返回的是中序遍历中第一次出现元素的迭代器;计数 count
返回的则是当前 键值 的数量。