基本算法题. 递归实现组合型枚举
从 1∼n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。
输入格式
两个整数 n,m ,在同一行用空格隔开。
输出格式
按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行内的数升序排列,相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7
排在 1 3 6 8
前面)。
数据范围
n>0 ,
0≤m≤n ,
n+(n−m)≤25
输入样例:
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
思考题: 如果要求使用非递归方法,该怎么做呢?
:four_leaf_clover:题解 --- 递归
所谓的组合型枚举,就是枚举的结果与顺序无关,只与组合它的数有关;
没错,这就是典型的从n个中选m个的问题。
只要按照升序枚举的话,所有的结果也是按照升序排序的,并且因为是与顺序无关,不需要去标记哪个数被用过,能不能枚举,只需要记录一下选择的数。
可以看出某一个点的子树其选过的数都是相同的,那么是不是可以记录选择的个数的同时记录一下从哪个数继续枚举下去。
纵观整棵树,不难发现根节点后面几颗子树会出现枚举不了的情况,并且随着n、m的增大,这种情况会越来越多。那有办法能够省去这些判断吗?
剪枝 ,dfs里非常常见,顾名思义就是将不需要的枝条剪去,也就是递归搜索树的不符合条件的子树或者分支。如本题,如果当前选的数加上剩下可以选的数还是不足需要的数,那么直接结束这一分支的搜索。
:memo:代码展示
#include <iostream>
using namespace std;
const int N = 35;
int n , m;
int st[N];
void dfs(int u , int start) {
if (u + n - start < m) return; // 剪枝 u - 1 + n - start + 1 < m
if(u > m) {
for(int i = 1; i <= m; i ++) cout << st[i] << " ";
cout << endl;
return;
}
for(int i = start; i <= n; i ++) {
st[u] = i;
dfs(u + 1 , i + 1);
st[u] = 0;
}
}
int main() {
cin >> n >> m;
dfs(1 , 1);
return 0;
}
【知识点:递归】
反复的回顾算法本质,厚积薄发:
递归 问题:
作为一种解决问题的有效方法,在递归过程中,函数将自身作为子例程调用。
递归的思想是 把一个大型复杂问题层层转化为一个与原问题规模更小的问题,问题被拆解成子问题后,递归调用继续进行,直到子问题无需进一步递归就可以解决的地步为止。