C++ STL库详解
第零章 我们为什么要学STL库?
STL库(英文全称:Standard Template Library,标准模板库),STL库提供了很多模板以供用户使用,从而精简代码,以提升代码的可读性。
以快速排序为例:
不使用STL库的代码如下:
void quickSort(int arr[], int low, int high) {
if (low < high) {
int i = low, j = high;
int pivot = arr[low]; // 基准元素
while (i < j) {
while (i < j && arr[j] >= pivot) j--;
if (i < j) arr[i++] = arr[j];
while (i < j && arr[i] <= pivot) i++;
if (i < j) arr[j--] = arr[i];
}
arr[i] = pivot;
quickSort(arr, low, i - 1); // 递归排序左子数组
quickSort(arr, i + 1, high); // 递归排序右子数组
}
看起来是不是超级复杂?但是!!!使用STL库的话,就能把代码简写成这样了:
sort(arr.begin(),arr.end());
一行就搞定了!
这就是STL库的魅力!那么,接下来让我们进入STL的奇妙世界吧!
第一章 vector
1.1 vector是什么
vector是一个数组,和C风格的数组int arr[]不同的是,它会动态分配内存空间,以达到最大化利用内存空间的效果。
如:在算法竞赛中有时候会遇到需要开辟一个1≤m,n≤106,1≤m×n≤106的二维数组的场景,这个时候用 int arr[100010][100010]会导致栈空间严重浪费(MLE),而使用vector数组就不会出现这种问题(vector<vector<int>> arr(m+10,vector<int> (n+10)))
另外,vector数据存储在堆上,因此不会发生栈空间溢出的情况,但需注意:在构造二维vector对象的时候,因为vector对象本身的信息仍在栈上,仍有可能发生栈溢出。
1.2 如何构造一个vector对象
vector创建对象的基本形式:
vector<数据类型> 函数名;
vector对象的函数构造有以下几种形式:
vector<int> v1; //创建一个空的vector对象
vector<int> v2(a); //创建一个长度为a的vector对象,默认全部赋值为0
vector<int> v3(a,b); //创建一个长度为a的vector对象,并全部赋值为b
//另外,还有以下几种进阶形式
vector<vector<int> > v4(a,vector<int>(b)) //创建一个长为a、宽为b的二维vector对象
vector<vector<int> > v5(a,vector<int>(b,c)) //创建一个长为a、宽为b的二维vector对象,并全部赋值为c
1.3 vector对象的操作
以下所写代码已默认创建了vector对象
vector<int> vec;
1.3.1 push_back()
基本格式:vec.push_back(a);
用法讲解:用于在vector数组的尾部插入一个数a
时间复杂度: O ( 1 ) O(1) O(1)
1.3.2 pop_back()
基本格式:vec.pop_back();
用法讲解:删除vector的尾部元素
时间复杂度: O ( 1 ) O(1) O(1)
1.3.3 begin()和end()
基本格式:vec.begin(),vec.end()
用法讲解:begin()返回值为第一个元素的地址,end()返回值为最后一个元素的下一个区域的地址
1.3.4 insert()
基本格式:
-
vec.insert(pos,value)(pos为地址,value为值) -
vec.insert(pos,count,value)(pos为地址,count和value为值) -
vec.insert(pos,first,last)(pos、first、last均为地址)
用法讲解:
-
从pos处插入元素value
-
从pos处插入count个元素value
-
从pos处插入从first到last的元素(左闭右开)
举例
#include<vector>
#include<iostream>
int main()
{
std::vector<int> vec1;
std::vector<int> vec2;
for(int i=0;i<10;i++)
{
vec1.push_back(i+1);
}
for(int i=0;i<10;i++)
{
vec2.push_back(i+1);
}
vec1.insert(vec1.begin()+1,11);
vec1.insert(vec1.begin()+3,12);
vec1.insert(vec1.begin(),5,13);
vec1.insert(vec1.begin(),vec2.begin(),vec2.end());
for(int i=0;i<vec1.size();i++)
{
std::cout << vec1[i] << ' ';
}
return 0;
}
它的运行结果为
1 2 3 4 5 6 7 8 9 10 13 13 13 13 13 1 11 2 12 3 4 5 6 7 8 9 10
代码分析:
首先,vec1和vec2都是从1到10这10个元素
然后,向vec1的第1个元素和第2个元素之间插入11
再然后,向vec1的第3个元素和第4个元素之间插入12
接着,向vec1的最前面插入5个13
最后,向 vec1的最前面依次插入vec2的所有元素
时间复杂度:
O
(
n
)
O(n)
O(n)
1.3.5 erase()
基本格式:
vec.erase(pos)vec.erase(first, last)
用法讲解:
- 删除pos处的值
- 删除从first到last之间的值(左闭右开)
时间复杂度: O ( n ) O(n) O(n)
1.3.6 resize()
基本格式:vec.resize(n,value)
用法讲解:修改vector的长度。如果是缩短,则删除多余的值;如果是扩大,且指定了默认值,则新元素均为默认值value(旧元素不变)
时间复杂度:
O
(
n
)
O(n)
O(n)
1.3.7 clear()
基本格式:vec.clear()
用法讲解:清空vec中的所有元素
时间复杂度:
O
(
n
)
O(n)
O(n)
1.3.8 empty()
基本格式:vec.empty()
用法讲解:如果vec里面是空的,返回true,否则返回false。
时间复杂度:
O
(
1
)
O(1)
O(1)
第二章 stack
栈(Stack)是一个后入先出的数据结构(LIFO),STL库提供了封装好的模拟栈的数据结构
2.1 创建栈
栈的封装在<stack>头文件上,创建栈的代码如下:
#include<stack>
int main()
{
std::stack<int> stk;
}
格式如下:
stack<数据类型> 栈名
2.2 栈的操作
2.2.1 push()
push()用于向栈顶推入元素
例如:stk.push(1); stk.push(2);
2.2.2 pop()
pop()用于移除栈顶元素
用法:stk.pop();
2.2.3 top()
功能:查看栈顶元素
用法:
#include<stack>
#include<iostream>
int main()
{
std::stack<int> stk;
stk.push(1); //推入1
stk.push(2); //推入2
std::cout << "Top element: "<< stk.top() << std::endl; //输出2
stk.pop(); //移除2
std::cout << "Top element after pop: "<< stk.top() << std::endl; //此时栈顶元素为1,所以输出1
return 0;
}
2.2.4 empty()
功能:查询栈是否为空,如果栈为空,返回true,否则返回false。
用法:
#include<stack>
int main()
{
std::stack<int> stk;
stk.push(1); //推入1
stk.push(2); //推入2
stk.pop(); //移除2
if(stk.empty())
{
// Do something ...
// 这里的代码实际上并不会被执行,因为此时栈仍有一个元素,stk.empty()返回false
}
stk.pop();
if(stk.empty())
{
// Do something ...
//这里的代码会被执行,因为此时栈为空,stk.empty()返回true
}
return 0;
}
2.2.5 size()
功能:返回栈内元素的个数
用法:略。
2.3 其它的注意事项
stack只能入栈、出栈和访问栈顶元素,不能做其他的事情,比如int a = stk[1];这种是不被允许的。
第三章 queue和priority_queue
3.1 队列
队列是一个先入先出的数据结构,它的push()和pop()操作和栈一样,这里不再赘述。
队列包含在头文件<queue>中
最主要的是队列没有top(),取而代之的是front(),也就是返回队首元素。
3.2 优先队列
优先队列与队列不同的是,优先队列会自动对队列元素进行排序,且默认为大顶堆(即最大的元素排在队首)
下面看一段代码示例:
#include<queue>
#include<iostream>
int main()
{
std::priority_queue<int> pq;
pq.push(1); //把1推入pq
pq.push(3); //把3推入pq,3>1,所以3在前面
pq.push(2); //把2推入pq,2<3,所以2在3后面,在1前面
std::cout << pq.top() << std::endl; //输出最大的元素3
return 0;
}
第四章 算法库<algorithm>
4.1 swap(a,b)
用法介绍:交换两个变量的值
代码示例:
#include<algorithm>
#include<iostream>
int main()
{
int a = 10, b = 20;
std::swap(a,b); //交换a和b的值
std::cout << a << ' ' << b; //输出20 10
}
4.2 sort(arr.begin(),arr.end())
用法介绍:快速排序数组(原地排序)
代码示例:
#include<vector>
#include<algorithm>
#include<iostream>
int main()
{
std::vector<int> vec={4,2,3,1,5}; //此时vec里面的元素顺序为[4,2,3,1,5]
std::sort(vec.begin(),vec.end()); //按照从小到大的顺序将vec进行排序
for(int i=0;i<5;i++)
{
std::cout << vec[i] << " "; //输出结果1 2 3 4 5
}
return 0;
}
进阶用法介绍:
sort(arr.begin(),arr.end(),greater<int>); //从大到小进行排序
时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn)(sort内置算法为快速排序算法)
4.3 __gcd(a,b)和__lcm(a,b)
功能介绍:__gcd(a,b)求最大公约数,__lcm(a,b)求最小公倍数
代码示例:
#include<algorithm>
#include<iostream>
int main()
{
int a=3, b=8;
std::cout <<"gcd(a,b)= " << std::__gcd(a,b);
return 0;
}
(因为lcm函数C++11标准好像不支持,所以此处最好还是手写一个lcm函数)
lcm(最小公倍数)函数实现如下所示:
#include<algorithm>
int lcm(int a,int b)
{
return a*b/std::__gcd(a,b);
}
后记
由于作者本人能力有限,无法详尽地写出所有的STL库的用法,本篇文章仅提供算法竞赛中常用的STL库用法的讲解
本篇文章所述代码均为C++11标准下的代码,且并未使用全局声明命名空间std(using namespace std;)和万能头文件(bits/stdc++.h),但在算法竞赛中一般可以使用,请读者自行选择是否使用。
浙公网安备 33010602011771号