DFS-全排列/组合数/子集
对于DFS来说,是和递归绕不开的。
1、全排列
对于全排列,最重要的一点就是标记每个数是否已经被使用,而核心点即对于递归的理解,进而是掌握如何设计。
题目描述
排列与组合是常用的数学方法。先给一个正整数 ( 1 < = n < = 10 )。例如 n=3,所有组合,并且按字典序输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
输入
输入一个整数n( 1<=n<=10)
输出
输出所有全排列
每个全排列一行,相邻两个数用空格隔开(最后一个数后面没有空格)
样例输入 Copy
3
样例输出 Copy
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
代码
#include <stdio.h> const int maxn = 11; bool flag[maxn];//对1~n进行标记,未使用的为false int result[maxn];//存放排序结果 int n; void myPrint(); void DFS(int curr) { if (curr == n) { myPrint(); return; } //这块是精华 for (int i = 1; i <= n; i++) { if (flag[i] == false) { result[curr] = i; flag[i] = true; DFS(curr + 1); flag[i] = false; } } } int main() { while (scanf("%d", &n) != EOF) { DFS(0); } } void myPrint() { for (int i = 0; i <n; i++) { printf("%d ", result[i]); }
核心部分解释
以n=3为例
2、组合数
组合数C(m,n)表示从m个中选n个,共有多少种选法。C(m,n)=C(m-1,n)+C(m-1,n-1)即等于第m个不选,从m-1个中选n个 加上第m个选上,从m-1个中选n-1个。
int DFS2(int m, int n) {
if (n == 0 || m == n) return 1;
return DFS2(m - 1, n) + DFS2(m - 1, n - 1);
}
求个数的话相对简单,接下来要打印出所有可能的组合数则相对复杂一些,但是思想还是很简单的,就是枚举+递归。
比如以m=4,n=3为例,从4个数中选择3个。
可以这样想,先选择1,那现在就变成从后三个中选择两个。
继续如果选择2,则在3和4中选择1个,共两种;如果选择3,则只能选择4,而不是在2和4中选择一个。不会有选择4的情况,因为最大是4,1和4是两个元素。
这样是为了避免重复,这也就是添加标记的原因。因为C(组合)的话是只看元素忽略次序的,flag的添加使得前面选择的元素在后面层递归时不会被选中。
下面进行详细的步骤说明:
m = 4 n = 3
num pre result
1 1 1
1 2 12
2 3 123
2 4 124
1 3 13
3 4 134
1 4 14
2 3 23
3 4 234
3 4 34
因此,最后能print的result有123 124 134 234
代码
#include <stdio.h> const int maxn = 11; bool flag[maxn];//对1~n进行标记,未使用的为false int result[maxn+2];//存放组合数结果 int m,n; void myPrint(); //num表示result中已经存在多少个数,pre表示上一个数是多少 void DFS3(int num, int pre) {
//如果result中已经存够n个,则输出 if (pre == n + 1) { myPrint(); return; }
for (int i = num; i <= m; i++) { if (!flag[i]) { flag[i] = true; result[pre] = i; DFS3(i, pre + 1); flag[i] = false; } } } int main() { while (scanf("%d %d", &m, &n) != EOF) { DFS3(1,1); } } void myPrint() { for (int i = 1; i <=n; i++) { printf("%d ", result[i]); } printf("\n"); }
第二种方法:子集方法。这种方法不使用递归,理解更加容易。把m个数映射到m个二进制位,若集合中包含当前位,则当前位为1,否则为0。判断当前集合中包含多少数 ,即多少位为1,可以通过与运算快速判断。
思路解释
假设m = 3 n =2
则 000 <= k <= 111
k 000 001 010 011 100 101 110 111
k-1 / 000 001 010 011 100 101 110
count1 0 1 1 2 1 2 2 3
其中有两个元素的(count1==2)有3个:011 101 110
i 011 011 011 101 101 101 110 110 110
1<<j 001 010 100 001 010 100 001 010 100
printf(&) 1 2 / 1 / 3 / 2 3
因此结果有三组
1 2
1 3
2 3
代码
#include <iostream> #include <bits/stdc++.h> using namespace std; void print_set(int m1,int n1){ for(int i=0;i<(1<<m1);i++){//1<<m1表示1左移m位,就是给1后面添加m个0(二进制) int k=i; int count1=0;
//计算当前集合中共有多少元素 while(k){ k=k&(k-1); count1++; }
//如果满足n个元素则输出 if(count1 == n1){ for(int j=0;j<m1;j++){ if(i&(1<<j)){ cout<<j+1<<" "; } } cout<<endl; } } } int main() { int m,n; while(scanf("%d %d",&m,&n)!=EOF){ print_set(m,n); } return 0; }

浙公网安备 33010602011771号