Files
final-exam/算法设计与分析/试题册/试题1.md
T

433 lines
14 KiB
Markdown
Raw Normal View History

2026-06-10 11:08:40 +08:00
---
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]]