一、auto占位符
在介绍高级STL数据结构之前,我们先来说说auto这个关键的占位符
1.auto可以根据后面的数据来推测前面的数据类型
因此,我们可以用auto随心所欲的声明变量,就像下面这样:
int main(){auto i = 1;auto j = 12.05;auto arr = { 1,2,3,4 };cout << i << endl << j << endl;return 0;}
输出结果
值得注意的是:
- 使用auto声明的变量必须初始化(你想啊,auto可以是任何类型,假如没有初始化,系统怎么知道他是什么类型呢)
- 函数和参数不能被声明为auto
2.auto可以作为迭代器
不会有人连迭代器是啥都不知道吧(bushi
例如for(int i = 0; i < 5; i++) 这个循环,这里面的 i 就是 迭代器
那么请大家来看这段代码
int main(){auto arr = { 1,2,3,4,4,5,5,6,7 };for (auto i : arr)cout << i << " ";return 0;}
输出结果
优雅,实在是太优雅了、
二、pair对组
你可能会好奇为什么map的前置知识要讲这么多。
基础不牢,地动山摇!
其实,map就是由一个一个pair组成的。
1.pair是什么
中文名叫 对组。跟结构体很像,它里面可以一次存两个数据。就像这样:
//初始化一个对组pair<int, int>p0(1, 2);
<>里面的是两个数据的类型,()里面是数据的值,当然这两个数据类型可以不一样。
2.创建一个对组
pair又专门的创建函数 make_pair ,是不是很高级
//创建一个对组pair<string, int>p1 = make_pair("tom", 30);
当然,除了使用内置函数,你直接初始化一个也算创建了一个对组。
3.访问对组的元素
对组的第一个元素叫 first, 第二个元素叫 second
请看下面的代码
int main(){//pair 对组的学习//初始化一个对组pair<int, int>p0(1, 2);//创建一个对组pair<string, int>p1 = make_pair("tom", 30);//输出cout << p1.first << " " << p1.second << endl; // 访问对组用序数词!!!return 0;}
可以看到,直接把pair
pair作为函数返回值时也可以用tie关键字来访问,在下面的代码中一并展示。
#include<iostream>#include<string>using namespace std;typedef pair<int, string> Arr; // 改个名字,简单些Arr getArr(){return make_pair(20,"tom");}int main(){Arr a; // 单独声明,既不初始化也不创造int n;string str; // 事先把tie的变量准备好tie(n, str) = getArr();cout << n << " " << str << endl;// 当然原始方法也可以a = getArr();cout << a.first << " " << a.second;return 0;}
三、map
首先说一下头文件吧
#include<map>
1.map是什么
map是STL的一个关联容器,它提供一对一的hash。第一个可以称为关键字(key),每个关键字只能在map中出现一次;第二个可能称为该关键字的值(value)。map以模板(泛型)方式实现,可以存储任意类型的数据,包括使用者自定义的数据类型。Map主要用于资料一对一映射(one-to-one)的情況,map內部的实现自建一颗红黑树,这颗树具有对数据自动排序的功能。在map内部所有的数据都是有序的。
关键点就是:
- 能提供一对一的hash,所以map几乎是哈希算法的标配函数;
- map里面的每一个pair的第一元素叫做key(关键字),第二个元素叫做value(值),可以用key访问到value;
- map里面的每一元素都是有序的。
2.创建一个map
就像这样,非常简单;map<int, int>m;
表示这个map里面每一个pair都必须是pair 的,也就是说,pair虽然不限制类型,但是map限制了pair的类型,如果将 理解为一种类型的话,那么,map里面的pair必须是同类型的。 3.在map中插入元素
这里总共有三种方法,但我只介绍两种,剩下的一种大家去CSDN一查便知。
主要是学的太多我也记不住。方法1.使用insert函数插入,这里我们就可以看到map的有序性了
请看下面的代码(里面的打印函数大家可以先看下面的代码) ```cppinclude
include
include
using namespace std;
void printMap(map
int main()
{
map
输出结果<br /><br />我们不妨观察一下,**这个map的顺序到底是谁的顺序**。<a name="v0I5V"></a>### 方法2.使用key引入```cppvoid printMap(map<int, int>& m){for (auto i = m.begin(); i != m .end(); i++){cout << "key= " << i->first << " ";cout << "value= " << (*i).second << endl;}}int main(){// 方法2. 使用key引入m[1] = 20;m[2] = 30;m[3] = 10;//输出printMap(m);return 0;}
解释一下:
m[1] = 20; 这句代码
[1] 表示key,这说明map是支持用“下标引用运算符[]”来访问key的,当使用了m[1], map就会去找first元素为1的对组,如果找不到,就创建一个,并将他的value值赋为20,如果找到了,就修改这个值。
4.map的有序性
从方法1的代码【在map中插入元素+打印[1]】中,我们看到,key值的大小就是map的排序方式。
那么,如果key是字母呢?
void printMap(map<char, int>& m){for (auto i = m.begin(); i != m .end(); i++){cout << "key= " << i->first << " ";cout << "value= " << (*i).second << endl;}}int main(){map<char, int>m;m.insert((pair<int, int>('a', 10)));m.insert((pair<int, int>('b', 60)));m.insert((pair<int, int>('d', 50)));m.insert((pair<int, int>('z', 10)));m.insert((pair<int, int>('s', 10)));//输出printMap(m);return 0;}
输出如你所见
可见,key是字母时,排序方式是key的ASCII码值。
大家可以自己探索一下string类型的key的排序方式(虽然没什么意义,毕竟做题是谁也不会放着数组的下标不用去搞个string做key名)
5.map元素的访问
结合输出map的输出函数,不难看出有两种方式。
void printMap(map<int, int>& m){for (auto i = m.begin(); i != m .end(); i++){cout << "key= " << i->first << " ";cout << "value= " << (*i).second << endl;}}
并且跟结构体的输出很像。
先用auto拿到map的每一个对组,再直接访问对组即可。
访问对组有两种方式,i->first和(*i).first都是直接指向的意思。我个人比较倾向于第一种,因为只有看见跟指针长得很像的东西我就想🤮
5.查找map里面的元素
vector里面的find函数大家都知道吧,这里跟他很像。
find函数查找的是key,当找到了这个key,他返回key的位置,找不到返回map的end()位置。
int main(){map<char, int>m;m.insert((pair<int, int>('a', 10)));m.insert((pair<int, int>('b', 60)));m.insert((pair<int, int>('d', 50)));m.insert((pair<int, int>('z', 10)));m.insert((pair<int, int>('s', 10)));//find函数查找map里面的元素值auto i = m.find('d');if (i == m.end()){cout << "not find";}else{cout << i->second;}return 0;}
6.删除map里面的元素和清空map
需要使用erase函数
void printMap(map<char, int>& m){for (auto i = m.begin(); i != m .end(); i++){cout << "key= " << i->first << " ";cout << "value= " << (*i).second << endl;}}int main(){map<char, int>m;m.insert((pair<int, int>('a', 10)));m.insert((pair<int, int>('b', 60)));m.insert((pair<int, int>('d', 50)));m.insert((pair<int, int>('z', 10)));m.insert((pair<int, int>('s', 10)));//find函数查找map里面的元素值int n = m.erase('b'); // 删除成功就返回1,否则返回0printMap(m);//find函数清除mapm.erase(m.begin(), m.end());printMap(m);return 0;}
7.map里面的函数汇总
| 函数名 | 功能 |
|---|---|
| begin() | 返回指向map头部的迭代器 |
| clear() | 删除所有元素 |
| count() | 返回指定元素出现的次数 |
| empty() | 如果map为空则返回true |
| end() | 返回指向map末尾的迭代器 |
| equal_range() | 返回特殊条目的迭代器对 |
| erase() | 删除一个元素 |
| find() | 查找一个元素 |
| insert() | 插入元素 |
| key_comp() | 返回比较元素key的函数 |
|---|---|
| lower_bound() | 返回键值>=给定元素的第一个位置 |
| max_size() | 返回可以容纳的最大元素个数 |
| size() | 返回map中元素的个数 |
| swap() | 交换两个map |
| upper_bound() | 返回键值>给定元素的第一个位置 |
四、unordered_map
1.unordered_map是什么
map是有序的,ordered是排好序的意思,那么顾名思义,unordered-map就是不排序的map
hash_map ≈ unordered_map,最初的 C++ 标准库中没有类似 hash_map 的实现,但不同实现者自己提供了非标准的 hash_map。 因为这些实现不是遵循标准编写的,所以它们在功能和性能保证方面都有细微差别。从 C++ 11 开始,hash_map 实现已被添加到标准库中。但为了防止与已开发的代码存在冲突,决定使用替代名称 unordered_map。这个名字其实更具描述性,因为它暗示了该类元素的无序性。
2.创建一个unordered_map
直接定义即可
需要使用头文件
#include
请看代码:
int main(){// unordered-map的学习unordered_map<string, string>umap;umap.emplace("tom", "is");cout << umap["tom"] << endl;return 0;}
3.unordered_map常用函数汇总
unordered_map和map的使用函数很像,大家直接看汇总吧
| 函数 | 功能 |
|---|---|
| begin() | 返回指向容器中第一个键值对的正向迭代器。 |
| end() | 返回指向容器中最后一个键值对之后位置的正向迭代器 |
| empty() | 若容器为空,则返回 true;否则 false。 |
| size() | 返回当前容器中存有键值对的个数。 |
| max_size() | 返回容器所能容纳键值对的最大个数 |
| at(key) | 返回容器中存储的键 key 对应的值,如果 key 不存在,则会抛出 out_of_range 异常。 |
| find(key) | 查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。 |
| count(key) | 在容器中查找以 key 键的键值对的个数。 |
| equal_range(key) | 返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中键为 key 的键值对所在的范围。 |
| emplace() | 向容器中添加新键值对,效率比 insert() 方法高。 |
|---|---|
| insert() | 向容器中添加新键值对 |
| erase() | 删除指定键值对 |
| clear() | 清空容器,即删除容器中存储的所有键值对 |
| swap() | 交换 2 个 unordered_map 容器存储的键值对,前提是必须保证这 2 个容器的类型完全相等。 |
4.unordered_map和map的比较
map:
有序性就是最大的优点。
缺点是速度慢,占用空间大。所以我们一般在做题时不用他。
unordered_map
内部用hash实现。
速度很快,
但是创建hash表需要的时间复杂度很大
五、实际应用
1、leetcode题目 剑指offer 2-010 和为k的子数组
题目链接: ==>传送门
这题可以说是模板题了。
我首先想到的是DFS,但此题数据太大,不容易过,所以,我们用hash求前缀和。
为什么要求前缀和呢?
假如,我们遍历nums数组,此时拿到的元素是nums[i],那么只要这个元素前面的一段元素的和为k-nums[i],那么这就是一种符合条件的情况。
但是,如果说前面总共有四个元素,而我们只需要取下标为1~3的元素,也就是第一个元素不能取,该怎么办呢?
我们默认随着数组的遍历,数组的前缀和是增加的。对于每一个前缀和,我们都把它放到unordered_map并计算次数。这就相当于把所有的前缀和都用一个数组存起来了。
既然已经得到了这个前缀和数组,那么后面的前缀和 减去 前面的前缀和 就等于中间这段的和了。
上代码:
#include<unordered_map>int subarraySum(vector<int>& nums, int k) {unordered_map<int, int>arr; // 第一个int表示前缀和,第二个int表示这个前缀和出现的次数arr[0] = 1;int ret = 0; // 用于计算前缀和int ans = 0;for (auto& i : nums){ret += i;if (arr.find(ret - k) != arr.end()) // ret-k 如果跟前面的前缀和有重复,重复的那个前缀和就是ret-k.// 而此时的前缀和是ret, 所以中间的那一段和就是kans += arr[ret - k];arr[ret]++; // 重新创建前缀和pair}return ans;}int main(){vector<int>nums = { 1,2,3};cout << subarraySum(nums, 3);return 0;}
2、leetcode题目-525 连续数组
题目链接:==》传送门
这个题是模板题稍微变了一下形状。
我们让0和1的数量相等,加入把0看成-1,那么,这个题就是要求和为0的最长连续子数组。
按照第一题的思路,我们用一个counter了存储前缀和,在遍历nums时每次更新counter,然后再map里面寻找这个前缀和是否重复。
如果重复了,也就是说,前i个元素和前j个元素的前缀和相等。那只能说明:
从i+1到j 的元素的和为0.
接下来计算长度:
从下标为i的元素到下标为j的元素的这一段的数组长度为 j - i + 1;
但是,我们这里的长度从i + 1开始算起,所以,本次数组的长度为 j - i;在每次循环符合条件是更新最大长度即可。
map存储的数点缀和与此前缀和的结束元素的下边。
上代码;
#include<unordered_map>int findMaxLength(vector<int>& nums) {int n = nums.size();unordered_map<int, int>map; // map第一个元素存储的是前缀和,第二个元素是此前缀和出现的结束下标map[0] = -1; // 边界条件int counter = 0; // 用于计算前缀和int ans = 0; // 连续数组的最大长度for (int i = 0; i < n; i++){if (nums[i] == 1)counter++;elsecounter--;if (map.count(counter) != 0) // 以value值查找用find,以key值查找用countans = max(ans, i - map[counter]);elsemap[counter] = i; // 将此前缀和的结束下标连同前缀和放进map}return ans;}int main(){vector<int>nums = { 0,1 };cout << findMaxLength(nums);return 0;}
