一、回顾与引入
在第一天我们了解了蓝桥杯赛制以及暴力解法在比赛中的应用。暴力解法能帮我们在一些题目中拿到部分分数,但想要在比赛中取得更好成绩,还需要掌握更高效的工具和方法。今天我们就来学习 C++ 中非常实用的 STL(Standard Template Library,标准模板库)容器,这一节的基础版本 我会重点讲解常用的:vector、queue、set、stack 这四个容器。这些容器能帮助我们更方便、高效地处理数据,在蓝桥杯的编程题中经常会用到。
注意:本小结不进行真题讲解,因为都是容器的基本用法,大家学会后在之后的做题中慢慢用这些容器去代替数组的用法,stack和queue明天在学习bfs和dfs的时候会用到。
二、vector 容器
(一)概念
在 C++ 里,vector
是标准模板库(STL)中的一个动态数组容器,它能在运行时按需调整大小。可以把它看作是一种增强版的数组,存储同一类型的多个元素。
(二)特点
- 动态大小:
vector
可在运行时动态调整大小,这意味着你能在程序运行时根据需要添加或删除元素,不用像普通数组那样提前确定大小。 - 连续存储:元素在内存中是连续存储的,这样就能像普通数组一样通过下标快速访问元素,时间复杂度为O(1)。
- 自动内存管理:
vector
会自动处理内存的分配和释放,降低了因手动管理内存而产生错误的风险。
(三)定义与初始化
#include <bits/stdc++.h>
using namespace std;
int main() {
// 定义一个存储 int 类型的空 vector
vector<int> v1;
//定义一个存储int类型且长度为100的vector动态数组,与int a[100]等价使用
vector<int>v2(100);
// 定义一个存储 int 类型且初始大小为 5 的 vector,每个元素初始值为 0
vector<int> v3(5, 0);
// 通过初始化列表初始化 vector
vector<int> v4 = {1, 2, 3};
//复制一个与v4一样的动态数组v5
vector<int>v5 (v3);
//如果要得到v4的逆序数组,用反向迭代器
vector<int>v6(v3.rbegin(),v3.rend());
//是将 v7 初始化为一个二维向量,该向量有 n 行 m 列,且所有元素初始值都为 0。
//与int a[n][m]等价使用
vector<vector<int>>v7(n,vector<int>(m,0));
return 0;
}
(四)迭代器(iterators)
begin():返回指向容器中的第一个元素
end():返回指向容器中最后一个元素的后一个位置的迭代器
rbegin():返回容器逆序的第一个元素的反向迭代器
rend():返回容器逆序的最后一个元素的前一个位置的迭代器
(考虑到概念晦涩难懂,下图是我对迭代器的个人理解)
(五)输入输出
#include<bits/stdc++.h>
using namespace std;
int main()
{
vector<int>a(10);
//1 2 3 4 5 6 7 8 9 10
for(int i=0;i<10;i++)cin>>a[i];
//正向输出方法1:
for(int i=0;i<10;i++)cout<<a[i]<<" ";
cout<<endl;
//正向输出方法2:
for(vector<int>::iterator it=a.begin();it!=a.end();it++)cout<<*it<<" ";
cout<<endl;
//正向输出方法3:
for(auto num:a)cout<<num<<" ";
cout<<endl;
//反向输出方法1:
for(int i=9;i>=0;i--)cout<<a[i]<<" ";
cout<<endl;
//反向输出方法2:
for(vector<int>::reverse_iterator it=a.rbegin();it!=a.rend();it++)cout<<*it<<" ";
cout<<endl;
}
注意:正向输出方法3非常方便简洁,建议大家掌握,不过这句话是c++11版本新加的,有些同学使用的devc默认使用环境中还没有这种语法,大家可以点击上方工具——>编译选项,加上以下语句,重新运行就好了。
(六)常用函数
push_back()
:用于在 vector
的末尾添加一个元素。
#include<bits/stdc++.h>
using namespace std;
int main()
{
vector<int>a;
for(int i=1;i<=10;i++)a.push_back(i);
for(auto num:a)cout<<num<<" ";
//1 2 3 4 5 6 7 8 9 10
}
pop_back()
:用于删除 vector
的最后一个元素。
#include<bits/stdc++.h>
using namespace std;
int main()
{
vector<int>a={1,2,3,4,5,6,7,8,9,10};
a.pop_back();
for(auto num:a)cout<<num<<" ";
//1 2 3 4 5 6 7 8 9
}
size():
返回 vector
中元素的数量。
#include<bits/stdc++.h>
using namespace std;
int main()
{
vector<int>a={1,2,3,4,5,6,7,8,9,10};
cout<<a.size();
//10
}
clear():
清空 vector
中的所有元素。
#include<bits/stdc++.h>
using namespace std;
int main()
{
vector<int>a={1,2,3,4,5,6,7,8,9,10};
a.clear();
cout<<a.size();
//0
}
empty():检查vector是否为空
#include<bits/stdc++.h>
using namespace std;
int main()
{
vector<int>a={1,2,3,4,5,6,7,8,9,10};
a.clear();
cout<<a.empty();
//1(输出1相当于true,为空;如果输出0相当于false)
}
(七)习题训练
题目描述:
给定一个未排序的整数数组 nums
和一个整数 k
,找出数组中第 k
大的元素。
思路讲解:
这道题的核心思路是对数组进行排序,然后根据排序后的结果获取第 k
大的元素。
由于我们要找第 k
大的元素,所以可以选择从大到小的排序方式,这样第 k
个位置(索引为 k - 1
)的元素就是我们要找的结果。
在 C++ 中,sort
函数默认是从小到大排序,我们可以通过传入自定义比较函数对象,来实现从大到小的排序。
代码实现:
#include <bits/stdc++.h>
using namespace std;
//自定义排序规则
bool cmp(int a,int b)
{
return a>b;
}
// 找出数组中第 k 大的元素
int findKthLargest(vector<int> nums, int k) {
// 对数组进行排序,从大到小排列
sort(nums.begin(), nums.end(), cmp);
// 第 k 大的元素索引为 k - 1
return nums[k - 1];
}
int main() {
vector<int> nums = {3, 2, 1, 5, 6, 4};
int k = 2;
// 调用函数找出第 k 大的元素
int result = findKthLargest(nums, k);
cout << "第 " << k << " 大的元素是: " << result << endl;
return 0;
}
(八)在蓝桥杯中的应用场景
在处理需要动态存储一组同类型数据,且数据量不确定的题目时很有用。比如统计一系列学生的成绩,事先不知道有多少学生,就可以用 vector 来存储成绩。
三、set容器
(一)概念
在 C++ 里,set
是标准模板库(STL)中的一个关联容器,它以红黑树(一种自平衡的二叉搜索树)作为底层数据结构,用于存储唯一的元素。也就是说,set
中不会存在重复的元素。
(二)特点
- 元素唯一性:
set
中的每个元素都是唯一的,插入重复元素时会被忽略。 - 自动排序:
set
会自动根据元素的值对元素进行排序,默认是升序排列。 - 高效查找:由于采用红黑树作为底层结构,查找、插入和删除操作的时间复杂度都是 O(log n)。
(三)定义与初始化
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s; // 定义一个存储 int 类型的空 set
set<int> s = {3, 1, 2}; // 使用初始化列表初始化 set,会自动排序为 1, 2, 3
return 0;
}
(四)常用函数
insert()
:用于向 set
中插入元素。
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s;
s.insert(10);
return 0;
}
erase()
:用于删除 set
中的元素。
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s = {1, 2, 3};
s.erase(2);
return 0;
}
find():
用于查找 set
中是否存在某个元素,若存在则返回指向该元素的迭代器,否则返回 end()
迭代器。
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s = {1, 2, 3};
auto it = s.find(2);
if (it != s.end()) {
cout << "Element found: " << *it << endl;
} else {
cout << "Element not found" <<endl;
}
return 0;
}
size()
:返回 set
中元素的数量。
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s = {1, 2, 3};
cout << "Set size: " << s.size() <<endl;
return 0;
}
(五)排序规则(重点!!!)
对于 int
类型的 set
,默认是按照元素的值从小到大进行排序。
#include <bits/stdc++.h>
using namespace std;
int main() {
set<int> s = {3, 1, 2};
for(auto num:s)cout<<num<<" ";
//1 2 3
cout << endl;
return 0;
}
也可以自己来制定排序规则
#include <bits/stdc++.h>
using namespace std;
class cmp{
public:
bool operator()(const int& a,const int& b){
return a>b;//从大到小
//return a<b;//从小到大
}
};
int main() {
set<int, cmp> s = {3, 1, 2};
for(auto num:s)cout<<num<<" ";
cout <<endl;
return 0;
}
结构体类型也可以排序:(重点,重点,重点!!)
#include<bits/stdc++.h>
using namespace std;
struct Student{
int id;
int score;
Student(int i, int s) : id(i), score(s) {}//可以直接使用Student(i,s)插入到set中
};
class cmp{
public:
bool operator()(const Student& a,const Student& b){
return a.score>b.score;//按分数从高到低
}
};
int main() {
set<Student, cmp> s;
s.insert(Student(1, 80));
s.insert(Student(2, 90));
s.insert(Student(3, 70));
for(auto num:s)cout<< "ID: " << num.id << ", Score: " << num.score << endl;
return 0;
}
(六)习题训练
题目描述:
给定一个包含重复元素的整数数组,要求使用 set
容器删除数组中的重复元素,并将剩余元素按升序排序,最后输出处理后的元素。
思路讲解:
set
容器的特性是元素唯一且会自动按升序排序,所以可以利用这个特性来解决问题。
遍历给定的整数数组,将数组中的每个元素插入到 set
中。由于 set
会自动处理重复元素,重复插入相同元素时会被忽略。
插入完成后,set
中就存储了原数组中所有不重复且按升序排列的元素。
最后遍历 set
并输出其中的元素。
代码实现:
#include <bits/stdc++.h>
using namespace std;
int main() {
// 给定包含重复元素的数组
vector<int> nums = {3, 1, 2, 2, 4, 3};
// 定义一个 set 容器
set<int> uniqueNums;
// 遍历数组,将元素插入 set
for (int num : nums) {
uniqueNums.insert(num);
}
// 输出 set 中的元素
for (int num : uniqueNums) {
cout << num << " ";
}
cout << endl;
return 0;
}
(七)在蓝桥杯中的应用场景
当需要处理去重或者对元素进行排序存储的情况时很实用。比如统计一篇文章中出现过的不同单词,使用 set 可以自动去重并排序。
四、队列(queue)容器
(一)概念
在 C++ 里,queue
是标准模板库(STL)中的一个容器适配器,它遵循先进先出(FIFO,First In First Out)的原则。这和现实生活中的排队情况类似,先到的人先接受服务,先进入队列的元素也会先被处理。
(二)特点
- FIFO 原则:元素的插入和删除操作遵循先进先出的顺序,插入操作在队尾进行,删除操作在队首进行。
- 封装性:
queue
是对其他容器(如deque
或list
)进行封装,提供了特定的接口,隐藏了底层容器的实现细节。 - 操作受限:只能在队首删除元素,在队尾插入元素,不支持随机访问和在中间插入或删除元素。
(三)定义及初始化
#include <bits/stdc++.h>
using namespace std;
int main() {
// 定义一个存储 int 类型的空队列
queue<int> q;
return 0;
}
(四)常用函数
push()
:用于在队列的尾部插入一个元素。
#include <bits/stdc++.h>
using namespace std;
int main() {
queue<int> q;
q.push(10);
return 0;
}
pop()
:用于删除队列头部的元素。注意,该函数不返回被删除的元素。
#include <bits/stdc++.h>
using namespace std;
int main() {
queue<int> q;
q.push(10);
q.pop();
return 0;
}
front()
:返回队列头部的元素,但不删除该元素:
#include <bits/stdc++.h>
using namespace std;
int main() {
queue<int> q;
q.push(10);
int frontElement = q.front();
cout << "队首元素: " << frontElement << endl;
return 0;
}
back()
:返回队列尾部的元素,但不删除该元素。
#include <bits/stdc++.h>
using namespace std;
int main() {
queue<int> q;
q.push(10);
q.push(20);
int backElement = q.back();
cout << "队尾元素: " << backElement << endl;
return 0;
}
empty()
:判断队列是否为空,如果队列为空则返回 true
,否则返回 false
。
#include <bits/stdc++.h>
using namespace std;
int main() {
queue<int> q;
if (q.empty()) {
cout << "队列为空" << endl;
}
return 0;
}
size()
:返回队列中元素的数量。
#include <bits/stdc++.h>
using namespace std;
int main() {
queue<int> q;
q.push(10);
q.push(20);
int queueSize = q.size();
cout << "队列元素数量: " << queueSize << endl;
return 0;
}
(五)输入输出
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cout << "请输入元素数量: ";
//5
cin >> n;
queue<int> q;
//1 2 3 4 5
for (int i = 0; i < n; ++i) {
int num;
cin >> num;
q.push(num);
}
//1 2 3 4 5
while (!q.empty()) {
cout << q.front() << " ";
q.pop();
}
cout << endl;
return 0;
}
(六)习题训练
题目描述:
有 n
个人围成一圈,从第一个人开始报数,报到 m
的人出列,然后从出列的下一个人重新开始报数,报到 m
的人又出列,如此反复,直到所有人都出列,求最后剩下的人的编号。
思路讲解:
可以使用队列来模拟这个过程。首先将 1
到 n
的编号依次加入队列。
然后开始循环,每次从队列头部取出一个元素,若该元素不是第 m
个报数的,就将其重新加入队列尾部;若该元素是第 m
个报数的,则将其从队列中移除。
不断重复这个过程,直到队列中只剩下一个元素,这个元素就是最后剩下的人的编号。
代码实现:
#include <bits/stdc++.h>
using namespace std;
// 解决约瑟夫环问题
int josephus(int n, int m) {
// 定义队列
queue<int> q;
// 将 1 到 n 的编号依次加入队列
for (int i = 1; i <= n; i++) {
q.push(i);
}
while (q.size() > 1) {
// 报数 m - 1 次
for (int i = 1; i < m; i++) {
// 将队首元素取出并加入队尾
int front = q.front();
q.pop();
q.push(front);
}
// 第 m 个报数的人出列
q.pop();
}
// 返回最后剩下的人的编号
return q.front();
}
int main() {
int n = 5; // 总人数
int m = 3; // 报数到 m 的人出列
// 调用函数求解
int result = josephus(n, m);
cout << "最后剩下的人的编号是: " << result << endl;
return 0;
}
(七)在蓝桥杯中的应用场景
常用于广度优先搜索(BFS)算法中。比如在走迷宫问题里,用来存储每一步可以到达的位置,按照进入的先后顺序依次处理,从而找到从起点到终点的最短路径。
五、堆栈(stack)容器
(一)概念
在 C++ 里,stack
是标准模板库(STL)中的一个容器适配器,它遵循后进先出(LIFO,Last In First Out)的原则。可以把它想象成一摞盘子,最后放上去的盘子会最先被拿走,在 stack
中,最后插入的元素会最先被删除。
(二)特点
- LIFO 原则:这是
stack
最核心的特点,元素的插入(入栈)和删除(出栈)操作都在栈顶进行,保证了后进入的元素先出来。 - 封装性:
stack
基于其他容器(如deque
、vector
或list
)实现,它封装了底层容器的操作,只提供了与栈相关的接口,如push
、pop
等。 - 操作受限:只能对栈顶元素进行操作,不支持随机访问,也不能在栈的中间插入或删除元素。
(三)定义及初始化
#include <bits/stdc++.h>
using namespace std;
int main() {
// 定义一个存储 int 类型的空栈
stack<int> s;
return 0;
}
(四)常用函数
push()
:将一个元素压入栈顶。
#include <bits/stdc++.h>
using namespace std;
int main() {
stack<int> s;
s.push(10);
return 0;
}
pop()
:移除栈顶元素,但不返回该元素。
#include <bits/stdc++.h>
using namespace std;
int main() {
stack<int> s;
s.push(10);
s.pop();
return 0;
}
top()
:返回栈顶元素的引用,但不将其从栈中移除。
#include <bits/stdc++.h>
using namespace std;
int main() {
stack<int> s;
s.push(10);
int topElement = s.top();
cout << "栈顶元素: " << topElement << endl;
return 0;
}
empty()
:判断栈是否为空,若为空则返回 true
,否则返回 false
。
#include <bits/stdc++.h>
using namespace std;
int main() {
stack<int> s;
if (s.empty()) {
cout << "栈为空" << endl;
}
return 0;
}
size()
:返回栈中元素的数量。
#include <bits/stdc++.h>
using namespace std;
int main() {
stack<int> s;
s.push(10);
s.push(20);
int stackSize = s.size();
cout << "栈中元素数量: " << stackSize << endl;
return 0;
}
(五)输入输出
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cout << "请输入元素数量: ";
//5
cin >> n;
stack<int> s;
//1 2 3 4 5
for (int i = 0; i < n; ++i) {
int num;
cin >> num;
s.push(num);
}
//5 4 3 2 1
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
cout << endl;
return 0;
}
(六)习题训练
题目描述:
给定一个字符串,使用栈来反转该字符串。
思路讲解:
利用栈后进先出的特性,将字符串的每个字符依次压入栈中。
然后依次从栈中弹出字符,组成新的字符串,这样就实现了字符串的反转。
代码实现:
#include <bits/stdc++.h>
using namespace std;
// 反转字符串
string reverseString(string s) {
// 定义栈
stack<char> st;
// 将字符串的每个字符依次压入栈中
for (char c : s) {
st.push(c);
}
string reversed = "";
// 依次从栈中弹出字符,组成新的字符串
while (!st.empty()) {
reversed += st.top();
st.pop();
}
return reversed;
}
int main() {
string s = "hello";
// 调用函数反转字符串
string reversed = reverseString(s);
cout << "反转后的字符串是: " << reversed << endl;
return 0;
}
(七)在蓝桥杯中的应用场景
常用于深度优先搜索(DFS)算法中,也可用于表达式求值等场景。比如在计算后缀表达式的值时,可以利用栈来存储操作数,按照运算规则进行计算。
六、总结
今天我们学习了 vector、queue、set、stack 这几种 STL 容器的基本概念、操作以及在蓝桥杯比赛中的应用场景。熟练掌握这些容器,能让我们在处理数据时更加得心应手,提高编程效率。在后续的学习中,我们还会深入探讨更多关于 STL 以及其他有助于在蓝桥杯中取得好成绩的知识内容。 明天我们将学习 BFS(广度优先搜索)和 DFS(深度优先搜索)算法,这也是在算法竞赛中非常重要的解题方法。