1.优缺点总述
STL中各容器对比图
各类线性数据结构优缺点
1.数组
1.优点
1.简单,容易理解
2.访问快捷,只需要用下标就可以
3.有某些应用场景直接对应,例如二维数组对应平面
2.缺点
删除和插入数据非常耗时
2.链表
1.优点
插入和删除数据很便捷
2.缺点
定位数据比较耗时,要从链表头开始沿着指针一个一个走。
3.队列
有自己的使用场景,无优缺点
3.栈
有自己的使用场景,无优缺点
各类非线性数据结构优缺点
1.二叉树
2.哈希表
2.数组和高精度
1.大数组的使用
在大赛中经常用大数组,注意以下情况
1.建议不要用malloc()动态分配,因为动态分配需要多写代码而且容易出错。
2.大数组应该定义成全局静态数组,而且不需要初始化为零,因为全局变量在初始化时自动赋值为零。因为定义到函数内部,可能会导致栈溢出。
2.对各种存储域的补充
1.栈
存储局部变量和函数调用信息等,容量较小
2.堆
由malloc,calloc,realloc等分配,需要手动释放。
3.数据段
存放已经初始化的全局变量与静态变量
4.bss段
存放未初始化的全局变量或静态变量。这些变量会被自动初始化0或NULL。
3.高精度算法
在计算机科学中,标准的数据类型如整型(int)、长整型(long)(long long)等都有其表示范围的限制。当需要处理比这些类型所能表示的范围更大的数字时,就需要使用高精度算法。
高精度算法这个过程类似于我们在学校里学习的手工算数,将数的每一位都存入数组,从最低位开始逐位处理,并处理进位。
除了C语言的静态数组,还可以用vector来定义大数组
1.代码演示高精度加法
#include<iostream>
using namespace std;
char a[1005],b[1005];//1005是防止有进位
string fun(string as, string bs)
{//注意不能写到主函数内,要写成子函数,因为a和b目前是指针类型,不能使用sizestring ss;//返回值int lmax;//两数组最长长度int lena = as.size(), lenb = bs.size();//长度for(int i=0;i<lena;i++){a[lena - i - 1] = as[i] - '0';}for (int i = 0; i < lenb; i++){b[lenb - i - 1] = bs[i] - '0';}lmax = lena > lenb ? lena:lenb;for (int i = 0; i < lmax; i++){//这样就不用区分那个数字较大了a[i] += b[i];a[i + 1] += a[i] / 10;a[i] %= 10;}if (a[lmax])//如果非零,则有进位{lmax++;}for (int i = lmax - 1; i >= 0; i--){ss += a[i] + '0';//将数字转化成字符,并且反转}return ss;
}
int main()
{string as, bs;cin >> as;cin >> bs;cout << fun(as,bs) << endl;return 0;
}
2.代码演示高精度减法
3.STL概述
1.string库
语法详情见STL-string容器
案例1
1.题目
问题描述
在烬寂海中居住着某种智慧生物。它们的文明发展程度相当于地球上的中世纪,但是它们拥有强大的科技与魔法。
一天,王国的法师得到了一段古老的魔法咒文,咒文中似乎隐藏着巨大的能量,但是咒文中有很多相似的字符串片段,法师们相信这些片段与魔法的启动有关。
现在,国王决定招募聪明的你,使用你的技术能力来帮助法师们解开这个谜团。
现在给你一个字符串 SS(主串),还有若干个模式串 PP。你需要统计每一个模式串在主串中出现的次数。
输入格式
第一行:一个字符串 SS,表示主串,只包含小写英文字母。
第二行:一个整数 nn,表示有 nn 个模式串。
接下来的 nn 行:每行一个字符串,代表一个模式串 PP,只包含小写英文字母。
输出格式
nn 行,每行一个整数,表示对应模式串在主串中出现的次数。
样例输入
bluemooninthedarkmoon 3 moon blue dark
样例输出
2 1 1
测评数据规模
主串的长度:∣S∣≤1×105∣S∣≤1×105。
模式串的数量:1≤n≤1001≤n≤100。
模式串的长度:∣P∣≤1000∣P∣≤1000。
运行限制
语言 最大运行时间 最大运行内存 C++ 3s 128M C 3s 128M Java 5s 128M Python3 7s 128M PyPy3 7s 128M Go 7s 128M JavaScript 7s 128M
2.作答(第一版)
#include <bits/stdc++.h>
using namespace std;
int main()
{// 请在此输入您的代码string s;cin >> s;int n;cin >> n;int sumz[200]={0};for (int i = 0; i < n; i++){string p;int posint = 0;int sum = 0;cin >> p;while (s.find(p,posint) != -1){sum++;posint = s.find(p,posint);posint += p.size();}sumz[i] = sum;}for (int i = 0; i < n; i++){cout << sumz[i] << endl;}return 0;
}
这一版只能通过百分之八十的案例,因为在第二十一行代码没有考虑到S为“aaaa”,P为“aa”,这样aa根据posint+=p.size();的话只能计数两次,应该改为posint++;这样就可以计数三次。
第二个问题是使用了静态数组sumz,浪费了空间,应该使用动态数组vector来节省空间。
3.作答(第二版)
#include <iostream>
#include<string>
#include<vector>
using namespace std;
int main()
{// 请在此输入您的代码string s;cin >> s;int n;cin >> n;vector<int> sumz;for (int i = 0; i < n; i++){string p;int posint = 0;int sum = 0;cin >> p;while (s.find(p,posint) != -1){sum++;posint = s.find(p,posint);posint ++;}sumz.push_back(sum);}for (int i = 0; i < n; i++){cout << sumz[i] << endl;}return 0;
}
4.官方题解
#include <iostream>
#include<string>
#include<vector>
using namespace std;
int main()
{string s;int n;vector<int> sum;cin >> s >> n;int n1 = n;while (n--){string p;cin >> p;int cnt = 0;for (int i = 0; i < s.length() - p.length() + 1; i++){if (p == s.substr(i, p.length()))cnt++;}sum.push_back(cnt);}for (int i = 0; i < n1; i++){cout << sum[i] << endl;}return 0;
}
使用了暴力算法
i < s.length() - p.length() + 1
的含义
这里的条件是
i < s.length() - p.length() + 1
。
s.length()
是主字符串s
的长度,p.length()
是子字符串p
的长度。
s.length() - p.length() + 1
表示的是主字符串中最后一个可能与子字符串匹配的起始位置。
4.比较
时间复杂度比较
官方题解:
- 外层循环执行
n
次(每个子字符串p
都需要处理一次)。- 内层循环的次数为
s.length() - p.length() + 1
,每次调用substr
提取子串并进行比较。- 假设主字符串
s
的长度为m
,子字符串p
的平均长度为k
,则内层循环的时间复杂度为 O(m⋅k)O(m⋅k)。- 总时间复杂度为:O(n⋅m⋅k)O(n⋅m⋅k)。
第二版:
- 外层循环同样执行
n
次。- 内层使用
find
函数查找子字符串的位置。find
函数在最坏情况下需要扫描整个主字符串s
,其时间复杂度为 O(m)O(m)。- 如果子字符串匹配多次,
find
会被调用多次,但总体上不会超过m
次。- 总时间复杂度为:O(n⋅m)O(n⋅m)。
结果
- 官方题解的时间复杂度为 O(n⋅m⋅k)O(n⋅m⋅k)。
- 第二版的时间复杂度为 O(n⋅m)O(n⋅m)。
因此,第二段代码的时间复杂度更低,效率更高。
案例2
1.题目
元音大写
问题描述
输入一个由小写英文字母组成的字符串,请将其中的元音字母 (a,e,i,o,u)(a,e,i,o,u) 转换成大写,其它字母仍然保持小写。
输入格式
输入一行包含一个字符串。
输出格式
输出转换后的字符串。
样例输入
lanqiao
样例输出
lAnqIAO
评测用例规模与约定
对于所有评测用例,字符串的长度不超过 100100。
2.作答
#include <bits/stdc++.h>
using namespace std;
int main()
{// 请在此输入您的代码string s;cin >> s;for (int i = 0; i < s.size(); i++){if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u'){s[i] -= 32;}}cout << s << endl;return 0;
}
3.解答
题目设计数据限制较小直接用暴力即可。
2.迭代器
1.auto
补充:auto可以自动推导数据类型。
auto
关键字是 C++11 引入的一个非常有用的特性,它允许编译器自动推导变量的类型。使用auto
可以简化代码,尤其是在处理复杂类型(如迭代器、模板实例化类型等)时。下面详细介绍auto
的用法及其适用场景。基本用法
当你声明一个变量并初始化它时,可以使用
auto
来让编译器根据初始化表达式的类型自动推导出变量的类型。示例 1:基础类型
auto x = 5; // x 是 int 类型 auto y = 3.14; // y 是 double 类型 auto z = 'a'; // z 是 char 类型 auto str = "Hello"; // str 是 const char* 类型
示例 2:复杂类型
std::vector<int> vec = {1, 2, 3, 4}; auto it = vec.begin(); // it 是 std::vector<int>::iterator 类型
auto
结合引用和指针
auto
还可以与引用和指针结合使用,但需要注意的是,auto
默认不会推导出引用或指针类型。如果你希望得到引用或指针类型的变量,需要显式指定。示例 3:引用
int a = 10; auto b = a; // b 是 int 类型,值为 10 auto& c = a; // c 是 int& 类型,它是 a 的引用 c = 20; // 修改 c 实际上修改了 a std::cout << a << std::endl; // 输出 20
示例 4:指针
cpp
深色版本
int x = 10; auto p = &x; // p 是 int* 类型,指向 x *p = 20; // 修改 x 的值 std::cout << x << std::endl; // 输出 20
auto
结合常量当初始化表达式是一个常量时,
auto
推导出的类型也会是一个常量类型。示例 5:常量
cpp
深色版本
const int ci = 10; auto aci = ci; // aci 是 int 类型,而不是 const int const auto caci = ci; // caci 是 const int 类型
auto
结合模板在模板编程中,
auto
可以极大地简化代码,特别是在定义模板函数返回类型或模板参数类型时。示例 6:模板中的
auto
cpp
深色版本
template <typename T> auto get_value(T t) -> decltype(t + 1) {return t + 1; }
auto
在范围循环中的应用
auto
在范围循环中特别有用,它可以让你轻松遍历容器中的元素,而无需显式声明迭代器类型。示例 7:范围循环
cpp
深色版本
std::vector<int> vec = {1, 2, 3, 4, 5};// 使用 auto 遍历 vector 中的每个元素 for (auto elem : vec) {std::cout << elem << " "; // 输出 1 2 3 4 5 } std::cout << std::endl;// 如果你想要修改元素,可以使用引用 for (auto& elem : vec) {elem *= 2; // 将每个元素乘以 2 }for (auto elem : vec) {std::cout << elem << " "; // 输出 2 4 6 8 10 } std::cout << std::endl;
注意事项
类型推导规则:
auto
会忽略顶层const
和引用,因此你需要显式添加它们。例如,
auto x = 5;
推导出int
而不是const int
或int&
。复合类型:
对于复合类型(如数组、函数指针等),直接使用
auto
可能会导致意外的结果。可以使用
decltype
辅助进行更复杂的类型推导。调试和可读性:
虽然
auto
可以减少重复代码,但在某些情况下可能会影响代码的可读性。确保使用auto
不会降低代码的理解难度。总结
auto
是一个强大的工具,用于简化代码并提高开发效率。它可以自动推导变量的类型,尤其适用于复杂类型或模板编程。
注意使用
auto
时要理解其推导规则,并在必要时显式指定引用或指针类型,以避免潜在的错误
#include <iostream>
#include<vector>
#include<string>
using namespace std;
int main()
{vector<int> s;s.push_back(1);s.push_back(2);s.push_back(3);s.push_back(4);s.push_back(5);s.push_back(6);auto it = s.begin();it += 5;cout << *it << endl;return 0;
}
目前蓝桥杯支持C++11标准库,支持auto关键字
3.容器
1.各个容器对比图
对比图
2.vector
vector详解
3.set
set详解
4.map
map详解
5.链表
1.手搓单链表
此次使用数组模拟静态链表,即逻辑上是链表,物理上是数组
#include<stdio.h>
using namespace std;
#include<iostream>
const int N = 10000;
struct cll
{int id;int data;int nextid;
}nodes[N];//nodes是结点的复数
int main()
{int n = 5;//构造链表nodes[0].id = 0;//显式赋值,将代码更加可视化。吐过不添加这串代码也没事,因为头节点会被隐式赋值,会自动赋值为0。nodes[0].nextid = 1;for (int i = 1; i <= n; i++){nodes[i].id = i;nodes[i].nextid = i + 1;nodes[i].data = i * 10;}//定义为循环链表,尾指向头nodes[n].nextid = nodes[0].nextid;//删除链表节点cout << "是否删除" << endl;int bool1 = 0;cin >> bool1;if (bool1 == 1){int nowid = 0;cout << "删除" << endl;cin >> nowid;//输入当前节点位置nodes[nowid - 1].nextid = nowid + 1;//定义为循环链表,尾指向头nodes[n].nextid = nodes[0].nextid;}//插入链表节点cout << "是否插入" << endl;int bool2 = 0;cin >> bool2;if (bool2 == 1){cout << "插入位置" << endl;int n1 = n;int insert = 0;cin >> insert;nodes[++n1].id = n1;nodes[n1].data = 666;nodes[n1].nextid = nodes[insert].nextid;nodes[insert].nextid = nodes[n1].id;if (insert == 0){//定义为循环链表,尾指向头nodes[n].nextid = nodes[0].nextid;}}//遍历链表int current = nodes[0].nextid; // 从第一个数据节点开始do {printf("Node ID: %d, Data: %d\n", nodes[current].id, nodes[current].data);current = nodes[current].nextid; // 移动到下一个节点} while (current != nodes[0].nextid);
}
对于插入操作中判断isnert==0的操作,是为了防止破坏链表结构
不更新链表结构的话,会导致无限循环
2.单链表题目
小王子单链表
题目描述
小王子有一天迷上了排队的游戏,桌子上有标号为 1−101−10 的 1010 个玩具,现在小王子将他们排成一列,可小王子还是太小了,他不确定他到底想把那个玩具摆在哪里,直到最后才能排成一条直线,求玩具的编号。已知他排了 MM 次,每次都是选取标号为 XX 个放到最前面,求每次排完后玩具的编号序列。
要求一:采用单链表解决
输入描述
第一行是一个整数 MM,表示小王子排玩具的次数。
随后 MM 行每行包含一个整数 XX,表示小王子要把编号为 XX 的玩具放在最前面。
输出描述
共 MM 行,第 ii 行输出小王子第 ii 次排完序后玩具的编号序列。
输入输出样例
示例 1
输入
5 3 2 3 4 2
输出
3 1 2 4 5 6 7 8 9 10 2 3 1 4 5 6 7 8 9 10 3 2 1 4 5 6 7 8 9 10 4 3 2 1 5 6 7 8 9 10 2 4 3 1 5 6 7 8 9 10
运行限制
- 最大运行时间:1s
- 最大运行内存: 128M
本题的解题思路是先删除再插入,不过由于是单链表,需要通过一个遍历算法算得到前面的节点id
3.手搓双向链表
#include<stdio.h>
using namespace std;
#include<iostream>
const int N = 10000;
struct cll
{int id;int data;int preid;int nextid;
}nodes[N];//nodes是结点的复数
int main()
{int n = 5;//构造链表nodes[0].id = 0;//显式赋值,将代码更加可视化。吐过不添加这串代码也没事,因为头节点会被隐式赋值,会自动赋值为0。nodes[0].nextid = 1;for (int i = 1; i <= n; i++){nodes[i].preid = i - 1;nodes[i].id = i;nodes[i].nextid = i + 1;nodes[i].data = i * 10;}//定义为循环链表,尾指向头nodes[n].nextid = nodes[0].nextid;//删除链表节点cout << "是否删除" << endl;int bool1 = 0;cin >> bool1;if (bool1 == 1){int nowid = 0;cout << "删除" << endl;cin >> nowid;//输入当前节点位置nodes[nowid - 1].nextid = nowid + 1;nodes[nowid + 1].preid = nodes[nowid - 1].id;//定义为循环链表,尾指向头nodes[n].nextid = nodes[0].nextid;}//插入链表节点cout << "是否插入" << endl;int bool2 = 0;cin >> bool2;if (bool2 == 1){cout << "插入位置" << endl;int n1 = n;int insert = 0;cin >> insert;nodes[++n1].id = n1;nodes[n1].data = 666;nodes[n1].preid = nodes[insert].id;nodes[n1].nextid = nodes[insert].nextid;nodes[insert].nextid = nodes[n1].id;if (insert == 0){//定义为循环链表,尾指向头nodes[n].nextid = nodes[0].nextid;}}//遍历链表int current = nodes[0].nextid; // 从第一个数据节点开始do {printf("Node ID: %d, Data: %d\n", nodes[current].id, nodes[current].data);current = nodes[current].nextid; // 移动到下一个节点} while (current != nodes[0].nextid);
}
4.极简链表
适合于单一场景,节点即值,值即节点
5.STL的list
list详解
1.list解决上面单链表题目
#include <iostream>
#include<list>
using namespace std;
const int n = 1000;
int main()
{list <int> toy{1,2,3,4,5,6,7,8,9,10};int m;int x;cin>>m;while(m--){cin>>x;toy.remove(x);toy.push_front(x);for(auto i:toy){cout<<i<<" ";}cout<<endl;}return 0;
}
2.list例题2
问题描述
给定按从小到大的顺序排列的数字 11 到 nn,随后对它们进行 mm 次操作,每次将一个数字 xx 移动到数字 yy 之前或之后。请输出完成这 mm 次操作后它们的顺序。
输入格式
第一行为两个数字 n,mn,m,表示初始状态为 11 到 nn 的从小到大排列,后续有 mm 次操作。
第二行到第 m+1m+1 行,每行三个数 x,y,zx,y,z。当 z=0z=0 时,将 xx 移动到 yy 之后;当 z=1z=1 时,将x移动到 yy 之前。
输出格式
一行,nn 个数字,中间用空格隔开,表示 mm 次操作完成后的排列顺序。
样例输入样例输出
2 1 3 5 4
说明/提示
n≤1e4n≤1e4,m≤1e4m≤1e4。
运行限制
语言 最大运行时间 最大运行内存 C++ 1s 256M C 1s 256M Java 2s 256M Python3 3s 256M PyPy3 3s 256M Go 3s 256M JavaScript 3s 256M
#include <iostream> #include<list> #include<algorithm> using namespace std; int main() {// 请在此输入您的代码list<int>cll;int n, m, x, y, z;cin >> n >> m;for (int i = 1; i <= n; i++){cll.push_back(i);}for (int i = 1; i <= m; i++){cin >> x >> y >> z;cll.remove(x);auto it = find(cll.begin(), cll.end(), y);if (z == 0){cll.insert(++it, x);}else if (z == 1){cll.insert(it, x);}}for (auto i : cll){cout << i << " ";}cout << endl;return 0; }
6.队列
未完待续