写在前面:这篇笔记是由本人原创,兄弟萌如果觉得不错的话,可以点个关注或收藏,方便以后查阅呀。
最近在刷LeetCode上的算法题目,如果想要游刃有余的做这些题目,掌握一门高级计算机编程语言的函数库是必不可少的,C++的标准模板库(STL)是一个不错的选择。为了记录这几天的学习成果,同时方便今后的查阅,遂写下这篇STL的学习笔记。
C++的STL博大精深,内容非常广泛,这篇笔记按照重要程度只对常用的容器和算法做简单而必要的介绍。
C++标准模板库由6大模块组成:分配器、容器、迭代器、算法、仿函数和适配器。
其中,容器container用来存储数据元素,迭代器iterator可以理解为类指针,即封装的指针,算法algorithm通过迭代器操纵容器中的元素。一个简单例子就是:sort(it1, it2)是STL中的一个排序算法,它通过迭代器it1,it2来确定容器的数据范围[it1, it2),从而对容器中的这部分数据进行排序(操纵)。
cin负责输入,cout负责输出。
cin和cout不用设置输入输出数据的格式,可以直接进行输入输出,但是开销比较大,有时候在LeetCode上容易超时,所以推荐使用C语言的标准输入输出函数scanf和printf。
#include < iostream >
using namespace std;
输入1:cin >> var1 >> var2 >> var3 >> ... ;
可以用回车换行作为变量之间的分隔符。
输入2:cin.getline(str, 100)
读取一行,str是缓冲区的起始位置,100是缓冲区的大小,如定义一个缓冲区数组:char str[100];
输入3:getline(cin, str)
读取一行,这里str是一个string字符串对象。
输出:cout << var1 << var2 << "\n" << endl;
输出换行符有两种方式:
方式1:输出 “\n”
方式2:输出 endl
必须指定格式,效率比cin、cout高,推荐使用。
#include
输入:scanf("%d%f%c", &var1, &var2, &var3);
输入一个整数、小数、字符,可以以空格为分隔符,回车换行作为结束符。
输出:printf("%d, %f, %c", var1, var2, var3);
输出一个整数、小数、字符并以逗号为分隔符。
底层是顺序结构——数组,当容量不足时,重新分配一个更大的连续数组。
存储不定长数据;
以邻接表的方式存储图(二维vector)。
#include < vecrot >
using namespace std;
形式1:vector< typename > vi;
可以理解为定义了一个变长数组:vi[size],size可变。
形式2:定义一个vector数组:
vector< typename > a[100];
形式3:定义一个二维vector:
vector< vector< int > > vi;
形式2与形式3的区别在于:前者一维长度固定为100,而后者两个维度均是可变的。
vector< typename >::iterator it;
相当于是一个类指针。
形式1:it ++;
形式2:++ it;
形式1:it --;
形式2:-- it;
it + n
,即迭代器加整数,表示迭代器it所对应位置后n位对应的迭代器,如:vi.begin() + i
指向vi[i]。
it - n
,即迭代器减整数,表示迭代器it所对应位置前n位对应的迭代器。
只有容器vector和string的迭代器才支持这种迭代器加减整数的移位操作。
迭代器不支持it < vi.end()
的比较操作,而只能是:it != vi.end()
,用在循环中判断是否到达容器末尾。
for (vector< int >::iterator it = vi.begin(); it != vi.end(); it ++){pass}
通过下标和迭代器两种方法访问。
下标从0开始,vi[i]
返回第i + 1个元素。
如:
vector< int >::iterator it = vi.begin() + i;
*it
代表第i + 1个元素。
假设vector< int > vi;
返回第一个元素位置对应的迭代器。
返回最后一个元素后一个位置对应的迭代器,通常作为循环的结束标志。
vi.push_back(x)
末尾添加x。
vi.pop_back()
删除末尾元素。
vi.size()
返回元素个数。
vi.clear()
清空所有元素。
vi.insert(it, x)
在迭代器it对应的位置插入x,后面元素均后移一位。
vi.erase(it)
删除迭代器it指向的单个元素。
vi.erase(first, last)
删除前闭后开区间[first, last)内的所有元素。
内部使用红黑树(一种平衡二叉查找树)实现。
特性1:自动去重,元素不重复。
特性2:自动排序,即把它看做是一个序列,这个序列从小到大排列。
自动去重并按升序排列。
#include < set >
using namespace std;
形式1:set< typename > st;
形式2:定义一个set数组:
set< typename > a[100];
形式3:定义一个嵌套集合:
set< vector< int > > st;
set< typename >::iterator it;
相当于是一个类指针。
形式1:it ++;
形式2:++ it;
形式1:it --;
形式2:-- it;
只有容器vector和string的迭代器才支持这种迭代器加减整数的移位操作(因为它们支持下标访问元素),set的迭代器不支持这种操作。
迭代器不支持it < st.end()
的比较操作,而只能是:it != st.end()
,用在循环中判断是否到达容器末尾。
for (set< int >::iterator it = st.begin(); it != st.end(); it ++){pass}
只能通过迭代器访问。
如:
set< int >::iterator it = st.begin() + i;
*it
代表第i + 1个元素。
假设set< int > st;
返回第一个元素位置对应的迭代器。
返回最后一个元素后一个位置对应的迭代器,通常作为循环的结束标志。
st.size()
返回元素个数。
st.clear()
清空所有元素。
st.insert(x)
插入元素x,自动去重和递增排序。
时间复杂度:O(logN)。
st.find(value)
查找value,返回对应的迭代器。
时间复杂度:O(logN)。
st.erase(it)
删除迭代器it指向的单个元素。
通常与st.find(x)搭配使用,如:
st.erase(st.find(x))
删除元素x。
st.erase(value)
删除指定元素值value。
st.erase(first, last)
删除前闭后开区间[first, last)内的所有元素。
底层是顺序结构——字符数组。
存储字符串。
#include < string >
using namespace std;
注意:与#include
不一样。
形式1:string str;
可以理解为定义了一个字符数组:char str[size],size可变。
形式2:初始化字符串:
string str = "abcd";
cin >> str;
回车换行标志输入结束。
cout << str;
string::iterator it;
相当于是一个类指针。
形式1:it ++;
形式2:++ it;
形式1:it --;
形式2:-- it;
it + n
,即迭代器加整数,表示迭代器it所对应位置后n位对应的迭代器,如:str.begin() + i
指向str[i]。
it - n
,即迭代器减整数,表示迭代器it所对应位置前n位对应的迭代器。
只有容器vector和string的迭代器才支持这种迭代器加减整数的移位操作。
迭代器不支持it < str.end()
的比较操作,而只能是:it != str.end()
,用在循环中判断是否到达容器末尾。
for (string::iterator it = str.begin(); it != str.end(); it ++){pass}
通过下标和迭代器两种方法访问。
下标从0开始,str[i]
返回第i + 1个字符。
如:
str::iterator it = str.begin() + i;
*it
代表第i + 1个字符。
假设string str;
返回第一个元素位置对应的迭代器。
返回最后一个元素后一个位置对应的迭代器,通常作为循环的结束标志。
str.c_str();
返回字符串对应的C字符数组,以’\0’结尾,对应的输出格式符为%s。
字符串支持有以下运算符:
连接运算符:
str1 = str2 + str3;
连接字符串
str1 += str2;
连接字符串并赋值
比较运算符:比较规则:字典序
str1 == str2
str1 != str2
str1 < str2
str1 <= str2
str1 > str2
str1 >= str2
返回元素个数。
str.clear()
清空所有元素。
str1.insert(int pos, str2)
从位置pos处开始插入字符串str2。
str.insert(it1, it2, it3)
从迭代器it1指定的位置开始插入[it2, it3)确定的字符串。
str.erase(it)
删除迭代器it指向的单个字符。
str.erase(first, last)
删除前闭后开区间[first, last)内的所有元素。
str.erase(pos, length)
从位置pos处开始删除length个字符。
返回一个子串,长度为len。
模式匹配方法
时间复杂度:O(m * n),m、n是两个字符串的长度。
str1.find(str2)
若存在,返回第一次出现的位置。
若不存在,返回常量string::npos,即-1。
str1.find(str2, pos)
从位置pos处开始查找str2。
若存在,返回第一次出现的(绝对)位置。
若不存在,返回常量string::npos,即-1。
会改变源字符串。
str1.replace(int pos, len, str2)
将从位置pos处开始的len个字符替换为字符串str2。
str1.replace(int pos, len, str2)
将区间[it1, it2)内的字符替换为字符串str2。
内部基于pair对象(后面会介绍,可以简单为由键值对组成的二元结构体)和红黑树(一种平衡二叉查找树)实现。
特性1:键key唯一。
特性2:按键key自动去重。
特性3:自动排序,即把它看做是一个序列,这个序列从小到大排列。
建立字符串与整数间的映射;
判断大整数或其他数据是否存在。
#include < map >
using namespace std;
形式1:map< typename1, typename2 > mp;
如:将set容器映射为字符串,map
map< typename1, typename2 >::iterator it;
相当于是一个类指针。
形式1:it ++;
形式2:++ it;
形式1:it --;
形式2:-- it;
只有容器vector和string的迭代器才支持这种迭代器加减整数的移位操作(因为它们支持下标访问元素),map的迭代器不支持这种操作。
迭代器不支持it < mp.end()
的比较操作,而只能是:it != mp.end()
,用在循环中判断是否到达容器末尾。
for (map< string, int >::iterator it = mp.begin(); it != mp.end(); it ++){pass}
通过下标和迭代器两种方法访问。
这里下标是键key,如:mp[‘c’]代表对应值value = 20。
如:
map< int >::iterator it = mp.begin() + i;
迭代器it指向第i + 1个键值对,
it -> first 代表键key,
it -> second 代表值value。
假设map< string, int > mp;
返回第一个键值对对应的迭代器。
返回最后一个键值对后一个位置对应的迭代器,通常作为循环的结束标志。
mp.size()
返回元素个数。
mp.clear()
清空所有元素。
mp['c'] = 20;
若存在键key = ‘c’,则修改其值为20;
若不存在,则插入键值对(‘c’, 20),并递增排序。
插入pair对象对应的键值对。
pair对象见后面的介绍。
mp.find(key)
查找键key,返回对应的迭代器,而不是对应值value。
时间复杂度:O(logN)。
mp.erase(it)
删除迭代器it指向的单个键值对。
通常与mp.find(key)搭配使用,如:
mp.erase(mp.find(key))
删除key对应的键值对。
mp.erase(first, last)
删除前闭后开区间[first, last)内的所有键值对。
特性:先进先出。
广度优先搜索。
#include < queue >
using namespace std;
queue< typename > q;
因为queue限制只能操作两端元素,所以没有迭代器用来遍历整个队列元素。
只能通过API访问。
q.front()
返回队首元素(出口)。
q.back()
返回队尾元素(入口)。
假设queue< int > q;
q.front()
返回队首元素(出口)。
使用前最好用q.empty()
判断是否为空。
q.back()
返回队尾元素(入口)。
使用前最好用q.empty()
判断是否为空。
q.push(x)
入队,队尾插入x。
q.pop()
出队,删除队首元素。
q.size()
返回元素个数。
q.empty()
判断是否为空。
首尾均可以插入和删除,API跟queue类似,省略。
底层是用堆实现。
特性:优先级最大元素置于队首,队尾负责入队(插入)。
贪心问题;
优化迪杰斯特拉算法。
#include < queue >
using namespace std;
形式1:priority_queue< typename > q;
形式2:见 8. 自定义优先级。
因为priority_queue限制只能操作两端元素,所以没有迭代器用来遍历整个优先队列元素。
只能通过API访问队首元素。
``q.top() 返回队首优先级最高的元素。
假设priority_queue< int > q;
q.top()
返回队首优先级最高的元素(出口)。
使用前最好用q.empty()判断是否为空。
q.push(x)
入队,队尾插入x。
q.pop()
出队,删除队首元素。
q.size()
返回元素个数。
q.empty()
判断是否为空。
默认数据大的优先级高。即:
priority_queue< int > q;
等价于priority_queue< int, vector
其中,int表示队列元素是整型,vector< int >
表示底层承载数据结构为vector容器,less< int >
是一个比较类,表示数据大的优先级高。
less< int >
类可以理解为是一个比较函数:less(int a, int b)
,它接受两个参数a、b,并且底层使用小于号<比较参数a和b,即a < b
,这样当比较结果为true时,返回右值b(较大值),当比较结果为false时,返回左值a(仍然是较大值),即:只返回较大值,所以当优先队列使用less< int >
作为参数时,认为数据大的优先级高。
greater< int >
类可以理解为是一个比较函数:greater(int a, int b)
,它接受两个参数a、b,并且底层使用大于号>比较参数a和b,即a > b
,这样当比较结果为true时,返回右值b(较小值),当比较结果为false时,返回左值a(仍然是较小值),即:只返回较小值,所以当优先队列使用greater< int >
作为参数时,认为数据小的优先级高。
可以理解为:无论内置less类、greater类还是自定义cmp类,它们都接受参数a、b,并且用大于号或小于号比较它们。三个类均是:当比较结果为true时,就返回右值放入vector< int >中,比较结果为false时,就返回左值放入vector< int >中。
此时可以通过返回值是两者中的大值还是小值判断priority_queue队列中是认为数据大的优先级高还是数据小的优先级高。
如:
struct fruit{
string name;
int price;
//定义友元函数,无论定义数字小的优先级高还是数字大的优先级高,只能重载小于号<,并且返回值类型为bool。
friend bool operator < (const fruit &f1, const fruit &f2){//当结构体数据比较大时,可以使用引用节省时间。
return f1.price < f2.price;//按照8.2.3 总结,始终返回较大值
或
return f1.price > f2.price;//按照8.2.3 总结,始终返回较小值
}
};
定义:priority_queue< fruit > q;
或
priority_queue< fruit, vector
struct cmp{
//重载小括号(),可以理解为遇到cmp(a, b)时调用此函数。
bool operator () (const fruit &f1, const fruit &f2){
return f1.price < f2.price;//按照8.2.3 总结,始终返回较大值
或
return f1.price > f2.price;//按照8.2.3 总结,始终返回较小值
}
定义:priority_queue< fruit, vector
底层是顺序结构——数组。
特性:后进先出。
模拟递归。
#include < stack >
using namespace std;
stack< typename > st;
因为stack的特性是后进先出,所以没有迭代器用来遍历整个容器元素。
只能通过API访问栈顶元素。
st.top()
返回栈顶元素。
假设stack< int > st;
返回栈顶元素。
st.push(x)
栈顶添加x。
st.pop()
删除栈顶元素。
st.size()
返回元素个数。
st.empty()
判断是否为空。
可以理解为是一个内部有两个元素的结构体,即:
struct pair{
typename1 first;
typename2 second;
};
代替二元结构体;
作为map的键值对进行插入,见 六、7.5.2
如:
mp.insert(make_pair("hello", 5))
或
mp.insert(pair< string, int > ("hello", 5))
。
#include < utility >
using namespace std;
或
#include < map >
using namespace std;
因为map内部实现涉及了pair,所以添加map头文件会自动添加utility头文件。
形式1:pair< typename1, typename2 > p;
形式2:初始化 pair< string, int > p("hello", 5);
形式3:创建临时对象,即省去对象名 pair< string, int > ("hello", 5)
形式4:使用函数创建临时对象 make_pair("hello", 5)
正常结构体访问方法。
p.first
访问第一个字段;
p.second
访问第二个字段。
假设pair< typename1, typename2 > p1, p2;
比较运算符:
比较规则:先比较第一个字段first,当first字段相等时,再比较第二个字段second。
p1 == p2
p1 != p2
p1 < p2
p1 <= p2
p1 > p2
p1 >= p2
C++STL中许多常用函数都封装在algorithm库中。
#include < algorithm >
using namespace std;
max(x, y)
返回两个参数中的较大值。
只能接受两个参数,如果想要比较三个数中的最大值,可以如下:max(x, max(y, z))
min(x, y)
返回两个参数中的较小值。
只能接受两个参数,如果想要比较三个数中的最小值,可以如下:
min(x, min(y, z))
abs(x)
返回整数x的绝对值。
只能接受整数参数,如果想要返回小数的绝对值,可以如下:
#include < math.h>
fabs(x),x可以是小数。
swap(x, y)
交换x、y的值。
reverse(it1, it2)
将容器或数组在区间[it1, it2)之间的元素值反转。
当是数组时,it1、it2是数组指针;
当是容器时,it1、it2是容器迭代器;
next_permutation(it1, it2)
求出区间[it1, it2)序列在全排列下的下一个序列,并且用这个新序列更新原序列[it1, it2),如果当前序列不是全排列的最后一个序列,返回true,否则,返回false。
如:由1、2、3组成的全排列是:
123
132
213
231
312
321
int a[10] = {1, 2, 3};
do{
printf("%d%d%d\n", a[0], a[1], a[2]);
}while(next_permutation(a, a + 3));
fill(it1, it2, value)
将数组(指针)或容器(迭代器)区间[it1, it2)内的元素设置为value。
规避了经典快排可能出现的会导致实际复杂度退化到O(n^2)的极端情况。
sort(it1, it2, 比较函数名)
比较函数是可选项,默认为递增排序。
排序的前提是元素具有可比性,结构体本身没有大小关系,因此需要自定义比较规则。
//返回值必须为bool型
bool cmp(int x, int y){
return x < y;
}
可以理解为:如果返回为true,x和y相对位置不变,x放在y前面,如果返回为false,x和y交换位置,y放在x前面,即:始终是较小值放在前面,较大值放在后面,所以从小到大排序。
//返回值必须为bool型
bool cmp(int x, int y){
return x > y;
}
可以理解为:如果返回为true,x和y相对位置不变,x放在y前面,如果返回为false,x和y交换位置,y放在x前面,即:始终是较大值放在前面,较小值放在后面,所以从大到小排序。
注意跟priority_queue中自定义优先级规则的区别:自定义优先级时,认为cmp函数如果返回true,就返回右值y,而这里认为cmp函数如果返回true,就保持x和y相对位置不变。
假设:
struct node{
int x;
int y;
}
自定义结构体比较函数:
bool cmp(node a, node b){
if (a.x != b.x) return a.x > b.x;
else return a.y < b.y;
}
即:先按x从大到小排序,假设x相等,再按y从小到大排序。
1:标准容器中,只有vector、string和deque可以使用sort()排序,因为set、map本身有序,故不允许使用sort()排序。
2:字符串string本身已经重载了>或<,故可以直接对字符串数组进行排序,另外可以自定义按照字符串长度排序的新规则。
二者均针对有序数组(返回指针)或有序容器(返回迭代器)进行操作。
lower_bound(first, last, value)
返回[first, last)内第一个大于等于value的元素的位置(指针或迭代器)。
upper_bound(first, last, value)
返回[first, last)内第一个大于value的元素的位置(指针或迭代器)。
情形1:当区间[first, last)内不存在查找值value时,lower_bound()和upper_bound()均返回插入该元素的位置指针或迭代器。如下图所示:
情形2:当区间[first, last)内存在若干个查找值value时,lower_bound()返回第一个value元素的位置指针或迭代器,upper_bound()返回最后一个value元素的位置指针或迭代器。如下图所示:
lower_bound()和upper_bound()均返回的是指针或迭代器,如果想要获取对应的下标,只需令返回值减去数组的首地址或容器的首个元素的迭代器即可。即:
[lower_bound() - a, upper_bound() - a)