目录
本篇介绍 STL 中树型结构(底层红黑树)的关联容器
补充:
- unordered_set、unordered_map 的底层是哈希表
- 他们的 key 需要满足:
- 可哈希性:键类型必须能够被哈希函数处理,以便将键映射到哈希表中的特定索引位置。通常,C++ 标准库提供的哈希函数 std::hash 可以处理内置类型(例如整数、浮点数、指针等),而对于自定义类型,需要提供自定义的哈希函数或者使用 std::hash_combine 等方式生成哈希值。
- 相等性比较:键类型必须支持相等性比较操作符 ==,以确保在哈希表中查找键时能够正确判断键是否相等。
0. 概述
🎯关联式容器
- 树型结构:set、map、multiset、multimap
底层:红黑树- 哈希结构:unordered_set、unordered_map
底层:哈希表(更优)
注意:使用时分别要包同名头文件,<set>、<map>、 …
树形结构–>红黑树 | 存放元素 | 关键功能 | 经典应用 |
---|---|---|---|
set | <value, value> | 排序、去重、不可修改 | count 接口实现 “在不在” 问题:门禁系统、车库系统、检查一篇文章中单词拼写正确… |
multiset | 排序、不去重、不可修改 | ||
map | <key, value> | 排序、去重、key不可修改、[]操作符 | “通过一个值查找另一个值”:字典、统计频次、电话查快递、姓名+验证码查考试成绩… |
multimap | 排序、不去重 、key不可修改 |
set 和 map (multiset & multimap)是典型的 关联式容器,与序列式容器(vector、list、deque…)不同的是,其里面存储的是<key, value>结构的 键值对,在数据检索时比序列式容器效率更高。
key 为键值,value 为 key 对应的信息。
// SGI中关于键值对的定义:
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first; // 通常来说,first 就是 key
T2 second; // second 就是 value
pair()
: first(T1())
, second(T2())
{}
pair(const T1& a, const T2& b)
: first(a)
, second(b)
{}
};
1. set
关键词: 排序 + 去重、不可修改、<value, value>
1.0 set 的模板参数说明
- T:set 中存放元素的类型,实际在底层存储 <value, value> 的键值对。
- Compare:set 中元素默认按照小于来比较。
- Alloc:set 中元素空间的管理方式。通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用 STL 标准库提供的空间配置器。
1.1 set 使用特点
-
其元素 value(同样也是键值 key,模板类型为 T)是 唯一 的。可以使用 set 去重。
元素可以插入和删除,但 不可以修改(const)。 -
使用set的迭代器遍历set中的元素,可以得到 有序 序列,默认按照小于来比较。
-
set 在底层是用二叉搜索树 红黑树 实现的。
-
set 中只放 value,但在底层实际存放的是由 <value, value> 构成的键值对。
( map/multimap 中存储的是真正的键值对 <key, value>) -
set中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n
1.2 set 使用情况
1. insert、iterator
//[set]
// 基本测试:insert,iterator
void test_set1()
{
set<int> s1;
s1.insert(2);
s1.insert(3);
s1.insert(5);
s1.insert(1);
s1.insert(5);
s1.insert(8);
s1.insert(8);
s1.insert(0);
set<int>::iterator it1 = s1.begin();
while (it1 != s1.end())
{
//*it1 += 1; // 搜索树不能随意修改,编译不通过
cout << *it1 << " ";
++it1;
}
cout << endl; // 输出 0 1 2 3 5 8
// 范围for
for (auto e : s1)
{
e += 1; // 这里 e 是一个 int,+1 操作没有在 s1 上实现
cout << e << " ";
}
cout << endl; // 输出 1 2 3 4 6 9
for (auto e : s1)
{
cout << e << " ";
}
cout << endl; // 输出 0 1 2 3 5 8
}
注意:auto e 的改变不影响 s1,auto& e 才会影响
2. find、count
// [set]
// 基本测试:find,count
void test_set2()
{
// 排序 + 去重
set<int> s1;
s1.insert(2);
s1.insert(3);
s1.insert(5);
s1.insert(1);
s1.insert(5);
s1.insert(8);
s1.insert(8);
s1.insert(0);
int x;
while (cin >> x)
{
/*auto ret = s1.find(x);
if (ret != s1.end())
{
cout << "在" << endl;
}
else
{
cout << "不在" << endl;
}*/
if (s1.count(x)) // count可以返回个数,这里可以用于查找“在不在”的问题
{
cout << "在" << endl;
}
else
{
cout << "不在" << endl;
}
}
}
注意:count 接口返回个数,可以用于查找 “在不在” 问题
2. multiset
关键词: 排序(不去重)、不可修改、<value, value>
2.1 multiset 使用特点
- 其元素 value(同样也是键值 key,模板类型为 T),可以重复 ;元素可以插入和删除,但 不可以修改(const)。
- 与 set 的区别是,multiset 中的元素可以重复,set 是中 value 是唯一的
- 使用迭代器对multiset中的元素进行遍历,可以得到 有序 的序列
- mtltiset 的插入接口中只需要插入即可
- multiset 中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n
- 使用时与 set 包含的头文件相同
2.2 multiset 使用情况
其他接口与 set 相同,此处演示与 set 不同的功能:
// 测试 multiset
void test_set3()
{
// 排序(不去重)
multiset<int> s1;
s1.insert(2);
s1.insert(3);
s1.insert(5);
s1.insert(1);
s1.insert(5);
s1.insert(8);
s1.insert(8);
s1.insert(5);
multiset<int>::iterator it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl; // 输出 0 1 2 3 5 5 8 8
// 如果有多个相同 key,find 中序的第一个 key
auto ret = s1.find(8);
while (ret != s1.end() && *ret == 8)
{
cout << *ret << " ";
++ret;
}
cout << endl; // 输出 8 8
cout << s1.count(1) << endl; // 输出 1
cout << s1.count(8) << endl; // 输出 2
}
注意:find 返回中序排序时,重复的第一个 key
3. map
关键词: 排序 + 去重、key不可修改、<key, value>、[ ]操作符
3.0 map 的模板参数说明
- key:键值对中 key 的类型
- T:键值对中 value 的类型
- Compare:比较器的类型,map 中的元素是按照 key 来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
- Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器
3.1 map 使用特点
- map 中的的元素是键值对 <key, value>
- map 中的 key 是唯一的,并且 不能修改
- 默认按照小于的方式对 key 进行比较(与 value 情况无关)
- map 中的元素如果用迭代器去遍历,可以得到一个 有序 的序列
- map的底层为平衡搜索树 红黑树,查找效率比较高 O ( l o g 2 N ) O(log_2 N) O(log2N)
- 支持
[]操作符
,operator[] 中实际进行 插入+查找。
3.2 map 使用情况
1. 使用场景:字典
// 字典
void test_map1()
{
map<string, string> dict;
dict.insert(pair<string,string>("plug","插头")); // 正常写成这样,构造一个pair的匿名对象进行插入
dict.insert(make_pair("sort", "排序")); // make_pair是一个函数模板,可以省略一步<显示参数>
dict.insert(make_pair("string", "字符串"));
dict.insert(make_pair("count", "计数"));
//map<string, string>::iterator dit = dict.begin();
auto dit = dict.begin();
while (dit != dict.end())
{
//cout << (*dit).first << ":" << (*dit).second << endl;
cout << dit->first << ":" << dit->second << endl;
++dit;
}
/*
输出:
count:计数
plug:插头
sort:排序
string:字符串
*/
}
2. 无法 insert 重复 key
// 测试 insert 重复 key
void test_map2()
{
map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("string", "字符串"));
dict.insert(make_pair("count", "计数"));
dict.insert(make_pair("string", "(字符串)")); //插入失败,因为 key-value 模型,考虑的只有 key,key 已经有了不能插入了
auto dit = dict.begin();
while (dit != dict.end())
{
cout << dit->first << ":" << dit->second << endl;
++dit;
}
/*
输出:
count:计数
sort:排序
string:字符串
*/
}
3. map 的重要功能 [ ],实现 插入、修改、插入+修改、查找
// [] 实现:
// 1. 插入
// 2. 修改
// 3. 插入+修改
// 4. 查找
// 【方法一】
typedef pair value_type
value_type& operator[](const key_type& k)
{
return(*((this->insert(make_pair(k, mapped_type()))).first)).second;
}
// 【方法二】
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V())); // make_pair里面的V()是一个匿名函数
return ret.first->second;
}
4. 使用场景:水果个数(统计出现频次)
// 测试[] 水果个数
void test_map3()
{
string arr[] = { "梨","葡萄","梨","桔子","西瓜","菠萝","菠萝","桔子" };
map<string, int> countMap;
//for (auto& e : arr) // 不使用[]的写法....
//{
// auto ret = countMap.find(e);
// if (ret == countMap.end())
// {
// countMap.insert(make_pair(e, 1));
// }
// else
// {
// ret->second++;
// }
//}
//for (auto& kv : countMap)
//{
// cout << kv.first << ": " << kv.second << endl;
//}
for (auto& e : arr)
{
countMap[e]++; // 添加k,修改v (没有key可以直接添加key并修改value,有key便修改它的value)
}
for (auto& kv : countMap)
{
cout << kv.first << ": " << kv.second << endl;
}
}
5. [] 功能测试
// 测试[]
void test_map4()
{
map<string, string> dict;
dict.insert(make_pair("string", "字符串")); // 插入
dict.insert(make_pair("string", "(字符串)")); // 插入失败
dict["left"]; // 插入 k,v 是 string 默认构造生成的缺省值""
dict["right"] = "右边"; // 插入 + 修改
dict["string"] = "(字符串)"; // 修改
cout << dict["string"] << endl; // 查找,输出 (字符串)
for (auto& kv : dict)
{
cout << kv.first << ": " << kv.second << endl;
}
/*
输出:
left:
right: 右边
string: (字符串)
*/
}
4. multimap
关键词: 排序(不去重)、key不可修改、<key, value>、[ ]操作符
4.1 multimap 使用特点
- multimap 中的 key 是 可以重复 的
- multimap 中的元素默认将 key 按照小于来比较
- multimap 中没有重载 operator[] 操作
(因为 [] 是一个 key 对应一个值,而 multi 允许多个相同 key) - 使用时与 map 包含的头文件相同
4.2 multimap 使用情况
// 允许键值冗余(不去重)
// multimap 没有 []!!因为原来 map 的 k 和 v 是一对一,现在是一对多了,[] 也没必要存在了
void test_map5()
{
multimap<string, string> mdict;
mdict.insert(make_pair("sort", "排序"));
mdict.insert(make_pair("string", "字符串"));
mdict.insert(make_pair("count", "计数"));
mdict.insert(make_pair("string", "(字符串)")); // 能进了
mdict.insert(make_pair("string", "字符串")); // value相同也可以插入,因为看的是key,跟value无关的蛤
for (auto& kv : mdict)
{
cout << kv.first << ":" << kv.second << endl;
}
/*
输出:
count:计数
sort:排序
string:字符串
string:(字符串)
string:字符串
*/
}
# 相关题目
- 复制带随机指针的链表
- 前K个高频单词
- 两个数组的交集
🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~