Part04 算法
CSP-J 初赛常考知识点总结 - 算法篇
1. 算法概念与描述
1.1 算法概念
- 算法:解决特定问题的有限步骤序列
- 特性:有穷性、确定性、可行性、输入、输出
- 评价标准:时间复杂度、空间复杂度、正确性、可读性
1.2 算法描述
- 自然语言描述:用人类语言描述算法步骤
- 流程图描述:使用标准图形符号表示算法流程
- 伪代码描述:介于自然语言和编程语言之间的描述方式
1.3 流程图符号含义
标准流程图符号含义:
- 圆角矩形:开始/结束符号
- 平行四边形:输入/输出操作
- 矩形:处理步骤/普通操作
- 菱形:判断/决策步骤
- 箭头:流程方向
- 圆形:连接点(用于连接不同页的流程图)
2. 入门算法
2.1 枚举法
- 思想:遍历所有可能的情况,找到满足条件的解
- 适用场景:解空间有限的问题
- 时间复杂度:通常为 O(n)O(n)O(n) 或 O(n2)O(n^2)O(n2)
// 示例:找出100以内的所有质数
for (int i = 2; i <= 100; i++) {bool isPrime = true;for (int j = 2; j * j <= i; j++) {if (i % j == 0) {isPrime = false;break;}}if (isPrime) cout << i << " ";
}
2.2 模拟法
- 思想:按照题目要求直接模拟过程
- 适用场景:流程清晰、步骤明确的问题
- 关键:准确理解题意,注意边界条件
3. 基础算法
3.1 贪心法
- 思想:每一步都采取当前最优选择
- 特点:局部最优不一定导致全局最优
- 适用条件:最优子结构、贪心选择性质
部分背包问题(经典例题)
问题描述:有 nnn 个物品,第 iii 个物品重量为 w[i]w[i]w[i],价值为 v[i]v[i]v[i]。背包容量为 CCC,如何选择物品放入背包,使得总价值最大?(物品可以分割)
贪心策略:按单位重量价值( vw\frac{v}{w}wv )从大到小排序,优先选择单位价值高的物品。
struct node {double m, v; // 重量m和价值vdouble avg; // 性价比(价值/重量)
} a[1010];
bool cmp(node a, node b) { return a.avg > b.avg; }
int main() { // 主程序入口int N, T;double sum = 0;cin >> N >> T;for (int i = 1; i <= N; i++) {cin >> a[i].m >> a[i].v;a[i].avg = a[i].v / a[i].m;}sort(a + 1, a + 1 + N, cmp);for (int i = 1; i <= N; i++) {if (a[i].m <= T) { // 最多能够取得(减去)当前物品的重量sum += a[i].v;T -= a[i].m; // 剩余的背包容量 = 当前背包容量 -// 能最装当前物品i的(最多)重量} else {sum += T * a[i].avg;break;}}printf("%.2lf", sum);return 0;
}
3.2 递推法
- 思想:根据已知条件逐步推导出后续结果
- 公式:f(n)=F(f(n−1),f(n−2),…)f(n) = F(f(n-1), f(n-2), \ldots)f(n)=F(f(n−1),f(n−2),…)
- 典型应用:斐波那契数列、杨辉三角
// 斐波那契数列递推
int fib(int n) {if (n <= 1) return n;int f0 = 0, f1 = 1, f2;for (int i = 2; i <= n; i++) {f2 = f0 + f1;f0 = f1;f1 = f2;}return f1;
}
3.3 递归法
- 思想:函数调用自身来解决问题
- 要素:递归基、递归关系
- 优缺点:代码简洁,但可能栈溢出
// 递归实现阶乘
int factorial(int n) {if (n == 0 || n == 1) retur n 1; // 递归基return n * factorial(n - 1); // 递归关系
}
3.4 二分法
- 思想:在有序序列中每次排除一半的搜索空间
- 时间复杂度:O(logn)O(\log n)O(logn)
- 应用:二分查找、二分答案
// 二分查找模板
int binarySearch(int arr[], int n, int target) {int left = 0, right = n - 1;while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] == target) return mid;else if (arr[mid] < target) left = mid + 1;else right = mid - 1;}return -1;
}
3.5 倍增法
- 思想:通过倍增加速处理过程
- 应用:快速幂、ST表、LCA
// 快速幂算法
long long fastPow(long long a, long long b) {long long result = 1;while (b) {if (b & 1) result *= a;a *= a;b >>= 1;}return result;
}
4. 数值处理算法
4.1 高精度加法
void add(int a[], int b[], int c[], int len) {int carry = 0;for (int i = 0; i < len; i++) {int sum = a[i] + b[i] + carry;c[i] = sum % 10;carry = sum / 10;}if (carry > 0) {c[len] = carry;}
}
4.2 高精度减法
void sub(int a[], int b[], int c[], int len) {int borrow = 0;for (int i = 0; i < len; i++) {int diff = a[i] - b[i] - borrow;if (diff < 0) {diff += 10;borrow = 1;} else {borrow = 0;}c[i] = diff;}
}
4.3 高精度乘法
void mul(int a[], int b, int c[], int len) {int carry = 0;for (int i = 0; i < len; i++) {int product = a[i] * b + carry;c[i] = product % 10;carry = product / 10;}if (carry > 0) {c[len] = carry;}
}
4.4 高精度除法(高除低)
void div(int a[], int b, int c[], int &r, int len) {r = 0;for (int i = len - 1; i >= 0; i--) {int dividend = r * 10 + a[i];c[i] = dividend / b;r = dividend % b;}
}
5. 排序算法
5.1 排序算法总结
排序算法 | 最好时间复杂度 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
---|---|---|---|---|---|---|
冒泡排序 | O(n)O(n)O(n) | O(n2)O(n^2)O(n2) | O(n2)O(n^2)O(n2) | O(1)O(1)O(1) | 稳定 | 小规模数据 |
选择排序 | O(n2)O(n^2)O(n2) | O(n2)O(n^2)O(n2) | O(n2)O(n^2)O(n2) | O(1)O(1)O(1) | 不稳定 | 小规模数据 |
插入排序 | O(n)O(n)O(n) | O(n2)O(n^2)O(n2) | O(n2)O(n^2)O(n2) | O(1)O(1)O(1) | 稳定 | 小规模或基本有序数据 |
计数排序 | O(n+k)O(n+k)O(n+k) | O(n+k)O(n+k)O(n+k) | O(n+k)O(n+k)O(n+k) | O(k)O(k)O(k) | 稳定 | 数据范围小的整数排序 |
快速排序 | O(nlogn)O(n \log n)O(nlogn) | O(nlogn)O(n \log n)O(nlogn) | O(n2)O(n^2)O(n2) | O(logn)O(\log n)O(logn) | 不稳定 | 大规模数据,平均性能好 |
归并排序 | O(nlogn)O(n \log n)O(nlogn) | O(nlogn)O(n \log n)O(nlogn) | O(nlogn)O(n \log n)O(nlogn) | O(n)O(n)O(n) | 稳定 | 大规模数据,稳定排序 |
堆排序 | O(nlogn)O(n \log n)O(nlogn) | O(nlogn)O(n \log n)O(nlogn) | O(nlogn)O(n \log n)O(nlogn) | O(1)O(1)O(1) | 不稳定 | 大规模数据,原地排序 |
5.2 冒泡排序
void bubbleSort(int arr[], int n) {for (int i = 0; i < n - 1; i++)for (int j = 0; j < n - i - 1; j++)if (arr[j] > arr[j + 1]) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}
}
5.3 选择排序
void selectionSort(int arr[], int n) {for (int i = 0; i < n - 1; i++) {int minIndex = i;for (int j = i + 1; j < n; j++)if (arr[j] < arr[minIndex])minIndex = j;int temp = arr[i];arr[i] = arr[minIndex];arr[minIndex] = temp;}
}
5.4 插入排序
void insertionSort(int arr[], int n) {for (int i = 1; i < n; i++) {int key = arr[i];int j = i - 1;while (j >= 0 && arr[j] > key) {arr[j + 1] = arr[j];j--;}arr[j + 1] = key;}
}
5.5 计数排序
void countingSort(int arr[], int n) {// 找出最大值int maxVal = arr[0];for (int i = 1; i < n; i++)if (arr[i] > maxVal)maxVal = arr[i];// 创建计数数组int count[maxVal + 1];for (int i = 0; i <= maxVal; i++)count[i] = 0;// 计数for (int i = 0; i < n; i++)count[arr[i]]++;// 重建数组int index = 0;for (int i = 0; i <= maxVal; i++)while (count[i] > 0) {arr[index++] = i;count[i]--;}
}
6. 搜索算法
6.1 深度优先搜索 (DFS)
// 使用邻接矩阵表示的图
void dfs(int node, bool visited[], int graph[][MAX], int n) {visited[node] = true;printf("%d ", node);for (int i = 0; i < n; i++) {if (graph[node][i] == 1 && !visited[i]) {dfs(i, visited, graph, n);}}
}
6.2 广度优先搜索 (BFS)
// 使用邻接矩阵表示的图
void bfs(int start, bool visited[], int graph[][MAX], int n) {int queue[MAX];int front = 0, rear = 0;visited[start] = true;queue[rear++] = start;while (front < rear) {int node = queue[front++];printf("%d ", node);for (int i = 0; i < n; i++)if (graph[node][i] == 1 && !visited[i]) {visited[i] = true;queue[rear++] = i;}}
}
7. 图论算法
7.1 深度优先遍历
- 应用:连通分量检测、拓扑排序、环路检测
7.2 广度优先遍历
- 应用:最短路径(无权图)、连通性检测
7.3 泛洪算法-洪水填充 (Flood Fill)
// 使用二维数组表示的图像
void floodFill(int image[][MAX], int sr, int sc, int newColor, int oldColor, int m, int n) {if (sr < 0 || sr >= m || sc < 0 || sc >= n || image[sr][sc] != oldColor) return;image[sr][sc] = newColor;// 四个方向递归填充floodFill(image, sr + 1, sc, newColor, oldColor, m, n);floodFill(image, sr - 1, sc, newColor, oldColor, m, n);floodFill(image, sr, sc + 1, newColor, oldColor, m, n);floodFill(image, sr, sc - 1, newColor, oldColor, m, n);
}
8. 动态规划
8.1 基本思路
- 定义状态:dp[i]dp[i]dp[i] 表示什么含义
- 状态转移方程:dp[i]=F(dp[i−1],dp[i−2],…)dp[i] = F(dp[i-1], dp[i-2], \ldots)dp[i]=F(dp[i−1],dp[i−2],…)
- 初始化:基础情况的处理
- 计算顺序:确定计算方向
- 结果提取:从状态中获取最终答案
8.2 简单一维动态规划
// 斐波那契数列
int fib(int n) {if (n <= 1)return n;int dp[n + 1];dp[0] = 0, dp[1] = 1;for (int i = 2; i <= n; i++)dp[i] = dp[i - 1] + dp[i - 2];return dp[n];
}
8.3 0-1背包问题
// f[j]:背包容量为 j,可选择前 i 件物品的最大价值
for (int i = 1; i <= n; i++)for (int j = m; j >= w[i]; i--)f[j] = max(f[j], f[j - w[i]] + v[i]);
8.4 完全背包问题
// f[i][j]:可选择前 i 个物品,重量为 j 的最大价值
for (int i = 1; i <= n; i++)for (int j = w[i]; j <= m; i++)f[i][j] = max(f[i][j], f[i][j - w[i]] + v[i]);
8.5 最长上升子序列 (LIS)
const int N = 5005;
// f[i]:所有的 (1~i) 子序列中含 i 为结尾的上升子序列最长长度
int f[N], a[N], n;
int main() {cin >> n;for (int i = 1; i <= n; i++)cin >> a[i], f[i] = 1;for (int i = 1; i <= n; i++)for (int j = 1; j < i; j++)if (a[j] < a[i])f[i] = max(f[i], f[j] + 1);int ans = 0;for (int i = 1; i <= n; i++)ans = max(ans, f[i]);cout << ans << "\n";
}
8.6 简单区间动态规划
for (int len = 1; len <= n; len++) // 枚举:区间长度for (int i = 1; i + len <= n; i++) { // 枚举:左端点int j = i + len; // 计算右端点for (int k = i; k < j; k++) // 枚举:分割点dp[i][j] = max / min(dp[i][j], dp[i][k] + dp[k][j]);}
9. STL 部分应用
9.1 优先队列 (priority_queue)
#include <queue>
#include <functional>// 默认是大顶堆
priority_queue<int> maxHeap;// 小顶堆的声明方式
priority_queue<int, vector<int>, greater<int>> minHeap;// 基本操作
maxHeap.push(3); // 插入元素
maxHeap.push(1);
maxHeap.push(4);
maxHeap.push(1);
maxHeap.push(5);cout << maxHeap.top(); // 5 (获取最大元素)
maxHeap.pop(); // 删除最大元素
cout << maxHeap.top(); // 4// 自定义比较函数
struct Compare {bool operator()(int a, int b) {return a > b; // 小顶堆}
};
priority_queue<int, vector<int>, Compare> customHeap;
9.2 一维前缀和与差分
前缀和
// 预处理前缀和数组
void precomputePrefixSum(int arr[], int n, int prefix[]) {prefix[0] = arr[0];for (int i = 1; i < n; i++) {prefix[i] = prefix[i - 1] + arr[i];}
}// 查询区间和 [l, r]
int queryRangeSum(int prefix[], int l, int r) {if (l == 0) return prefix[r];return prefix[r] - prefix[l - 1];
}
差分
// 构建差分数组
void buildDifferenceArray(int arr[], int n, int diff[]) {diff[0] = arr[0];for (int i = 1; i < n; i++) {diff[i] = arr[i] - arr[i - 1];}
}// 区间修改 [l, r] 增加 val
void rangeUpdate(int diff[], int n, int l, int r, int val) {diff[l] += val;if (r + 1 < n) {diff[r + 1] -= val;}
}// 从差分数组还原原数组
void restoreArray(int diff[], int n, int arr[]) {arr[0] = diff[0];for (int i = 1; i < n; i++) {arr[i] = arr[i - 1] + diff[i];}
}
9.3 STL 二分查找函数
#include <algorithm>
#include <vector>vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// lower_bound: 返回第一个大于等于目标值的迭代器
auto lb = lower_bound(v.begin(), v.end(), 5);
cout << "lower_bound for 5 at position " << (lb - v.begin()) << endl;// upper_bound: 返回第一个大于目标值的迭代器
auto ub = upper_bound(v.begin(), v.end(), 5);
cout << "upper_bound for 5 at position " << (ub - v.begin()) << endl;// binary_search: 检查元素是否存在
bool exists = binary_search(v.begin(), v.end(), 5);// 在普通数组中使用
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int n = sizeof(arr) / sizeof(arr[0]);int* lb_arr = lower_bound(arr, arr + n, 5);
cout << "lower_bound for 5 at position " << (lb_arr - arr) << endl;
备考建议:
- 理解各种算法的基本思想和适用场景
- 掌握常见算法的模板代码
- 熟练分析算法的时间复杂度和空间复杂度
- 多练习经典算法问题,提高解决问题的能力
- 注意算法在实际应用中的优化技巧
常见考点:
- 排序算法的选择和实现
- 搜索算法的应用和优化
- 动态规划的状态设计和转移方程
- 图论算法的基本操作和应用
- 高精度运算的实现和处理
CSP-J 入门组复习大纲
- 2.1.1 基础知识与编程环境
- 2.1.2 C++ 程序设计
- 2.1.3 数据结构
- 2.1.4 算法
- 2.1.5 数学
- 2.1.6 其他杂项