#CSPJCSMN09. 普及组程序专项-递归

普及组程序专项-递归

普及组递归算法专题训练(12题)

题目1:汉诺塔问题

1  #include <iostream>
2  using namespace std;
3  
4  void hanoi(int n, char from, char to, char aux) {
5      if (n == 1) {
6          cout << "Move disk 1 from " << from << " to " << to << endl;
7          return;
8      }
9      hanoi(n - 1, from, aux, to);
10     cout << "Move disk " << n << " from " << from << " to " << to << endl;
11     hanoi(n - 1, aux, to, from);
12 }
13 
14 int main() {
15     int n;
16     cin >> n;
17     hanoi(n, 'A', 'C', 'B');
18     return 0;
19 }

判断题

  1. 汉诺塔问题的时间复杂度是O(2^n)。 ( )
  2. 汉诺塔问题的递归方程是T(n) = 2T(n-1) + 1。 ( )
  3. 对于3个盘子,需要7次移动。 ( )

选择题 4. 对于4个盘子,需要多少次移动? ( )
A. 15
B. 16
C. 31
D. 32

  1. 第9行的hanoi(n - 1, from, aux, to)中,aux参数传递的是:( )
    A. 源柱子
    B. 目标柱子
    C. 辅助柱子
    D. 盘子编号

题目2:斐波那契数列(带行号)

1  #include <iostream>
2  using namespace std;
3  
4  int fib(int n) {
5      if (n <= 1) return n;
6      return fib(n - 1) + fib(n - 2);
7  }
8  
9  int main() {
10     int n;
11     cin >> n;
12     cout << fib(n) << endl;
13     return 0;
14 }

判断题

  1. 第5行的递归基包含n=0和n=1。 ( )
  2. 第6行的递归调用会产生大量重复计算。 ( )
  3. 计算fib(5)时,fib(3)会被计算2次。 ( )

选择题 4. 计算fib(6)时,fib(2)会被计算多少次? ( )
A. 3次
B. 4次
C. 5次
D. 6次

  1. 如果将第5行改为if (n == 0) return 0; if (n == 1) return 1;,会有什么影响? ( )
    A. 功能相同,但更明确
    B. 效率提高
    C. 可能栈溢出
    D. 没有影响

题目3:阶乘计算(带行号)

1  #include <iostream>
2  using namespace std;
3  
4  int factorial(int n) {
5      if (n == 0) return 1;
6      return n * factorial(n - 1);
7  }
8  
9  int main() {
10     int n;
11     cin >> n;
12     cout << factorial(n) << endl;
13     return 0;
14 }

判断题

  1. 第5行是递归基,当n=0时返回1。 ( )
  2. 计算factorial(5)需要6次函数调用(包括第一次调用)。 ( )
  3. 如果将第6行改为return n + factorial(n - 1),会计算1+2+...+n的和。 ( )

选择题 4. 输入n=5时,函数调用的顺序是: ( )
A. factorial(5)→factorial(4)→factorial(3)→factorial(2)→factorial(1)→factorial(0)
B. factorial(0)→factorial(1)→factorial(2)→factorial(3)→factorial(4)→factorial(5)
C. factorial(5)→factorial(1)→factorial(2)→factorial(3)→factorial(4)
D. 以上都不对

  1. 如果输入n=-1,程序会: ( )
    A. 返回1
    B. 无限递归直到栈溢出
    C. 编译错误
    D. 返回0

题目4:字符串反转(带行号)

1  #include <iostream>
2  #include <string>
3  using namespace std;
4  
5  string reverse(string s) {
6      if (s.length() <= 1) return s;
7      return reverse(s.substr(1)) + s[0];
8  }
9  
10 int main() {
11     string s;
12     cin >> s;
13     cout << reverse(s) << endl;
14     return 0;
15 }

判断题

  1. 第6行的递归基处理空字符串和单字符字符串。 ( )
  2. 第7行的s.substr(1)获取从索引1开始到结尾的子串。 ( )
  3. 这个算法在每次递归调用时都会创建新的字符串,效率不高。 ( )

选择题 4. 对于字符串"abc",递归调用过程是: ( )
A. reverse("abc")→reverse("bc")→reverse("c")
B. reverse("abc")→reverse("ab")→reverse("a")
C. reverse("abc")→reverse("bca")→reverse("cab")
D. reverse("abc")→reverse("a")→reverse("b")→reverse("c")

  1. 如果将第7行改为return s[s.length()-1] + reverse(s.substr(0, s.length()-1)),会: ( )
    A. 功能相同,但递归方向不同
    B. 结果错误
    C. 效率更高
    D. 栈溢出

题目5:数组求和(带行号)

