dfs解决组合问题
题目链接
题目概述
组合就是从n个元素中抽出r个元素(不分顺序且 r <= n ),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
输入: 一行两个自然数 n、r ( 1 < n < 21,1 < = r < = n )。
输出: 所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,所有的组合也按字典顺序。
例如 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
解题思路
这个题跟解决全排列是一样的思路。先放置一个数,然后在没有放置过的数字当中选择一个放在下一个位置,依次进行。依旧是用递归解决,在选择放置下一个数字时,和解决之前的问题是一样的,还是要在没有放置过的数字当中选一个放置,这样就是把问题分解成为了规模更小的相同的问题。
跟全排列有两点不一样的地方
- 递归终止条件是已经放置了 r 个数字
- 举个例子,比如说 n = 5,r = 3,找到了(1,3,4)这个组合,如果还按之前循环的方式,在 1 到 n 中找未放置过的数字,标记后继续递归,但是我们思考一下,在之后是会找到(3,1,4)或者(4,1,3)等组合,所以我们要保证不会出现这样元素重复的组合,所以在循环时,就要在大于之前放置的数字的中寻找,这样既可以保证一个组合中数字是从小到大排列的,也可以保证不会出现元素重复的组合。
代码如下
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,r;
int a[5];
bool vis[23];
void dfs(int x) {
if(x == r+1) { //结束的条件是 已经放置了r的数字。此时是r+1;
for(int i = 1; i<= r; i++) {
printf("%d ",a[i]);
}
printf("\n");
return;
}
for(int i = a[x-1]+1; i<= n; i++) { //前一个放置的数字为a[x-1],之后的查找在a[x-1]到n中进行。
if(vis[i] == false) {
a[x] = i;
vis[i] = true;
dfs(x+1);
vis[i] = false;
}
}
return;
}
int main() {
scanf("%d%d",&n,&r);
memset(vis,false,sizeof(vis));
a[0] = 0;
dfs(1);
}