【题目描述】
排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r≤n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
现要求你用递归的方法输出所有组合。
例如n=5,r=3,所有组合为:
1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5
【输入】
一行两个自然数n、r(1<n<21,1≤r≤n)。
【输出】
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。
【输入样例】
5 3
【输出样例】
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
【解题思路】
可以采用递归的方法,逐步构建组合并在达到所需元素数量时打印它们。下面是详细的解题思路:
1. 递归的基本思想
递归方法的核心思想是分而治之,即将一个大问题分解成多个小问题解决。对于这个问题,需要构建从1到n的所有r个数的组合。可以通过递归地选择或不选择每个数字来实现。
2. 定义递归函数
递归函数的目的是用来尝试每个数并决定是否将其加入当前正在构建的组合中。可以定义一个函数,该函数接收当前组合和下一个尝试的数作为参数。
3. 递归过程
- 选择当前数字:如果选择将当前数字加入组合中,需要更新当前组合并减少剩余需要的数字数量,然后递归调用函数处理下一个数字。
- 不选择当前数字:如果决定不把当前数字加入组合中,只需递归调用函数考虑下一个数字,不改变当前组合或剩余需要的数字数量。
4. 终止条件
递归有两个基本的终止条件:
- 组合完成:如果组合中的数字已经足够(即达到了r个),则打印或返回该组合。
- 数字用尽:如果我们已经考虑到了n,且组合中的数字还不够r个,这条路径不能形成有效的组合,应该终止这条路径的递归。
5. 输出所有组合
通过递归地考虑每个数字加入或不加入组合,我们可以生成所有可能的组合。当一个有效的组合被完整构建时,我们按照要求的格式输出它。
6. 开始递归
递归的起始点是从数字1开始,尝试将它加入组合中,同时也考虑不加入的情况,由此展开所有可能的选择路径。
通过上述步骤,可以系统地探索并输出所有从1到n的数字中选取r个的组合。递归方法的优点是逻辑简单直观,易于实现,而且能够保证覆盖所有可能的组合情况。
【代码实现】
#include <iostream>
using namespace std;
int n, r; // n 是总数,r 是要选择的数的数量
int nums[25]; // 存储当前正在构建的组合
// x 是当前考虑的数字,dep 是当前已经填充到 nums 数组中的数字数量
void dfs(int x, int dep) {
// 如果已经选择了足够的数字,即 r 个
if (r == dep) {
// 打印当前组合
for (int i = 0; i < r; i++) {
cout.width(3); // 设置输出宽度为 3,确保格式对齐
cout << nums[i]; // 输出当前组合中的数
}
cout << endl; // 一个组合打印完毕,输出换行
return; // 返回上一层递归
}
// 如果当前数字 x 超过了 n,说明没有更多的数可以选择了
if (x > n) {
return; // 直接返回
}
// 选择当前数字 x
nums[dep] = x;
dfs(x + 1, dep + 1); // 递归选择下一个数字
// 不选择当前数字 x
dfs(x + 1, dep); // 递归考虑下一个数字,但当前位置不填充
}
int main() {
cin >> n >> r; // 输入 n 和 r
dfs(1, 0); // 从数字 1 开始,当前还没有选择任何数字
return 0;
}