diff --git a/算法设计与分析/复习文档/分治法.md b/算法设计与分析/复习文档/分治法.md index 9c3c152..071388a 100644 --- a/算法设计与分析/复习文档/分治法.md +++ b/算法设计与分析/复习文档/分治法.md @@ -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 +using namespace std; + +// merge函数省略,此处仅展示归并排序的分治骨架 +// void merge(vector& A, int left, int mid, int right) { ... } + +void mergeSort(vector& 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 +#include +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 findMaxMin(vector& 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 +#include +using namespace std; + +// 一次遍历找第二大的数,时间O(n) +int findSecondMax(vector& 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 伪币问题 diff --git a/算法设计与分析/复习文档/动态规划.md b/算法设计与分析/复习文档/动态规划.md index be3d6e3..48f6c67 100644 --- a/算法设计与分析/复习文档/动态规划.md +++ b/算法设计与分析/复习文档/动态规划.md @@ -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 +#include +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& arr) { + int n = arr.size(); + vector 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 +#include +using namespace std; + +int maxMatrixPath(vector>& matrix, int n) { + vector> dp(n, vector(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 +using namespace std; + +int climbStairs(int n) { + if (n <= 2) return n; + vector 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 +using namespace std; + +int fibDP(int n) { + if (n <= 1) return n; + vector 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 +#include +#include +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> dp(m + 1, vector(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 的当前字符"两种情况的较大值。 diff --git a/算法设计与分析/复习文档/回溯法.md b/算法设计与分析/复习文档/回溯法.md index f0a07b4..fa44eca 100644 --- a/算法设计与分析/复习文档/回溯法.md +++ b/算法设计与分析/复习文档/回溯法.md @@ -78,17 +78,26 @@ flowchart TD ### 五、回溯法一般框架 -```plaintext -void backtrack(int t) { +```cpp +#include +using namespace std; + +// 回溯法框架 +// t: 当前搜索深度(第几个决策) +// n: 问题规模(总共需要做的决策数) +// x: 当前解向量 +void backtrack(int t, int n, vector& 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 +#include +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>& graph, vector& 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>& graph, int m, vector& 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 +#include +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& 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& 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皇后是经典的回溯应用。由于每行只放一个皇后,解空间是排列树。剪枝函数检查列冲突和两条对角线冲突,大幅减少搜索空间。 diff --git a/算法设计与分析/复习文档/图搜索与NP复杂性.md b/算法设计与分析/复习文档/图搜索与NP复杂性.md index c210e9a..e003c47 100644 --- a/算法设计与分析/复习文档/图搜索与NP复杂性.md +++ b/算法设计与分析/复习文档/图搜索与NP复杂性.md @@ -21,28 +21,52 @@ DFS 沿着一条路径**尽可能深地**搜索,走不通再回退。 **实现方式**:使用**栈**(Stack)或**递归调用栈** -```plaintext -function DFS(graph, start): - visited = set() - stack = [start] +```cpp +#include +#include +#include +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>& graph, int start) { + int n = graph.size(); + vector visited(n, false); + stack 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 +#include +using namespace std; + +// DFS(递归版本) +// graph: 邻接表, node: 当前顶点, visited: 访问标记数组 +void DFS_recursive(vector>& graph, int node, vector& 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 +#include +#include +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>& graph, int start) { + int n = graph.size(); + vector visited(n, false); + queue 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) diff --git a/算法设计与分析/复习文档/算法基础与复杂度分析.md b/算法设计与分析/复习文档/算法基础与复杂度分析.md index a9a5fe4..849baaa 100644 --- a/算法设计与分析/复习文档/算法基础与复杂度分析.md +++ b/算法设计与分析/复习文档/算法基础与复杂度分析.md @@ -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 +using namespace std; + +// 查找数组中的最小值 +int findMin(vector& 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)。 diff --git a/算法设计与分析/复习文档/贪心算法.md b/算法设计与分析/复习文档/贪心算法.md index b325c62..cef35ba 100644 --- a/算法设计与分析/复习文档/贪心算法.md +++ b/算法设计与分析/复习文档/贪心算法.md @@ -58,18 +58,35 @@ flowchart TD **贪心策略**:**按结束时间从早到晚排序**,优先选择结束时间早的活动。 -```plaintext -function activitySelection(activities): - sort activities by finish time - selected = [activities[1]] // 选择第一个结束最早的 - lastFinish = activities[1].finish +```cpp +#include +#include +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 activitySelection(vector& activities) { + // 按结束时间从早到晚排序 + sort(activities.begin(), activities.end(), + [](const Activity& a, const Activity& b) { + return a.finish < b.finish; + }); + + vector 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 +#include +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& 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; +} ``` **解释**:因为可以取物品的一部分,所以优先取"性价比"最高的物品一定能得到最优解。剩余容量不够装整个物品时,取一部分即可。