1  #include <iostream>
2  using namespace std;
3  
4  int sumArray(int arr[], int n) {
5      if (n <= 0) return 0;
6      return arr[n-1] + sumArray(arr, n-1);
7  }
8  
9  int main() {
10     int n;
11     cin >> n;
12     int arr[n];
13     for (int i = 0; i < n; i++) {
14         cin >> arr[i];
15     }
16     cout << sumArray(arr, n) << endl;
17     return 0;
18 }

判断题

  1. 第5行处理n<=0的情况,包括数组为空的情况。 ( )
  2. 第6行的arr[n-1]访问数组的最后一个元素。 ( )
  3. 这个算法会创建n个递归调用帧。 ( )

选择题 4. 对于数组[1,2,3,4,5],递归调用的返回值顺序是: ( )
A. 0→1→3→6→10→15
B. 15→10→6→3→1→0
C. 5→9→12→14→15→0
D. 0→5→4→3→2→1

  1. 如果将第6行改为return arr[0] + sumArray(arr+1, n-1),会: ( )
    A. 功能相同,但使用指针偏移
    B. 结果错误
    C. 效率更高
    D. 需要修改函数签名

题目6:最大公约数(欧几里得算法)(带行号)

1  #include <iostream>
2  using namespace std;
3  
4  int gcd(int a, int b) {
5      if (b == 0) return a;
6      return gcd(b, a % b);
7  }
8  
9  int main() {
10     int a, b;
11     cin >> a >> b;
12     cout << gcd(a, b) << endl;
13     return 0;
14 }

判断题

  1. 第5行是递归基,当b=0时返回a。 ( )
  2. 第6行利用辗转相除法原理。 ( )
  3. 如果a<b,第一次递归调用会交换a和b的位置。 ( )

选择题 4. 计算gcd(48,18)的递归调用参数序列是: ( )
A. (48,18)→(18,12)→(12,6)→(6,0)
B. (48,18)→(18,48)→(48,18)→...
C. (18,48)→(48,18)→(18,12)→(12,6)→(6,0)
D. (48,18)→(30,18)→(18,12)→(12,6)→(6,0)

  1. 如果将第5行改为if (b == 0 || a == b) return a;,会: ( )
    A. 增加一个递归基,提高效率
    B. 结果可能错误
    C. 减少递归深度
    D. A和C都正确

题目7:快速幂运算(带行号)

1  #include <iostream>
2  using namespace std;
3  
4  double power(double x, int n) {
5      if (n == 0) return 1;
6      if (n % 2 == 0) {
7          double half = power(x, n/2);
8          return half * half;
9      } else {
10         return x * power(x, n-1);
11     }
12 }
13 
14 int main() {
15     double x;
16     int n;
17     cin >> x >> n;
18     cout << power(x, n) << endl;
19     return 0;
20 }

判断题

  1. 第5行是递归基,任何数的0次方等于1。 ( )
  2. 第6-8行处理偶数次幂的情况,使用分治策略。 ( )
  3. 第10行处理奇数次幂的情况,转换为偶数次幂计算。 ( )

选择题 4. 计算power(2,10)的递归调用序列是: ( )
A. power(2,10)→power(2,5)→power(2,4)→power(2,2)→power(2,1)→power(2,0)
B. power(2,10)→power(2,5)→power(2,4)→power(2,2)→power(2,1)
C. power(2,10)→power(2,5)→power(2,2)→power(2,1)→power(2,0)
D. 以上都不对

  1. 如果将第6行改为if (n % 2 == 0 || n == 0),会: ( )
    A. 增加不必要的判断
    B. 提高效率
    C. 结果错误
    D. 没有影响

题目8:集合的所有子集(回溯法)(带行号)

1  #include <iostream>
2  #include <vector>
3  using namespace std;
4  
5  void backtrack(vector<int>& nums, int start, vector<int>& path, vector<vector<int>>& result) {
6      result.push_back(path);
7      
8      for (int i = start; i < nums.size(); i++) {
9          path.push_back(nums[i]);
10         backtrack(nums, i + 1, path, result);
11         path.pop_back();
12     }
13 }
14 
15 vector<vector<int>> subsets(vector<int>& nums) {
16     vector<vector<int>> result;
17     vector<int> path;
18     backtrack(nums, 0, path, result);
19     return result;
20 }
21 
22 int main() {
23     vector<int> nums = {1, 2, 3};
24     auto result = subsets(nums);
25     
26     for (auto& subset : result) {
27         cout << "[";
28         for (int i = 0; i < subset.size(); i++) {
29             cout << subset[i];
30             if (i < subset.size() - 1) cout << ",";
31         }
32         cout << "] ";
33     }
34     return 0;
35 }

