vault backup: 2026-06-10 11:19:43

This commit is contained in:
2026-06-10 11:19:43 +08:00
parent 788576ec4b
commit b9bc08ab2f
6 changed files with 391 additions and 209 deletions
+69 -39
View File
@@ -65,13 +65,21 @@ flowchart TD
归并排序是分治法最经典的应用之一,时间复杂度 **O(n logn)**。
```plaintext
function mergeSort(A, left, right):
if left < right:
mid = (left + right) / 2
mergeSort(A, left, mid) // 分解:排序左半部分
mergeSort(A, mid+1, right) // 分解:排序右半部分
merge(A, left, mid, right) // 合并:合并两个有序子数组
```cpp
#include <vector>
using namespace std;
// merge函数省略,此处仅展示归并排序的分治骨架
// void merge(vector<int>& A, int left, int mid, int right) { ... }
void mergeSort(vector<int>& A, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(A, left, mid); // 分解:排序左半部分
mergeSort(A, mid + 1, right); // 分解:排序右半部分
// merge(A, left, mid, right); // 合并:合并两个有序子数组
}
}
```
- **分解**:将数组从中间分为两半
@@ -95,28 +103,42 @@ function mergeSort(A, left, right):
- 每组2个元素:组内比较1次,再与当前最大、最小各比较1次
- 比较次数从 **2(n-1)** 降至约 **3n/2**
```plaintext
function findMaxMin(A, n):
// 初始化
if n is odd:
maxVal = minVal = A[1]
start = 2
else:
if A[1] < A[2]:
minVal = A[1], maxVal = A[2]
else:
minVal = A[2], maxVal = A[1]
start = 3
```cpp
#include <vector>
#include <utility>
using namespace std;
// 两两比较
for i = start to n step 2:
if A[i] < A[i+1]:
smaller, larger = A[i], A[i+1]
else:
smaller, larger = A[i+1], A[i]
if smaller < minVal: minVal = smaller
if larger > maxVal: maxVal = larger
return (maxVal, minVal)
// 同时求最大值和最小值,比较次数约 3n/2
pair<int, int> findMaxMin(vector<int>& A, int n) {
int maxVal, minVal;
int start;
// 初始化:根据元素个数的奇偶性决定初始值
if (n % 2 == 1) {
maxVal = minVal = A[0];
start = 1;
} else {
if (A[0] < A[1]) {
minVal = A[0]; maxVal = A[1];
} else {
minVal = A[1]; maxVal = A[0];
}
start = 2;
}
// 两两比较:每组内比较1次,再分别与全局最大最小比较
for (int i = start; i < n; i += 2) {
int smaller, larger;
if (A[i] < A[i + 1]) {
smaller = A[i]; larger = A[i + 1];
} else {
smaller = A[i + 1]; larger = A[i];
}
if (smaller < minVal) minVal = smaller;
if (larger > maxVal) maxVal = larger;
}
return {maxVal, minVal};
}
```
**解释**:每次循环处理两个元素,组内比较1次确定大小,再分别与全局最大最小比较最多2次,所以每组最多3次比较。总比较次数约为 3(n-1)/2。
@@ -129,17 +151,25 @@ function findMaxMin(A, n):
2. 只有当某个元素**大于当前最大值**时,才更新第二大值
3. 一次遍历即可完成
```plaintext
function findSecondMax(A, n):
maxVal = A[1]
secondMax = -infinity
for i = 2 to n:
if A[i] > maxVal:
secondMax = maxVal // 原来的最大变为第二
maxVal = A[i] // 更新最大
else if A[i] > secondMax:
secondMax = A[i] // 更新第二
return secondMax
```cpp
#include <vector>
#include <climits>
using namespace std;
// 一次遍历找第二大的数,时间O(n)
int findSecondMax(vector<int>& A, int n) {
int maxVal = A[0];
int secondMax = INT_MIN;
for (int i = 1; i < n; i++) {
if (A[i] > maxVal) {
secondMax = maxVal; // 原来的最大变为第二
maxVal = A[i]; // 更新最大
} else if (A[i] > secondMax) {
secondMax = A[i]; // 更新第二
}
}
return secondMax;
}
```
#### 4.5 伪币问题
@@ -76,17 +76,24 @@ $$dp[i] = \max(arr[i], \ dp[i-1] + arr[i])$$
**含义**:以第 i 个元素结尾的最大子数组和,要么是从头开始(只取 arr[i]),要么是接在之前的最大子数组后面。
```plaintext
function maxSubarraySum(arr, n):
dp = array of size n
dp[0] = arr[0]
maxSum = dp[0]
```cpp
#include <vector>
#include <algorithm>
using namespace std;
for i = 1 to n-1:
dp[i] = max(arr[i], dp[i-1] + arr[i])
maxSum = max(maxSum, dp[i])
int maxSubarraySum(vector<int>& arr) {
int n = arr.size();
vector<int> dp(n);
dp[0] = arr[0];
int maxSum = dp[0];
return maxSum
for (int i = 1; i < n; i++) {
dp[i] = max(arr[i], dp[i-1] + arr[i]); // 从头开始 or 接在前面
maxSum = max(maxSum, dp[i]);
}
return maxSum;
}
```
**解释**:对每个位置 i,我们做一个决策——是从当前位置重新开始子数组,还是接在前面的子数组后面。选择较大的那个作为 dp[i]。最后取所有 dp[i] 的最大值。
@@ -104,25 +111,30 @@ function maxSubarraySum(arr, n):
$$dp[i][j] = \max(dp[i-1][j], \ dp[i][j-1]) + matrix[i][j]$$
```plaintext
function maxMatrixPath(matrix, n):
dp = n x n matrix
dp[0][0] = matrix[0][0]
```cpp
#include <vector>
#include <algorithm>
using namespace std;
int maxMatrixPath(vector<vector<int>>& matrix, int n) {
vector<vector<int>> dp(n, vector<int>(n));
dp[0][0] = matrix[0][0];
// 填第一行(只能从左边来)
for j = 1 to n-1:
dp[0][j] = dp[0][j-1] + matrix[0][j]
for (int j = 1; j < n; j++)
dp[0][j] = dp[0][j-1] + matrix[0][j];
// 填第一列(只能从上面来)
for i = 1 to n-1:
dp[i][0] = dp[i-1][0] + matrix[i][0]
for (int i = 1; i < n; i++)
dp[i][0] = dp[i-1][0] + matrix[i][0];
// 填其余位置
for i = 1 to n-1:
for j = 1 to n-1:
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + matrix[i][j]
for (int i = 1; i < n; i++)
for (int j = 1; j < n; j++)
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + matrix[i][j];
return dp[n-1][n-1]
return dp[n-1][n-1];
}
```
**解释**:到达位置 (i,j) 只能从上方 (i-1,j) 或左方 (i,j-1) 来。取两者中较大的路径和,加上当前位置的值。边界条件是第一行和第一列只有一条路径。
@@ -137,15 +149,19 @@ $$f(n) = f(n-1) + f(n-2)$$
**解释**:到达第 n 阶的方法数 = 从第 n-1 阶走1步 + 从第 n-2 阶走2步。这是一个变体的 Fibonacci 数列。
```plaintext
function climbStairs(n):
if n <= 2: return n
dp = array of size n+1
dp[1] = 1
dp[2] = 2
for i = 3 to n:
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
```cpp
#include <vector>
using namespace std;
int climbStairs(int n) {
if (n <= 2) return n;
vector<int> dp(n + 1);
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++)
dp[i] = dp[i-1] + dp[i-2];
return dp[n];
}
```
> [!question] 爬楼梯和Fibonacci有什么关系?
@@ -155,15 +171,19 @@ function climbStairs(n):
用动态规划可以将 Fibonacci 从 O(2ⁿ) 优化到 O(n):
```plaintext
function fibDP(n):
if n <= 1: return n
dp = array of size n+1
dp[0] = 0
dp[1] = 1
for i = 2 to n:
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
```cpp
#include <vector>
using namespace std;
int fibDP(int n) {
if (n <= 1) return n;
vector<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];
}
```
> [!tip] 进一步优化空间
@@ -179,18 +199,26 @@ function fibDP(n):
$$dp[i][j] = \begin{cases} dp[i-1][j-1] + 1 & \text{if } X[i] = Y[j] \\ \max(dp[i-1][j], \ dp[i][j-1]) & \text{if } X[i] \neq Y[j] \end{cases}$$
```plaintext
function LCS(X, Y, m, n):
dp = (m+1) x (n+1) matrix, all zeros
```cpp
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
for i = 1 to m:
for j = 1 to n:
if X[i] == Y[j]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
int LCS(string& X, string& Y, int m, int n) {
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
return dp[m][n]
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (X[i-1] == Y[j-1])
dp[i][j] = dp[i-1][j-1] + 1; // 字符匹配,LCS长度+1
else
dp[i][j] = max(dp[i-1][j], dp[i][j-1]); // 取两种情况的较大值
}
}
return dp[m][n];
}
```
**解释**:如果当前字符匹配,则 LCS 长度等于去掉这两个字符后的 LCS 长度加 1;如果不匹配,则取"去掉 X 的当前字符"和"去掉 Y 的当前字符"两种情况的较大值。
+85 -44
View File
@@ -78,17 +78,26 @@ flowchart TD
### 五、回溯法一般框架
```plaintext
void backtrack(int t) {
```cpp
#include <vector>
using namespace std;
// 回溯法框架
// t: 当前搜索深度(第几个决策)
// n: 问题规模(总共需要做的决策数)
// x: 当前解向量
void backtrack(int t, int n, vector<int>& x) {
if (t > n)
output(x); // 到达叶节点,输出解
else {
for (int i = f(n,t); i <= g(n,t); i++) {
x[t] = h(i); // 第t个位置尝试第i种选择
if (constraint(t) && bound(t))
backtrack(t+1); // 满足约束,继续深入
// 否则剪枝,回退(x[t]会被下次循环覆盖)
}
// output(x); // 到达叶节点,输出解
return;
// f(n,t) 和 g(n,t) 为第t个位置可选值的范围
// h(i) 将第i个候选值赋给x[t]
// constraint(t) 为约束函数,bound(t) 为限界函数
for (int i = 1; i <= n; i++) {
x[t] = i; // 第t个位置尝试第i种选择
if (/* constraint(t) && */ /* bound(t) */ true)
backtrack(t + 1, n, x); // 满足约束,继续深入
// 否则剪枝,回退(x[t]会被下次循环覆盖)
}
}
```
@@ -116,23 +125,40 @@ void backtrack(int t) {
**时间复杂度**:最坏 O(mⁿ),四色问题为 O(4ⁿ)
```plaintext
function graphColoring(graph, m, colors, vertex):
if vertex == n:
printSolution(colors) // 所有顶点着色完成
return
```cpp
#include <vector>
#include <iostream>
using namespace std;
for c = 1 to m:
colors[vertex] = c // 尝试颜色c
if isSafe(graph, colors, vertex):
graphColoring(graph, m, colors, vertex + 1)
colors[vertex] = 0 // 回溯
// 判断将 vertex 着色为 colors[vertex] 是否安全
bool isSafe(vector<vector<int>>& graph, vector<int>& colors, int vertex) {
int n = graph.size();
for (int v = 0; v < n; v++) {
if (graph[vertex][v] && colors[v] == colors[vertex])
return false; // 相邻顶点颜色相同,不安全
}
return true;
}
function isSafe(graph, colors, vertex):
for each neighbor v of vertex:
if colors[v] == colors[vertex]:
return false
return true
// 图着色回溯算法
// graph: 邻接矩阵, m: 颜色数, colors: 着色方案, vertex: 当前处理的顶点
void graphColoring(vector<vector<int>>& graph, int m, vector<int>& colors, int vertex) {
int n = graph.size();
if (vertex == n) {
// 所有顶点着色完成,输出解
for (int i = 0; i < n; i++)
cout << colors[i] << " ";
cout << endl;
return;
}
for (int c = 1; c <= m; c++) {
colors[vertex] = c; // 尝试颜色c
if (isSafe(graph, colors, vertex))
graphColoring(graph, m, colors, vertex + 1);
colors[vertex] = 0; // 回溯
}
}
```
```mermaid
@@ -173,29 +199,44 @@ flowchart TD
- 检查列冲突和对角线冲突
- 冲突则剪枝
```plaintext
function solveNQueens(board, row, n):
if row == n:
printBoard(board)
return
```cpp
#include <vector>
#include <iostream>
using namespace std;
for col = 0 to n-1:
if isSafe(board, row, col, n):
board[row][col] = "Q" // 放置皇后
solveNQueens(board, row + 1, n)
board[row][col] = "." // 回溯
function isSafe(board, row, col, n):
// 判断在 (row, col) 放置皇后是否安全
bool isSafe(vector<string>& board, int row, int col, int n) {
// 检查同一列
for i = 0 to row-1:
if board[i][col] == "Q": return false
for (int i = 0; i < row; i++)
if (board[i][col] == 'Q') return false;
// 检查左上对角线
for i = row-1, j = col-1; i >= 0 && j >= 0; i--, j--:
if board[i][j] == "Q": return false
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--)
if (board[i][j] == 'Q') return false;
// 检查右上对角线
for i = row-1, j = col+1; i >= 0 && j < n; i--, j++:
if board[i][j] == "Q": return false
return true
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++)
if (board[i][j] == 'Q') return false;
return true;
}
// N皇后回溯算法
// board: 棋盘, row: 当前处理的行, n: 棋盘大小
void solveNQueens(vector<string>& board, int row, int n) {
if (row == n) {
// 输出一个解
for (int i = 0; i < n; i++)
cout << board[i] << endl;
cout << endl;
return;
}
for (int col = 0; col < n; col++) {
if (isSafe(board, row, col, n)) {
board[row][col] = 'Q'; // 放置皇后
solveNQueens(board, row + 1, n);
board[row][col] = '.'; // 回溯
}
}
}
```
**解释**:N皇后是经典的回溯应用。由于每行只放一个皇后,解空间是排列树。剪枝函数检查列冲突和两条对角线冲突,大幅减少搜索空间。
@@ -21,28 +21,52 @@ DFS 沿着一条路径**尽可能深地**搜索,走不通再回退。
**实现方式**:使用**栈**(Stack)或**递归调用栈**
```plaintext
function DFS(graph, start):
visited = set()
stack = [start]
```cpp
#include <vector>
#include <stack>
#include <iostream>
using namespace std;
while stack is not empty:
node = stack.pop()
if node not in visited:
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
stack.push(neighbor)
// DFS(栈实现)
// graph: 邻接表, start: 起始顶点
void DFS(vector<vector<int>>& graph, int start) {
int n = graph.size();
vector<bool> visited(n, false);
stack<int> st;
st.push(start);
while (!st.empty()) {
int node = st.top();
st.pop();
if (!visited[node]) {
visited[node] = true;
cout << node << " ";
for (int neighbor : graph[node]) {
if (!visited[neighbor])
st.push(neighbor);
}
}
}
}
```
递归版本更直观:
```plaintext
function DFS_recursive(graph, node, visited):
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
DFS_recursive(graph, neighbor, visited)
```cpp
#include <vector>
#include <iostream>
using namespace std;
// DFS(递归版本)
// graph: 邻接表, node: 当前顶点, visited: 访问标记数组
void DFS_recursive(vector<vector<int>>& graph, int node, vector<bool>& visited) {
visited[node] = true;
cout << node << " ";
for (int neighbor : graph[node]) {
if (!visited[neighbor])
DFS_recursive(graph, neighbor, visited);
}
}
```
### 二、BFS(广度优先搜索)
@@ -51,18 +75,33 @@ BFS **按层遍历**,先访问所有距离为1的节点,再访问距离为2
**实现方式**:使用**队列**(Queue)
```plaintext
function BFS(graph, start):
visited = set()
queue = [start]
visited.add(start)
```cpp
#include <vector>
#include <queue>
#include <iostream>
using namespace std;
while queue is not empty:
node = queue.dequeue()
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.enqueue(neighbor)
// BFS(队列实现)
// graph: 邻接表, start: 起始顶点
void BFS(vector<vector<int>>& graph, int start) {
int n = graph.size();
vector<bool> visited(n, false);
queue<int> q;
q.push(start);
visited[start] = true;
while (!q.empty()) {
int node = q.front();
q.pop();
cout << node << " ";
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
}
```
### 三、BFS vs DFS 对比
@@ -112,13 +151,17 @@ flowchart TD
**原理**:gcd(a, b) = gcd(b, a mod b),直到 b = 0 时 a 即为答案。
```plaintext
function gcd(a, b):
while b != 0:
temp = b
b = a mod b
a = temp
return a
```cpp
// 欧几里得算法(辗转相除法)
// 求两个正整数的最大公约数
int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
```
**示例**:gcd(2146, 8100)
@@ -48,14 +48,20 @@ create time: 2026-06-10 11:11
2. **流程图**:图形化表示,直观清晰。注意:**菱形表示判断/选择**
3. **伪代码**:介于自然语言和编程语言之间,兼顾可读性和精确性
```plaintext
// 伪代码示例:查找数组中的最小值
function findMin(A, n):
minVal = A[1]
for i = 2 to n:
if A[i] < minVal:
minVal = A[i]
return minVal
```cpp
#include <vector>
using namespace std;
// 查找数组中的最小值
int findMin(vector<int>& A, int n) {
int minVal = A[0];
for (int i = 1; i < n; i++) {
if (A[i] < minVal) {
minVal = A[i];
}
}
return minVal;
}
```
> [!tip] 流程图关键符号
@@ -161,17 +167,18 @@ T(0) = 0, T(1) = 0
- 每次返回,从栈顶弹出恢复现场
- **DFS(深度优先搜索)** 也使用栈实现
```plaintext
// Fibonacci的迭代版本(使用栈模拟的思想,但实际是循环)
function fibIterative(n):
if n <= 1: return n
prev = 0
curr = 1
for i = 2 to n:
next = prev + curr
prev = curr
curr = next
return curr
```cpp
// Fibonacci的迭代版本(时间O(n),空间O(1))
int fibIterative(int n) {
if (n <= 1) return n;
int prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
int next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
```
迭代版本将时间复杂度从 O(2ⁿ) 降至 O(n),空间复杂度从 O(n)(递归栈)降至 O(1)。
@@ -58,18 +58,35 @@ flowchart TD
**贪心策略**:**按结束时间从早到晚排序**,优先选择结束时间早的活动。
```plaintext
function activitySelection(activities):
sort activities by finish time
selected = [activities[1]] // 选择第一个结束最早的
lastFinish = activities[1].finish
```cpp
#include <vector>
#include <algorithm>
using namespace std;
for i = 2 to n:
if activities[i].start >= lastFinish:
selected.append(activities[i])
lastFinish = activities[i].finish
struct Activity {
int start, finish;
};
return selected
// 活动选择问题:按结束时间贪心选择最多的互不冲突活动
vector<int> activitySelection(vector<Activity>& activities) {
// 按结束时间从早到晚排序
sort(activities.begin(), activities.end(),
[](const Activity& a, const Activity& b) {
return a.finish < b.finish;
});
vector<int> selected;
selected.push_back(0); // 选择第一个结束最早的
int lastFinish = activities[0].finish;
for (int i = 1; i < (int)activities.size(); i++) {
if (activities[i].start >= lastFinish) {
selected.push_back(i); // 记录被选中的活动下标
lastFinish = activities[i].finish;
}
}
return selected;
}
```
**解释**:选择结束时间最早的活动,可以为后续活动留出最多的时间空间。每选择一个活动,就排除与其冲突的活动,然后继续在剩余活动中选结束最早的。
@@ -83,21 +100,37 @@ function activitySelection(activities):
**贪心策略**:按**价值密度**(vᵢ/wᵢ)从高到低排序,优先取价值密度最高的物品。
```plaintext
function fractionalKnapsack(items, W):
sort items by value/weight ratio descending
totalValue = 0
remaining = W
```cpp
#include <vector>
#include <algorithm>
using namespace std;
for each item in items:
if item.weight <= remaining:
totalValue += item.value
remaining -= item.weight
else:
totalValue += item.value * (remaining / item.weight)
break
struct Item {
double weight, value;
};
return totalValue
// 分数背包问题:按价值密度贪心,可取物品的一部分
double fractionalKnapsack(vector<Item>& items, double W) {
// 按价值密度(value/weight)从高到低排序
sort(items.begin(), items.end(),
[](const Item& a, const Item& b) {
return (a.value / a.weight) > (b.value / b.weight);
});
double totalValue = 0;
double remaining = W;
for (const auto& item : items) {
if (item.weight <= remaining) {
totalValue += item.value; // 整个物品装入
remaining -= item.weight;
} else {
totalValue += item.value * (remaining / item.weight); // 装入一部分
break;
}
}
return totalValue;
}
```
**解释**:因为可以取物品的一部分,所以优先取"性价比"最高的物品一定能得到最优解。剩余容量不够装整个物品时,取一部分即可。