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; }

 

 
 
posted @ 2021-03-31 09:54  徐不二  阅读(380)  评论(0)    收藏  举报