--- tags: - 算法设计与分析 - 试题 create time: 2026-06-10 10:00 --- # 试题 1 — 武汉科技大学《算法设计与分析》 ## 概述 - **课程**:算法设计与分析 - **学校**:武汉科技大学 - **题型**:单选题、简答题、代码分析题、算法设计与编程题 - **知识点**:算法复杂度分析、贪心算法、分治法、动态规划、回溯法、图算法 > [!tip] 学习建议 > 本试题包含代码分析和算法设计题,建议先理解各算法的核心思想,再对照代码验证。重点关注 **n皇后回溯** 和 **最大连续子数组和** 两个经典问题。 ## 正文 ### 一、单选题(每题2分,共10题,20分) **从备选的答案中选出合适的答案,并把答案标号写在答题纸中。** 1. 算法必须具备输入、输出和( )等四个特性。 - A. 可行性 - B. 程序代码 - C. 确定性 - D. 无限循环 2. 算法的复杂度包含( )两种。 - A. 空间复杂度和时间复杂度 - B. 最好复杂度和最坏复杂度 - C. 平均复杂度和最坏复杂度 - D. 最低复杂度和最高复杂度 3. 下列关于时间复杂度的说法中,一般认为哪个复杂度最高的是( )。 - A. O(logn) - B. O(n²) - C. O(n!) - D. O(n) 4. 斐波那契数列的递归实现时间复杂度是( )。 - A. O(n) - B. O(2ⁿ) - C. O(logn) - D. O(nlogn) 5. 在解决一个问题时,可以将问题划分成 k 个子问题,( )。 - A. 分治策略 - B. 贪心策略 - C. 动态规划策略 - D. 回溯策略 6. 哪些是图问题的优化算法可以在多项式时间实现( )。 - A. 最大流最小割算法 - B. 分支限界算法 - C. 贪心算法 - D. 广度优先算法 7. 以下关于回溯法的描述中,不正确的是( )。 - A. 回溯法一般用深度优先策略搜索解空间 - B. 回溯法适用于求解组合数较大的问题 - C. 回溯法的时间复杂度一般为 O(n!) - D. 回溯法在问题的解空间中按深度优先搜索 8. 有一个集合有 n 个元素,如果进行所有的子集的搜索,则当 n=10 时,最多搜索到的子集树的第( )层。 - A. 3 - B. 5 - C. 7 - D. 1 9. 分治法通常基于( )策略。 - A. 递归 - B. 迭代 - C. 贪心 - D. 动态规划 10. 设有一个递归算法 `int f(int n) { if(n<=1) return 1; else return f(n-1)+f(n-2); }`,计算 f(5) 需要调用( )次。 - A. 5 - B. 10 - C. 15 - D. 以上都不对 **参考答案:** | 题号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |------|---|---|---|---|---|---|---|---|---|---| | 答案 | C | A | C | B | A | B | B | B | A | A | > [!warning] 易错点 > 第4题:斐波那契递归实现的时间复杂度是 O(2ⁿ) 而非 O(n)。递归树是二叉树结构,每层展开2个分支,深度为 n,因此总调用次数为指数级。第10题 f(5) 的递归调用次数为 15 次。 > [!tip] 答题技巧 > 算法四要素:**有穷性、确定性、可行性、输入输出**。复杂度从低到高排序:O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(2ⁿ) < O(n!)。 --- ### 二、简答题(每题5分,共6题,30分) **要求:200字以内,不需配代码,不要配图。** #### 1. 中国剩余定理问题(10分) 有一堆苹果,3个3个地数,最后余2个;5个5个地数,最后余3个;7个7个地数,最后余2个。这堆苹果至少有多少个? **参考答案:** 这是典型的中国剩余定理(孙子定理)问题。 设苹果数为 x,则: - x ≡ 2 (mod 3) - x ≡ 3 (mod 5) - x ≡ 2 (mod 7) 由条件1和条件3:x ≡ 2 (mod 3) 且 x ≡ 2 (mod 7),因为 gcd(3,7)=1,所以 x ≡ 2 (mod 21)。 即 x = 21k + 2,代入条件2:21k + 2 ≡ 3 (mod 5) → k ≡ 1 (mod 5) 最小正整数解:k = 1,x = 21×1 + 2 = **23**。 > [!note] 教学提示 > 中国剩余定理的关键在于逐步合并同余方程。先找两个模数的最小公倍数,将两个条件合并为一个,再与第三个合并。 #### 2. 晋级赛问题 N人晋级赛,每人每轮淘汰一名对手。编号为1~N人进行一轮比赛,下一轮的比赛选手的编号规律是:1和N比赛,2和N-1比赛,3和N-2比赛,……。问晋级比赛的轮次和比赛场次。 **参考答案:** - 每轮比赛场次为 floor(N/2),晋级人数为 ceil(N/2) - 配对规则:第 i 号与第 (N+1-i) 号对阵(对称配对) - 总比赛场次 = N - 1(每场淘汰1人) - 轮次 = ceil(log₂N) #### 3. 贪心算法框架 贪心算法的框架是什么? **参考答案:** 贪心算法基本框架: 1. 从问题的初始解出发 2. 循环执行:从候选解集中选取当前最优的候选解 3. 若加入该候选解后解仍可行,则将其加入当前解 4. 重复直到达到目标或候选集为空 ```mermaid flowchart TD A["Start: Empty Solution"] --> B{"Candidate Set Empty?"} B -->|"No"| C["Select Best Candidate"] C --> D{"Solution Still Valid?"} D -->|"Yes"| E["Add to Solution"] E --> B D -->|"No"| F["Discard Candidate"] F --> B B -->|"Yes"| G["Return Solution"] ``` > [!note] 教学提示 > 贪心法的核心性质:**贪心选择性质**(局部最优导致全局最优)和**最优子结构**。并非所有问题都适用贪心法,如0-1背包问题用贪心法得不到最优解。 #### 4. 回溯法基本思想 回溯法的基本思想是什么? **参考答案:** 1. **解空间**:定义问题的解空间树(子集树或排列树) 2. **深度优先搜索**:从根节点出发,按DFS策略搜索解空间 3. **剪枝函数**:用约束函数和限界函数剪去不可能产生最优解的子树 4. **回溯**:当搜索到某节点不满足约束时,回退到上一个节点,尝试其他分支 ```mermaid flowchart TD A["Root Node"] --> B["Expand Node"] B --> C{"Constraint Satisfied?"} C -->|"Yes"| D{"Leaf Node?"} D -->|"Yes"| E["Record Solution"] D -->|"No"| F["Go Deeper"] F --> B C -->|"No"| G["Prune & Backtrack"] G --> H{"More Branches?"} H -->|"Yes"| B H -->|"No"| I["Return to Parent"] ``` #### 5. 候诊椅问题 在某医院的大厅,100个座位的候诊椅排成一排。第一个来的人选择任意一个座位坐下;后来的人要么坐在空位上,要么坐在已坐有人的旁边。当两边都有人时,这个人要坐在这两人的中间。求第100个人最多可以坐几次空位。 **参考答案:** 这是一个经典的递推/概率问题。 分析表明,第100个人最多可以坐到 **1个空位**。 #### 6. 贪心、分治、动态规划比较 请简要描述贪心算法、分治法、动态规划法的基本思想及异同。 **参考答案:** | 特性 | 贪心法 | 分治法 | 动态规划 | |------|--------|--------|----------| | 核心思想 | 局部最优→全局最优 | 分解→递归→合并 | 子问题重叠→表格存储 | | 子问题 | 不回溯 | 独立不重叠 | 重叠子问题 | | 适用条件 | 贪心选择性质 | 可分解可合并 | 最优子结构+重叠子问题 | ```mermaid flowchart LR subgraph Greedy["Greedy"] G1["Local Optimal"] --> G2["Global Optimal"] end subgraph Divide["Divide & Conquer"] D1["Divide"] --> D2["Recurse"] D2 --> D3["Merge"] end subgraph DP["Dynamic Programming"] P1["Subproblems"] --> P2["Table Lookup"] P2 --> P3["Optimal Solution"] end ``` --- ### 三、代码分析题(每题8分,共3题,24分) **要求:300字以内。** #### 1. n皇后问题代码分析 阅读以下 n 后问题的分析代码,指出其算法策略: ```c int COL[20], MAIN_DIAG[40], COUNTER_DIAG[40]; int n, COUNT; int check(int t) { int i, j; i = t; j = COL[t]; while (i > 0) { if (COL[i] == j || (COL[i]-j)==i-t || (j-COL[i])==i-t) return 0; i--; } return 1; } void backtrack(int t) { int i; if (t > n) { for (i = 1; i <= n; i++) printf("%d ", COL[i]); printf("\n"); COUNT++; } else { for (i = 1; i <= n; i++) { COL[t] = i; if (check(t)) backtrack(t + 1); COL[t] = 0; } } } ``` **参考答案:** **算法策略:回溯法(深度优先搜索 + 剪枝)** - `COL[i]` 记录第 i 行皇后所在的列号 - `check(t)` 检查第 t 行放置皇后是否与前面已放置的皇后冲突(同行、同列、同对角线) - `backtrack(t)` 递归地在第 t 行尝试放置皇后,若 t > n 则找到一个解 - **时间复杂度**:O(n!) > [!note] 教学提示 > n皇后问题是回溯法的经典应用。剪枝函数 check() 是关键——它利用"同一列"和"同对角线"约束大幅减少搜索空间。对角线判断公式:`|COL[i]-COL[j]| == |i-j|`。 #### 2. 最大连续子数组和 阅读以下代码,判断代码实现了哪个算法分析设计原理: ```c int MaxSum(int a[], int n) { int sum = 0, b = 0; for (int i = 0; i < n; i++) { if (b > 0) b += a[i]; else b = a[i]; if (b > sum) sum = b; } return sum; } ``` **参考答案:** **算法策略:动态规划(Kadane算法)** - 维护当前子数组和 b,若 b > 0 则继续累加,否则从当前元素重新开始 - 状态转移:`b[i] = max(a[i], b[i-1] + a[i])` - **时间复杂度**:O(n),**空间复杂度**:O(1) > [!tip] 关键理解 > 这是动态规划的经典应用。核心思想:如果前面的子数组和为负数,那么它对后面的结果只会产生负面影响,不如重新开始。这也是"局部最优→全局最优"的体现。 #### 3. max2函数分析 已知 max 函数,求 4 个数 a、b、c、d 中分别相邻两数的最大数,以 max 函数为基本对象,讨论 max2 函数的求解过程,写出 max2 的函数原型(C语言格式),并分析 max2 函数的时间复杂度和空间复杂度。 **参考答案:** ```c void max2(int a, int b, int c, int d, int *max1, int *max2, int *min1, int *min2); ``` 求解过程: 1. 比较 a 和 b → max1=max(a,b), min1=min(a,b) 2. 比较 c 和 d → max2=max(c,d), min2=min(c,d) 3. 从 {max1, max2} 中选较大者为总最大值 - **时间复杂度**:O(1)(最多3次比较) - **空间复杂度**:O(1) --- ### 四、算法设计与编程题(每题13分,共2题,26分) #### 1. 分治法求最大两个数和最小两个数 利用分治算法求一组数据中最大的两个数和最小的两个数。 **参考答案:** **算法策略:分治法** — 将数组分为两半,递归地在每半中找最大/最小的两个数,然后合并。 ```c // 分治法求最大两个数和最小两个数 void findMaxMin(int a[], int l, int r, int *max1, int *max2, int *min1, int *min2) { if (l == r) { *max1 = *min1 = a[l]; *max2 = INT_MIN; *min2 = INT_MAX; return; } if (r - l == 1) { // 基本情况:两个元素 if (a[l] > a[r]) { *max1 = a[l]; *max2 = a[r]; *min1 = a[r]; *min2 = a[l]; } else { *max1 = a[r]; *max2 = a[l]; *min1 = a[l]; *min2 = a[r]; } return; } // 分治:递归处理左右两半 int mid = (l + r) / 2; int lm1, lm2, rm1, rm2, ln1, ln2, rn1, rn2; findMaxMin(a, l, mid, &lm1, &lm2, &ln1, &ln2); findMaxMin(a, mid+1, r, &rm1, &rm2, &rn1, &rn2); // 合并:从4个候选中选最大2个和最小2个 // ...(合并逻辑) } ``` **时间复杂度**:T(n) = 2T(n/2) + O(1) = **O(n)** ```mermaid flowchart TD A["Array: a[l..r]"] --> B{"r-l <= 1?"} B -->|"Yes"| C["Direct Compare"] B -->|"No"| D["Divide: mid = (l+r)/2"] D --> E["findMaxMin(l, mid)"] D --> F["findMaxMin(mid+1, r)"] E --> G["Merge: Select Top 2 Max & Min"] F --> G ``` > [!note] 教学提示 > 分治法的关键是**分解**、**递归求解**、**合并**三步。本题的合并步骤需要从4个候选值中选出最大的两个和最小的两个,最多需要6次比较。 #### 2. 方阵最大路径和(动态规划) 在一个 n×n 的方阵中,填入 1, 2, 3... n² 个数字。方阵最下方有一人,此人走完 n 个方格后必须在最下方出来。任意时刻,每个人走过一方格,必须走到此方格的8个方向之一继续走。输入为 n,找出使所有数相加之和为最大的路径。 **参考答案:** **算法策略:动态规划** — 从最后一行向上递推,每个格子的最大路径和 = 自身值 + 下方三格中的最大值。 ```c int maxPathSum(int n, int grid[n][n]) { int dp[n][n]; for (int j = 0; j < n; j++) dp[n-1][j] = grid[n-1][j]; // 初始化最后一行 for (int i = n-2; i >= 0; i--) { for (int j = 0; j < n; j++) { dp[i][j] = grid[i][j]; int maxDown = dp[i+1][j]; // 正下方 if (j > 0 && dp[i+1][j-1] > maxDown) maxDown = dp[i+1][j-1]; // 左下方 if (j < n-1 && dp[i+1][j+1] > maxDown) maxDown = dp[i+1][j+1]; // 右下方 dp[i][j] += maxDown; } } int result = dp[0][0]; for (int j = 1; j < n; j++) if (dp[0][0+j] > result) result = dp[0][j]; return result; } ``` **时间复杂度**:O(n²),**空间复杂度**:O(n²) ```mermaid flowchart TD A["Grid n x n"] --> B["Initialize Bottom Row"] B --> C["For i = n-2 downto 0"] C --> D["dp[i][j] = grid[i][j] + max(down-left, down, down-right)"] D --> E["Answer = max(dp[0][0..n-1])"] ``` > [!tip] 关键理解 > 动态规划的**状态定义**:`dp[i][j]` 表示从第 i 行第 j 列出发到最下方的最大路径和。**状态转移方程**:`dp[i][j] = grid[i][j] + max(dp[i+1][j-1], dp[i+1][j], dp[i+1][j+1])`。这是典型的"自底向上"递推。 --- ## 关联笔记 - [[算法设计与分析/试题册/试题2]] - [[算法设计与分析/试题册/试题3]] - [[算法设计与分析/试题册/试题4]] - [[算法设计与分析/试题册/试题5]]