GESP C++七级核心知识点详解
一、数学库函数(三角函数、对数函数、指数函数)
通俗解释
C++ 中的数学库函数(需包含头文件 <cmath>)是预定义的常用数学计算工具,能直接实现三角、对数、指数等复杂运算,无需手动编写计算逻辑,核心用于快速解决数学相关问题(如几何计算、数据建模等)。
- 三角函数:
sin(x)(正弦)、cos(x)(余弦)等,输入参数为弧度(而非角度),返回对应三角函数值; - 对数函数:
log10(x)返回 x 以 10 为底的对数,log2(x)返回 x 以 2 为底的对数(C++11 及以上支持); - 指数函数:
exp(x)返回 e 的 x 次方(e 是自然常数,约 2.71828)。
C++ 例题
#include <iostream>
#include <cmath> // 必须包含数学库头文件
using namespace std;
int main() {
// 1. 三角函数:计算 30 度的正弦值(需先转弧度)
double angle_deg = 30.0;
double angle_rad = angle_deg * M_PI / 180.0; // 角度转弧度:弧度 = 角度 × π / 180
double sin_val = sin(angle_rad);
cout << "30度的正弦值:" << sin_val << endl; // 输出约 0.5
// 2. 对数函数:计算 log10(100)(10的2次方=100)
double log10_val = log10(100.0);
cout << "log10(100) = " << log10_val << endl; // 输出 2.0
// 3. 指数函数:计算 exp(1)(e的1次方≈2.71828)
double exp_val = exp(1.0);
cout << "exp(1) = " << exp_val << endl; // 输出约 2.71828
return 0;
}
重点
- 必须包含头文件
<cmath>,否则无法调用数学库函数; - 牢记各函数的参数要求(如三角函数输入弧度)和返回值类型(通常为
double); - 熟悉常用常量
M_PI(π,需开启 C++11 及以上标准)。
难点
- 角度与弧度的转换逻辑(容易忘记转换直接输入角度,导致结果错误);
- 对数函数的底数区分(
log(x)是自然对数 ln(x),而非以 10 为底,需注意与log10(x)区分)。
易错点
- 直接将角度传入三角函数(如
sin(30)会计算 30 弧度的正弦值,结果完全错误); - 遗漏
<cmath>头文件或未开启 C++11 标准(导致log2(x)、M_PI无法使用); - 用
int类型接收返回值(如int res = sin(angle_rad),会丢失小数部分,导致精度错误)。
二、复杂动态规划(二维DP、区间DP、LIS、LCS、滚动数组优化)
通俗解释
动态规划(DP)是一种“拆分大问题、记录小答案、避免重复计算”的解题思想。复杂动态规划聚焦更复杂的场景:
- 二维DP:用二维数组记录“两个维度相关”的小问题答案(如“前 i 个物品放入容量 j 的背包”);
- 区间DP:专门解决“区间相关”问题(如“字符串区间的最长回文子串”“数组区间的最大和”),核心是“从短区间扩展到长区间”;
- LIS(最长上升子序列):在一串数字中找“不下降(或严格上升)且最长”的子序列(子序列不要求连续);
- LCS(最长公共子序列):在两个字符串/数组中找“最长且顺序一致”的公共子序列(不要求连续);
- 滚动数组优化:将二维DP数组压缩为一维,减少内存占用(核心是“后一个状态只依赖前一个状态”)。
C++ 例题(经典核心题型)
1. 最长上升子序列(LIS)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18}; // 输入序列
int n = nums.size();
vector<int> dp(n, 1); // dp[i] 表示:以 nums[i] 结尾的最长上升子序列长度(初始为1,自身是子序列)
// 状态转移:遍历每个元素,对比前面所有元素
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) { // 严格上升
dp[i] = max(dp[i], dp[j] + 1); // 更新最长长度
}
}
}
int lis_len = *max_element(dp.begin(), dp.end()); // 找 dp 数组的最大值
cout << "最长上升子序列长度:" << lis_len << endl; // 输出 4(子序列:2,3,7,101 或 2,5,7,101 等)
return 0;
}
2. 最长公共子序列(LCS)
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
string s1 = "ABCBDAB";
string s2 = "BDCAB";
int m = s1.size(), n = s2.size();
// dp[i][j] 表示:s1前i个字符和s2前j个字符的最长公共子序列长度
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
// 状态转移
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1[i - 1] == s2[j - 1]) { // 字符相同,长度+1
dp[i][j] = dp[i - 1][j - 1] + 1;
} else { // 字符不同,取前两种情况的最大值
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
cout << "最长公共子序列长度:" << dp[m][n] << endl; // 输出 4(子序列:BCAB 或 BDAB 等)
return 0;
}
3. 滚动数组优化(LCS 优化为一维DP)
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
string s1 = "ABCBDAB";
string s2 = "BDCAB";
int m = s1.size(), n = s2.size();
vector<int> dp(n + 1, 0); // 一维数组,节省空间
for (int i = 1; i <= m; i++) {
int prev = 0; // 保存 dp[i-1][j-1] 的值(避免被覆盖)
for (int j = 1; j <= n; j++) {
int temp = dp[j]; // 暂存当前 dp[j](即 dp[i-1][j])
if (s1[i - 1] == s2[j - 1]) {
dp[j] = prev + 1;
} else {
dp[j] = max(dp[j], dp[j - 1]);
}
prev = temp; // 更新 prev 为下一轮的 dp[i-1][j-1]
}
}
cout << "最长公共子序列长度(滚动数组优化):" << dp[n] << endl; // 输出 4
return 0;
}
重点
- 动态规划的核心:状态定义(明确
dp[i]或dp[i][j]表示什么)和 状态转移方程(如何从子问题答案推导当前问题答案); - LIS 和 LCS 是必考经典题型,需牢记基础状态转移逻辑;
- 滚动数组优化的核心是“识别状态依赖关系”(如 LCS 中
dp[i][j]只依赖dp[i-1][j]、dp[i][j-1]、dp[i-1][j-1])。
难点
- 状态定义不准确(如 LIS 中误将
dp[i]定义为“前 i 个元素的最长上升子序列长度”,而非“以第 i 个元素结尾”); - 状态转移方程推导错误(尤其是区间 DP,需明确区间扩展逻辑);
- 滚动数组优化时,未正确保存“被覆盖的前一状态”(导致数据丢失)。
易错点
- DP 数组初始化错误(如 LIS 中未将
dp[i]初始化为 1,导致长度计算偏小); - 数组下标越界(如字符串处理时,混淆
s[i]和s[i-1],因为 DP 数组通常从 1 开始索引); - 区间 DP 中“区间长度的遍历顺序错误”(应从短区间到长区间,而非从左到右遍历节点);
- 滚动数组优化时,忘记用临时变量保存前一状态(如 LCS 中直接覆盖
dp[j],导致dp[i-1][j-1]丢失)。
三、图的定义及遍历(图的概念、存储、BFS、DFS)
通俗解释
图是由“节点”(如城市)和“边”(如城市间的道路)组成的结构,是描述多元素关联关系的工具:
- 图的分类:无向图(边无方向,如双向道路)、有向图(边有方向,如单向车道);
- 节点的度:无向图中节点连接的边数,有向图中分为入度(指向节点的边数)和出度(离开节点的边数);
- 图的存储:C++ 中常用两种方式——邻接矩阵(二维数组,适合节点少、边多的稠密图)、邻接表(vector 数组,适合节点多、边少的稀疏图);
- BFS(广度优先搜索):“逐层扩散”的遍历方式,像“水波涟漪”,用队列实现,适合找“最短路径”(无权重图);
- DFS(深度优先搜索):“一条路走到黑”的遍历方式,像“迷宫探险”,用递归或栈实现,适合找“所有路径”或“连通块”。
C++ 例题(邻接表存储 + BFS + DFS)
#include <iostream>
#include <vector>
#include <queue>
#include <visited> // 注意:实际中用数组标记访问,此处为笔误,应为手动定义visited数组
using namespace std;
vector<vector<int>> adj; // 邻接表:adj[u] 存储 u 直接相连的节点
vector<bool> visited; // 标记节点是否已访问
// DFS 递归实现
void dfs(int u) {
visited[u] = true;
cout << u << " "; // 输出当前访问的节点
// 遍历所有与 u 相连的未访问节点
for (int v : adj[u]) {
if (!visited[v]) {
dfs(v);
}
}
}
// BFS 队列实现
void bfs(int start) {
queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
cout << u << " "; // 输出当前访问的节点
// 遍历所有与 u 相连的未访问节点,加入队列
for (int v : adj[u]) {
if (!visited[v]) {
visited[v] = true;
q.push(v);
}
}
}
}
int main() {
int n = 5; // 节点数(0~4)
int m = 6; // 边数
adj.resize(n);
// 无向图的边(0-1, 0-2, 1-3, 1-4, 2-4, 3-4)
adj[0].push_back(1);
adj[1].push_back(0);
adj[0].push_back(2);
adj[2].push_back(0);
adj[1].push_back(3);
adj[3].push_back(1);
adj[1].push_back(4);
adj[4].push_back(1);
adj[2].push_back(4);
adj[4].push_back(2);
adj[3].push_back(4);
adj[4].push_back(3);
// 测试 DFS
cout << "DFS 遍历结果:";
visited.assign(n, false); // 初始化访问标记
dfs(0); // 从节点 0 开始
cout << endl; // 输出:0 1 3 4 2(或其他顺序,取决于邻接表存储顺序)
// 测试 BFS
cout << "BFS 遍历结果:";
visited.assign(n, false); // 重置访问标记
bfs(0); // 从节点 0 开始
cout << endl; // 输出:0 1 2 3 4(逐层扩散,顺序固定)
return 0;
}
重点
- 掌握图的两种存储方式(邻接矩阵、邻接表)的适用场景和实现;
- 理解 BFS 和 DFS 的核心思想(逐层 vs 深度)和实现工具(队列 vs 递归/栈);
- 遍历前必须初始化“访问标记数组”(避免重复访问节点)。
难点
- 邻接表的构建逻辑(尤其是有向图,需区分“起点”和“终点”);
- DFS 递归深度过大导致栈溢出(需用迭代版 DFS 优化);
- 结合实际问题设计图的存储(如节点是字符串时,需用哈希表映射为整数索引)。
易错点
- 忘记标记访问节点(导致无限循环,程序崩溃);
- 邻接表构建时“漏加边”(如无向图只加了
u→v,没加v→u); - 节点编号从 1 开始,但数组初始化从 0 开始(导致下标越界);
- BFS 中未在“加入队列时”标记访问(而是在“弹出队列时”标记,导致同一节点多次入队,效率低下)。
四、泛洪算法(Flood Fill)
通俗解释
泛洪算法是图遍历的一种特殊应用,核心是“从一个起点出发,将所有‘相邻且满足条件’的节点标记为同一类”,像“洪水从起点蔓延,淹没所有可达的区域”。常见应用:图像填色(如 Photoshop 魔棒工具)、连通块查找(如找出二维数组中所有相连的 1)。C++ 中通常用 BFS 或 DFS 实现。
C++ 例题(二维数组连通块填充)
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// 方向数组:上下左右四个方向
int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
// BFS 实现泛洪填充
void floodFillBFS(vector<vector<int>>& grid, int x, int y, int oldVal, int newVal) {
int m = grid.size(), n = grid[0].size();
if (grid[x][y] != oldVal) return; // 起点不满足条件,直接返回
queue<pair<int, int>> q;
q.push({x, y});
grid[x][y] = newVal; // 标记为新值(避免重复访问)
while (!q.empty()) {
auto [curX, curY] = q.front();
q.pop();
// 遍历四个方向
for (auto& dir : dirs) {
int nx = curX + dir[0];
int ny = curY + dir[1];
// 边界判断 + 满足旧值条件
if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == oldVal) {
grid[nx][ny] = newVal;
q.push({nx, ny});
}
}
}
}
int main() {
// 二维数组表示图像(1 表示需填充的区域,0 表示边界)
vector<vector<int>> grid = {
{1, 1, 1},
{1, 1, 0},
{1, 0, 1}
};
floodFillBFS(grid, 0, 0, 1, 2); // 从 (0,0) 出发,将所有 1 改为 2
// 输出填充后的结果
cout << "填充后的二维数组:" << endl;
for (auto& row : grid) {
for (int val : row) {
cout << val << " ";
}
cout << endl;
}
/* 输出:
2 2 2
2 2 0
2 0 2
*/
return 0;
}
重点
- 方向数组的定义(上下左右四个方向,避免漏写或写错);
- 边界判断(确保访问的节点在二维数组内,不越界);
- 标记逻辑(填充时直接修改原值为新值,或用单独的访问数组,避免重复处理)。
难点
- 多连通块的处理(需遍历所有未标记的节点,逐个作为起点泛洪);
- 复杂条件的判断(如“相邻”定义为“8个方向”,或填充条件为“值在某个区间内”);
- 大规模数据的效率优化(如二维数组过大时,BFS 比 DFS 更稳定,避免栈溢出)。
易错点
- 方向数组写错(如写成
{0,1}, {0,-1}只处理左右,漏了上下); - 边界判断遗漏(如只判断
nx < m,没判断nx >= 0,导致下标越界); - 未及时标记已处理的节点(导致同一节点多次入队,程序超时或崩溃);
- 起点坐标错误(如输入
(x,y)时混淆行和列,导致填充失败)。
五、哈希表(概念与应用)
通俗解释
哈希表是一种“键值对”存储结构,核心是“通过哈希函数将‘键’(如学生学号)映射到数组的索引,快速查找对应‘值’(如学生姓名)”。C++ 中常用哈希表容器:
unordered_map:存储键值对(key-value),键唯一,查找、插入、删除效率约 O(1)(理想情况);unordered_set:只存储键(key),键唯一,用于快速判断某个元素是否存在。
哈希表的优势是“无需排序,直接通过键访问”,比普通数组(需按索引访问)、有序容器(如map,O(log n) 效率)更高效。
C++ 例题(unordered_map 统计字符出现次数)
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
string s = "abacaba";
unordered_map<char, int> charCount; // 键:字符,值:出现次数
// 遍历字符串,统计次数
for (char c : s) {
charCount[c]++; // 键不存在时,默认值为 0,++ 后变为 1
}
// 遍历哈希表,输出结果
cout << "字符出现次数:" << endl;
for (auto& pair : charCount) {
cout << pair.first << ": " << pair.second << endl;
}
/* 输出(顺序不固定,因为 unordered_map 无序):
a: 4
b: 2
c: 1
*/
// 查找某个字符的次数
char target = 'a';
if (charCount.find(target) != charCount.end()) {
cout << target << " 出现了 " << charCount[target] << " 次" << endl;
} else {
cout << target << " 未出现" << endl;
}
return 0;
}
重点
- 掌握
unordered_map和unordered_set的基本操作:insert()(插入)、find()(查找)、erase()(删除)、遍历; - 理解哈希表的核心优势(快速访问,O(1) 平均时间复杂度);
- 明确键的唯一性(
unordered_map和unordered_set中键不能重复,重复插入会覆盖或失败)。
难点
- 哈希冲突的理解(多个键映射到同一索引,C++ 内部通过“链地址法”解决,但无需手动处理);
- 自定义类型作为键(需重载哈希函数和
==运算符,考纲中较少涉及,了解即可); - 哈希表与有序容器(
map/set)的选择(需有序时用map,无需有序时用unordered_map更高效)。
易错点
- 访问
unordered_map中不存在的键(会自动插入该键,值为默认值,导致统计错误,需用find()先判断是否存在); - 混淆
unordered_map和map(前者无序、哈希实现,后者有序、红黑树实现,效率和用途不同); - 遍历哈希表时依赖顺序(
unordered_map无序,如需有序输出,需存入数组后排序); - 键的类型选择错误(如用
vector<int>作为键,未自定义哈希函数,导致编译错误)。
986


&spm=1001.2101.3001.11974&articleId=156953536&d=1&t=3&u=2b1d37fa587041f0ac2fedba91dcaef2)

被折叠的 条评论
为什么被折叠?



