前言
本篇文章将总结常用STL,以及其常用方法。
前置知识
迭代器
介绍
定义:迭代器是一种检查容器内元素并遍历元素的数据类型。迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。
迭代器和指针的区别
- 迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,通过重载了指针的一些操作符,->,*,++, --等封装了指针,是一个“可遍历STL容器内全部或部分元素”的对象, 本质是封装了原生指针,指针的++只是简单的去增加快大小的地址,这在支持随机迭代器的容器中是可行的,但是当去遍历list这种内存不连续的来说,就无法只通过简单的++来实现了,而迭代器是可以++访问任何容器的。
- 迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用*取值后的值而不能直接输出其自身。
- 指针能指向函数而迭代器不行,迭代器只能指向容器;指针是迭代器的一种。指针只能用于某些特定的容器;迭代器是指针的抽象和泛化。所以,指针满足迭代器的一切要求。
迭代器在使用后就会被释放,不能再次使用,而指针可以
迭代器的运算符
*iter // 返回迭代器iter所指元素的引用
iter->mem // ->左边为指针,.左边为实体 == (*iter).mem
iter + n // 返回的迭代器是原迭代器向前移动若干个元素。如果超出容器的范围,则返回迭代器的第一个元素的位置,或者尾元素的下一个位置。
iter - b // 返回的迭代器是原迭代器向后移动若干个元素。如果超出容器的范围,则返回迭代器的第一个元素的位置,或者尾元素的下一个位置。
iter += n // 迭代器加法赋值语句,将iter + n 赋值给iter (!!!不能超范围!!!)
iter -= n // 迭代器减法赋值语句,将iter - n 赋值给iter (!!!不能超范围!!!)
iter1 - iter2 // 返回两个迭代器之间的距离。返回的类型为difference_type,带符号的整数。建议使用auto
>、>= 、<、<=、 ==、 != // 判断两个迭代器的位置。必须是同一个容器的迭代器
迭代器的类型:iterator 和 const_iterator
begin和end返回的具体类型由对象是否是常量决定,如果是常量,begin和end返回的const_iterator;如果不是常量,返回iterator。
vector<int>::iterator it; // it可以读写vector<int>的元素
vector<int>::const_iterator it3 // it3只能读元素,不能写元素
迭代器分类
容器 | 迭代器功能 迭代器属性 |
---|---|
vector | 随机访问 /一种随机访问的数组类型,提供了对数组元素进行快速随机访问以及在序列尾部进行快速的插入和删除,可以在需要的时候修改其自身的大小 |
deque(双向队列) | 随机访问/ 一种随机访问的数组类型,提供了对序列两端进行快速的插入和删除,可以在需要的时候修改其自身的大小 |
list | 双向 /不支持随机访问,插入和删除所花费的时间是固定的,可以进行双向迭代 |
set/multiset | 双向 /随机存储,关键字和数据元素是同一个值,可以进行双向迭代 |
map/multimap | 双向 /成对数值,关键字与元素进行关联,可以进行双向迭代 |
stack | 无 /不允许遍历,所以不支持迭代器,只支持插入和取出头部元素 |
queue | 无 /不允许遍历,所以不支持迭代器,只支持低端插入和取出头部元素 |
priority_queue | 无/ 不允许遍历,所以不支持迭代器,只支持插入和取出头部元素 |
迭代器失效
容器的插入insert和erase操作可能导致迭代器失效
对于erase操作不要使用操作之前的迭代器,因为erase的那个迭代器一定失效了,正确的做法是返回删除操作时候的那个迭代器。
通用函数
函数 | 说明 /适用范围 |
---|---|
.begin() .end() | 返回第一个元素迭代器和最后一个元素迭代器 /支持迭代器的 |
sort(first,last,cmp) | 排序,/默认是升序 vector |
reverse(first,last) | 翻转列表,可以将vector行翻转/ vector,string |
reseize(int),reserve(int) | 重新定义容器大小和元素多少 /全 |
empty() | 如果为空返回真/ 全 |
front(),back() | 返回第一个,最后一个元素 / queue,vector |
push_back(),pop_back() | 在末尾插入和删除一个元素(emplace_back) /vector,string,list |
pop(),push() | 删除元素/加入元素/ queue、stack |
top() | 返回顶部元素 /stack、优先队列 |
v.insert(v.begin(),v1.first,v1.last) | 向任意迭代器it处插入一个元素/插入某个值/ 支持迭代器 |
swap() | 交换两个容器的内容(可以交换二维数组行)/ 全 |
lower_bound(first,last, target) | 查找第一个大于等于target目标值的位置 /vector,set,map |
upper_bound(first,last, target) | 查找第一个大于target目标值的位置**(基于二分查找实现,所以可以用作有序的容器)** /vector,set,map |
binary_search(first,last, target) | 查找target是否存在于数组或vector中,找到返回true/ vector |
find(first,last, target) | 返回一个指向被查找到元素的迭代器/位置/ set,string,map |
count(first,last, target) | 计数 /set,map |
erase(迭代器) | 删除某个元素 ,并返回下一个元素的迭代器 /set,vector,map |
clear() | 清空容器/ 全 |
push()\push_back()\emplace_back()\insert
- push(val):在栈顶增加元素(stack),将元素接到队列的末端(queue)
- push_back(val):该函数将一个新的元素加到vector的最后面,位置为当前最后一个元素的下一个元素,新的元素的值是val的拷贝(或者是移动拷贝)
emplace_back():push_back 和 emplace_back 的差异
insert():在指定位置插入/在关联式容器中插入某个值。
set.insert(xx).second
用来确认某个值是否插入成功,成功就返回true。
sort排序(添加lambda表达式)
注意:
- sort排序的范围是前闭后开,例如:sort(arr, arr+10); 排序的范围是从arr[0]到arr[9]
- sort的排序方法不写的话,默认是从小到大
//从大到小排序
sort(temp.begin(),temp.end(),[](int a ,int b){return a>b;});
//按照第一列升序排序
sort(v.begin(),v.end(),[](vector<int> &a,vector<int>&b){return a[0]<b[0];});
//定义按照第一列升序排列,如果相等,按照第二列升序排列
bool cmp(vector<int> &a,vector<int>&b>{
if(a[0]!=b[0] return a[0]<b[0];
else return a[1]<b[1];
}
一、STL?
STL 是“Standard Template Library”的缩写,中文译为“标准模板库”。STL 是 C++ 标准库的一部分
常用的容器:vector,stack,queue,deque,list,map,set
头文件:algorithm,vector
vector是标准库中常见的一种容器,使用起来非常方便,可以用来代替c++原本的数组。
以下介绍容器 vector 的相关用法
函数 | 说明 /适用范围 |
---|---|
.begin() .end() | 返回第一个元素迭代器和最后一个元素迭代器 /支持迭代器的 |
sort(first,last,cmp) | 排序,默认是升序 vector |
reverse(first,last) | 翻转列表,可以将vector行翻转 vector,string |
reseize(int),reserve(int) | 重新定义容器大小和元素多少 全 |
empty() | 如果为空返回真 全 |
front(),back() | 返回第一个/最后一个元素 queue,vector |
push_back(),pop_back() | 在末尾插入和删除一个元素(emplace_back) vector,string,list |
pop(),push() | 删除元素/加入元素 queue、stack |
top() | 返回顶部元素 stack、优先队列 |
v.insert(v.begin(),v1.first,v1.last) | 向任意迭代器it处插入一个元素/插入某个值 支持迭代器 |
swap() | 交换两个容器的内容(可以交换二维数组行) 全 |
lower_bound(first,last, target) | 查找第一个大于等于target目标值的位置 vector,set,map |
upper_bound(first,last, target) | 查找第一个大于target目标值的位置**(基于二分查找实现,所以可以用作有序的容器)** vector,set,map |
binary_search(first,last, target) | 查找target是否存在于数组或vector中,找到返回true vector |
find(first,last, target) | 返回一个指向被查找到元素的迭代器/位置 set,string,map |
count(first,last, target) | 计数 set,map |
erase(迭代器) | 删除某个元素 ,并返回下一个元素的迭代器 set,vector,map |
clear() | 清空容器 全 |
二、Vector
1. vector的创建
vector作为存放一串数据的容器,在创建和初始化的时候就要考虑数据类型、数据的个数以及数据的值,并且针对这几个属性就可以有几种不同的初始化方式。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main() {
vector<int> ve1;
vector<int> ve2(7);//定义大小,int默认初始化是0
vector<int> ve3(10,1);//大小10 + 初始1
return 0;
}
2 方法函数
知道了如何定义初始化可变数组,下面就需要知道如何添加,删除,修改数据。
相关方法函数如下:
c指定为数组名称 | 代码 含义 |
---|---|
c.front() | 返回第一个数据 O ( 1 ) O(1)O(1) |
c.back() | 返回最后一个数据 O ( 1 ) O(1)O(1) |
c.pop_back() | 删除最后一个数据 O(1) |
c.push_back(element) | 在尾部加一个数据 O(1) |
c.size() | 返回实际数据个数(unsigned类型) O(1) |
c.clear() | 清除元素个数 O(N),N为元素个数 |
c.resize(n,v) | 改变数组大小为n,n个空间数值赋为v,如果没有默认赋值为0 |
c.insert(it,x) | 向任意迭代器it插入一个元素x O(N) |
例:c.insert(c.begin()+2,-1) | 将-1插入c[2]的位置 |
c.erase(first,last) | 删除[first,last)的所有元素 |
c.begin() | 返回首元素的迭代器(通俗来说就是地址) |
c.end() | 返回最后一个元素后一个位置的迭代器(地址) |
c.empty() | 判断是否为空,为空返回真,反之返回假 |
注意: end()返回的是最后一个元素的后一个位置的地址,不是最后一个元素的地址,所有容器均是如此
以上 O(n),O(1)说的是时间复杂度
//其他操作(vector本身不具有的函数)
sort(ve.begin(),ve.end()); //对元素进行从小到大排列
reverse(ve.begin(),ve.end()); //元素倒置
vector<int>::iterator it = find(ve.begin(), ve.end(), val);//查找
vector<int>::iterator it = find(vec.begin(), vec.end(), 6);
3. vector的三种遍历方式
vector<int> ve = {1,2,3};
int n = ve.size();获取大小
//第一种,下标遍历
for(int i = 0; i < n; i ++) cout<<ve[i];//ve.at(i);
//第二种,for_each
for(int a : ve) cout<< a;
//迭代器遍历
for(vector<int> :: iterator iter = ve.begin(); iter != ve,end(); iter ++) cout<< *iter;
三、stack
1. 介绍
栈为数据结构的一种,是STL中实现的一个先进后出,后进先出的容器。
就像火车进入没有出口的隧道一样,隧道是stack栈容器,火车车厢是入栈元素,火车头先进去,火车尾最后进隧道,当火车倒出来时,火车尾最先出来,火车头最后出来,所有的元素满足先进后出的规则。
头文件需要添加
#include<stack>
//声明
stack<int>sta;
stack<string>sta;
stack<node>sta;//node是结构体类型
2. 方法函数
代码 | 含义 |
---|---|
push() | 压栈,增加元素 O(1) |
pop() | 移除栈顶元素 O(1) |
top() | 取得栈顶元素(但不删除)O(1) |
empty | 检测栈内是否为空,空为真 O(1) |
size() | 返回stack内元素的个数 O(1) |
3. 注意点
a.栈遍历
栈只能对栈顶元素进行操作,如果想要进行遍历,只能将栈中元素一个个取出来存在数组中
四、queue(队列)
1. 介绍
队列是一种先进先出的数据结构。 比喻性的描述可为 一条两端通透的隧道,火车车厢先进就先出,后进就后出。
头文件
#include<queue>
定义初始化
queue<int>q;
2. 方法函数
代码 | 含义 |
---|---|
front() | 返回队首元素 O(1) |
back() | 返回队尾元素 O(1) |
push() | 尾部添加一个元素副本 进队O(1) |
pop() | 删除第一个元素 出队 O(1) |
size() | 返回队列中元素个数,返回值类型unsigned int O(1) |
empty() | 判断是否为空,队列为空,返回true O(1) |
3. 队列模拟
使用q[ ]数组模拟队列
hh表示队首元素的下标,初始值为0
tt表示队尾元素的下标,初始值为-1,表示刚开始队列为空
队列模拟下标很容易弄错,每个人都有自己的队头队尾的下标表示方法,我习惯让队首队尾直接当做元素的下标来访问
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int q[N];
int main()
{
int hh = 0,tt = -1;
// 入队
q[++tt] = 1;
q[++tt] = 2;
// 出队
while(hh<=tt)
{
int t = q[hh++];
printf("%d ",t);
}
return 0;
}
五、deque
概述
Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间。
所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。
Deque容器和vector容器最大的差异,一在于deque允许使用常数项时间对头端进行元素的插入和删除操作。二在于deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque身上是不会发生的。也因此,deque没有必须要提供所谓的空间保留(reserve)功能.
虽然deque容器也提供了Random Access Iterator,但是它的迭代器并不是普通的指针,其复杂度和vector不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该尽可能的使用vector,而不是deque。对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque.
实现原理
Deque容器是连续的空间,至少逻辑上看来如此,连续现行空间总是令我们联想到array和vector,array无法成长,vector虽可成长,却只能向尾端成长,而且其成长其实是一个假象,事实上(1) 申请更大空间 (2)原数据复制新空间 (3)释放原空间 三步骤,如果不是vector每次配置新的空间时都留有余裕,其成长假象所带来的代价是非常昂贵的。
Deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者尾端。Deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。
既然deque是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。Deque代码的实现远比vector或list都多得多。
Deque采取一块所谓的map(注意,不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。
方法
- 定义和初始化
- 头文件:#include
- 定义:deque name;
- 初始化(常用的):
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
访问方式
- 通过下标进行访问
deque[index]
- 通过迭代器进行访问
deque<typename>::iterator it;
,*it就是要取的值。
常用函数
函数 | 说明 |
---|---|
push_front(elem) | 在容器头部插入一个数据 |
pop_front() | 删除容器第一个数据 |
六、string
- 包含在string头文件中
string是一个类,不用考虑内存分配问题,并且提供了一系列的操作函数
操作 | string/ char* |
---|---|
定义 | string s /char s[105] |
获取第i个字符 | s[i]/ s[i] |
返回长度 | s.size()/ strlen(s) |
读入一行 | getline(cin, s) /gets(s) |
赋值 | s = “hello” /strcpy(s, “hello”) |
拼接 | s += “world”/ strcat(s, “world”) |
比较 | s == “hello”/ strcmp(s, “hello”) |
概述
string和c风格字符串对比:
- Char* 是一个指针,String是一个类,string封装了char* ,管理这个字符串,是一个char* 型的容器。
- String封装了很多实用的成员方法,查找find,拷贝copy,删除delete 替换replace,插入insert
- 不用考虑内存释放和越界,string管理char*所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。
实现原理
类字符串数组
方法
定义和初始化
- 头文件:
#include <string>
- 定义:
string name;
- 初始化(常用的):
string();//创建一个空的字符串 例如: string str;
string(const string& str);//使用一个string对象初始化另一个string对象
string(const char* s);//使用字符串s初始化
string(int n, char c);//使用n个字符c初始化
string = " ";
访问方式
通过下标进行访问> string[index]
常用函数
函数 | 说明 |
---|---|
stoi(string) | 将字符串s转化成整形,s为string类型,即string --> int |
to_string(int) | 将整型转换为字符串 |
stringstream | 可以将数字和字符串任意转换 |
string a=s.substr(0,3); | 返回从pos号开始、长度为len的子串。 |
str.replace(it1, it2, str2) | 把str的迭代器[it1, it2)范围的子串换成str2 |
str.replace(pos, len, str2) | 把str从pos位开始、长度为len的字符串换成str2 |
str.append(string s, int pos, int n) | 把字符串s中从pos开始的n个字符连接到当前字符串结尾 |
七、对组(pair)
包含在utility头文件中,但一般来说,iostream也包含它
方法 | 描述 示例 |
---|---|
make_pair | 构造一个对组 make_pair(1, 2) |
first | 返回第一个元素 p.first O(1) |
second | 返回第二个元素 p.second O(1) |
对pair进行排序时,默认按first进行升序,当first相等时,按second升序
无变量名的初始化:
pair<int, int> (1, 1);//未命名, 值为(1,1)
有变量名的初始化:
pair<int, int> p(1, 1);//创建一个p,值为(1,1)
八、字典(map)
包含在map头文件中
map是一个关联容器,提供一对一的键值对(key-value)映射
方法 | 描述 示例 |
---|---|
clear | 清空 m.clear() O(n) |
insert | 插入键值对 m.insert(make_pair(key, value)) O(logn) |
find | 查找key为x的二元组,并返回其迭代器 m.find(x) O(logn) |
[ ] 操作符 | 返回key映射到的value m[key] O(logn) |
[ ] 操作符 | 对m[key]来进行赋值操作 m[key] = value O(logn) |
对map排序:
1)如果按key来排序,则无需进行任何操作,map自动按照key进行排序
2)如果按value来排序,可以把pair放到其他容器中(如vector),再用sort进行排序(可以写cmp函数或者重载<)
遍历map:
1、使用增强for循环(需要C++11才可以)
map<int, int> m;
for (auto item : m) {
cout << item.first << " " << item.second << endl;
}
2、使用迭代器(不需要C++11)
map<int, int> m;
map<int, int>::iterator it;
for (it = m.begin(); it != m.end(); it++){
cout << it -> first << ' ' << it -> second << endl;
}
九、 优先队列(priority_queue)
包含在queue头文件中
priority_queue可以理解为一个大根二叉堆,堆顶元素为最大值
- 声明一个从大到小取出数值的优先队列
priority_queue<int> que; // 大根堆
- 声明一个从小到大取出数值的优先队列
priority_queue<int, vector<int>, greater<int> > que; // 小根堆
函数
binary_search函数
binary_search(arr, arr + n, x):
在(非递减的)数组中二分查找元素x是否出现.
如果找到元素x则返回true,如果找不到则返回false。
lower_bound函数
lower_bound(arr, arr + n, x):
从数组的下标0到下标n-1二分查找第一个大于或等于x的数字
如果找到则返回该数字的地址,如果找不到则返回下标n的地址。
通过返回的地址减去数组首地址,就可以得到数字在数组中的下标。
int idx = lower_bound(arr, arr + n, x) - arr;
upper_bound函数
upper_bound(arr, arr + n, x):从数组的下标0到下标n-1二分查找第一个大于x的数字
如果找到则返回该数字的地址,如果找不到则返回下标n的地址。
通过返回的地址减去数组首地址,就可以得到数字在数组中的下标。
int idx = upper_bound(arr, arr + n, x) - arr;
__gcd函数
包含在algorithm头文件中
__gcd(a, b):
返回a和b的最大公约数
如果是C语言,就需要自己实现这个函数
int gcd(int a, int b)
{
if (b == 0) return a;
return gcd(b, a % b);
}