判断题

  1. 第6行的result.push_back(path)记录当前路径对应的子集。 ( )
  2. 第9-11行实现选择、递归、撤销选择的回溯过程。 ( )
  3. 第10行的backtrack调用中,i+1确保不会重复选择相同元素。 ( )

选择题 4. 对于nums={1,2,3},递归树的第一层(start=0)会生成哪些子集? ( )
A. {},{1},{2},{3}
B. {},{1},{1,2},{1,2,3}
C. {},{1},{1,2},{1,3},{2},{2,3},{3}
D. 以上都不对

  1. 如果将第6行移到第8行之后,会: ( )
    A. 缺少空子集
    B. 缺少所有子集
    C. 结果相同
    D. 程序错误

题目9:全排列(包含重复元素)(带行号)

1  #include <iostream>
2  #include <vector>
3  #include <algorithm>
4  using namespace std;
5  
6  void permuteUnique(vector<int>& nums, int start, vector<vector<int>>& result) {
7      if (start == nums.size()) {
8          result.push_back(nums);
9          return;
10     }
11     
12     for (int i = start; i < nums.size(); i++) {
13         if (i != start && nums[i] == nums[start]) continue;
14         
15         swap(nums[start], nums[i]);
16         permuteUnique(nums, start + 1, result);
17         swap(nums[start], nums[i]);
18     }
19 }
20 
21 vector<vector<int>> permuteUnique(vector<int>& nums) {
22     vector<vector<int>> result;
23     sort(nums.begin(), nums.end());
24     permuteUnique(nums, 0, result);
25     return result;
26 }
27 
28 int main() {
29     vector<int> nums = {1, 1, 2};
30     auto result = permuteUnique(nums);
31     
32     for (auto& perm : result) {
33         for (int num : perm) {
34             cout << num << " ";
35         }
36         cout << endl;
37     }
38     return 0;
39 }

判断题

  1. 第7-9行是递归基,当start等于数组大小时记录一个排列。 ( )
  2. 第13行跳过重复元素,避免生成重复排列。 ( )
  3. 第15-17行通过交换元素生成不同的排列顺序。 ( )

选择题 4. 对于nums={1,1,2},start=0时,第12-18行的循环会执行几次? ( )
A. 1次
B. 2次
C. 3次
D. 4次

  1. 如果删除第13行,对于nums={1,1,2}会: ( )
    A. 产生重复排列
    B. 结果正确但效率低
    C. 缺少某些排列
    D. 程序错误

题目10:数独求解器(带行号)

1  #include <iostream>
2  #include <vector>
3  using namespace std;
4  
5  bool isValid(vector<vector<char>>& board, int row, int col, char c) {
6      for (int i = 0; i < 9; i++) {
7          if (board[i][col] == c) return false;
8          if (board[row][i] == c) return false;
9          if (board[3 * (row / 3) + i / 3][3 * (col / 3) + i % 3] == c) return false;
10     }
11     return true;
12 }
13 
14 bool solveSudoku(vector<vector<char>>& board) {
15     for (int i = 0; i < 9; i++) {
16         for (int j = 0; j < 9; j++) {
17             if (board[i][j] == '.') {
18                 for (char c = '1'; c <= '9'; c++) {
19                     if (isValid(board, i, j, c)) {
20                         board[i][j] = c;
21                         if (solveSudoku(board)) return true;
22                         board[i][j] = '.';
23                     }
24                 }
25                 return false;
26             }
27         }
28     }
29     return true;
30 }
31 
32 int main() {
33     vector<vector<char>> board = {
34         {'5','3','.','.','7','.','.','.','.'},
35         {'6','.','.','1','9','5','.','.','.'},
36         {'.','9','8','.','.','.','.','6','.'},
37         {'8','.','.','.','6','.','.','.','3'},
38         {'4','.','.','8','.','3','.','.','1'},
39         {'7','.','.','.','2','.','.','.','6'},
40         {'.','6','.','.','.','.','2','8','.'},
41         {'.','.','.','4','1','9','.','.','5'},
42         {'.','.','.','.','8','.','.','7','9'}
43     };
44     
45     solveSudoku(board);
46     
47     for (auto& row : board) {
48         for (char c : row) {
49             cout << c << " ";
50         }
51         cout << endl;
52     }
53     return 0;
54 }

判断题

  1. 第6-10行的isValid函数检查行、列和3x3宫格是否合法。 ( )
  2. 第20-22行尝试放置数字,递归求解,失败时回溯。 ( )
  3. 第25行返回false表示当前分支无解,需要回溯。 ( )

选择题 4. 第9行检查3x3宫格的索引计算中,3 * (row / 3) + i / 3表示: ( )
A. 宫格内的行索引
B. 宫格内的列索引
C. 整个棋盘的行索引
D. 整个棋盘的列索引

  1. 如果将第22行删除,会: ( )
    A. 无法回溯,可能导致死循环
    B. 求解速度更快
    C. 结果可能错误
    D. 程序崩溃

题目11:表达式求值(带行号)

1  #include <iostream>
2  #include <string>
3  #include <stack>
4  #include <cctype>
5  using namespace std;
6  
7  int calculate(string s) {
8      stack<int> nums;
9      stack<char> ops;
10     int num = 0;
11     char sign = '+';
12     
13     for (int i = 0; i < s.length(); i++) {
14         char c = s[i];
15         
16         if (isdigit(c)) {
17             num = num * 10 + (c - '0');
18         }
19         
20         if (c == '(') {
21             int count = 1;
22             int j = i + 1;
23             while (j < s.length() && count > 0) {
24                 if (s[j] == '(') count++;
25                 if (s[j] == ')') count--;
26                 j++;
27             }
28             num = calculate(s.substr(i + 1, j - i - 2));
29             i = j - 1;
30         }
31         
32         if (!isdigit(c) && c != ' ' || i == s.length() - 1) {
33             if (sign == '+') {
34                 nums.push(num);
35             } else if (sign == '-') {
36                 nums.push(-num);
37             } else if (sign == '*') {
38                 int top = nums.top();
39                 nums.pop();
40                 nums.push(top * num);
41             } else if (sign == '/') {
42                 int top = nums.top();
43                 nums.pop();
44                 nums.push(top / num);
45             }
46             
47             sign = c;
48             num = 0;
49         }
50     }
51     
52     int result = 0;
53     while (!nums.empty()) {
54         result += nums.top();
55         nums.pop();
56     }
57     return result;
58 }
59 
60 int main() {
61     string expr = "3*(4+5)-6/2";
62     cout << calculate(expr) << endl;
63     return 0;
64 }

判断题

  1. 第20-30行处理括号,递归计算括号内的表达式。 ( )
  2. 第32-49行遇到运算符或字符串结尾时处理前一个运算符。 ( )
  3. 第47行更新sign为当前字符c,用于下一次运算。 ( )

选择题 4. 对于表达式"2+34",当遇到''时,sign的值是: ( )
A. '+'
B. '*'
C. ' '
D. 不确定

  1. 如果将第28行改为num = calculate(s.substr(i, j - i));,会: ( )
    A. 包含外层括号,导致无限递归
    B. 结果正确
    C. 缺少括号内的内容
    D. 数组越界

题目12:N皇后问题(带行号)

1  #include <iostream>
2  #include <vector>
3  using namespace std;
4  
5  bool isValid(vector<string>& board, int row, int col) {
6      for (int i = 0; i < row; i++) {
7          if (board[i][col] == 'Q') return false;
8      }
9      
10     for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
11         if (board[i][j] == 'Q') return false;
12     }
13     
14     for (int i = row - 1, j = col + 1; i >= 0 && j < board.size(); i--, j++) {
15         if (board[i][j] == 'Q') return false;
16     }
17     
18     return true;
19 }
20 
21 void backtrack(vector<string>& board, int row, vector<vector<string>>& result) {
22     if (row == board.size()) {
23         result.push_back(board);
24         return;
25     }
26     
27     for (int col = 0; col < board.size(); col++) {
28         if (!isValid(board, row, col)) continue;
29         
30         board[row][col] = 'Q';
31         backtrack(board, row + 1, result);
32         board[row][col] = '.';
33     }
34 }
35 
36 vector<vector<string>> solveNQueens(int n) {
37     vector<vector<string>> result;
38     vector<string> board(n, string(n, '.'));
39     backtrack(board, 0, result);
40     return result;
41 }
42 
43 int main() {
44     int n = 4;
45     auto solutions = solveNQueens(n);
46     
47     for (int i = 0; i < solutions.size(); i++) {
48         cout << "Solution " << i + 1 << ":" << endl;
49         for (string row : solutions[i]) {
50             cout << row << endl;
51         }
52         cout << endl;
53     }
54     return 0;
55 }

判断题

  1. 第6-8行检查同一列是否有皇后冲突。 ( )
  2. 第10-12行检查左上对角线是否有皇后冲突。 ( )
  3. 第14-16行检查右上对角线是否有皇后冲突。 ( )

选择题 4. 第30-32行实现的选择、递归、撤销操作中,第32行的作用是: ( )
A. 放置皇后
B. 移除皇后,回溯
C. 检查合法性
D. 记录解

  1. 如果将第28行改为if (isValid(board, row, col)),删除continue,会: ( )
    A. 功能相同,但效率低
    B. 结果错误
    C. 缺少解
    D. 程序错误