递归基础—组合数 / 排列数—输出的各种办法(dfs/递归/bfs)

Posted kid-yln

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了递归基础—组合数 / 排列数—输出的各种办法(dfs/递归/bfs)相关的知识,希望对你有一定的参考价值。

B: 部分和问题***(注意部分和 ! = 任意子区间求和不一样)

描述   给你N个数,问你能不能从其中取出一些,让它们的和为K.

输入

第一行包括两个数,N,K,分别代表数字个数,以及和为K. 接下来N行,每行一个数字.

输出

如果能选出一些数和为K, 输出YE5,  否则,输出N0

样例

输入:

4 0

1 -1  2  3

输出:

YE5

输入:

2 2

1 -3

输出:

N0

本题求组合数和,注意pe之外,思维比较基础,办法很多,以下为利用dfs思想实现的一种办法

如果求组合数输出各种可能,直接递归是不行的,那样智能遍历,需要一个媒介数组,存放每种可能,因为要每次返回都要利用上一层的东西

观察 1 2 3 4  对于C(4,3)= C(n,r)

1 2 3

1 2 4

1 3 4

2 3 4

对于(a,b,c)型,最终在 首位   a =  n-r+1 = 4-3+1 = 2 时停止遍历 ,注意第47行的条件;

组合数比全排列难的地方就在于这个限制 "重复" 条件,首先,对于任何一层的组合,遍历首位不会超过n-r+1

如果用bool 标记已经找过的数,思路并不难和排列差不多,标记每次遍历到直接跳过就行了,返回的时候在函数末尾消除标记就可

但是如果找规律,就需要明确找的数都只能比上一层的大,然后在此基础上循环到最大值n

 

以下为— dfs 利用栈—实现的  输出n个数的 全组和  代码:

技术图片
 1 #include <iostream>
 2 #include <cstring>
 3 #include <queue>
 4 #include <cstdio>
 5 #include <cmath>
 6 #include <map>
 7 #include <algorithm>
 8 typedef long long ll;
 9 using namespace std;
10 const int m=25;
11 ll a[m];
12 bool t=0;
13 ll sum=0;
14 int n;
15 ll k;
16 //用数组下标输出组合数!!!
17 //C(n,k)=C(n=1,k-1)
18 
19 
20 //1 2 3 4 5 6
21 //5 22 2 4 6 8 10
22 //5  2 4 6 8 10
23 ll b[m];
24 //b[m]看作一个栈
25 int g=1;
26 ll r;
27 
28 void dfs(int num,int it) {
29 
30     b[it]=a[num];
31     if(it==r) {
32         for(int i=1; i<r; i++)printf("%lld ",b[i]);
33         printf("%lld
",b[r]);
34         //cout<<g++<<" "<<it<<"
";
35     } else {
36         for(int i=num+1; i<=n; i++) {
37             dfs(i,it+1);
38         }
39     }
40 }
41 
42 int main() {
43 
44     scanf("%d",&n);
45     for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
46     for(r=1; r<=n; r++) {
47         for(int i=1; i<=n-r+1; i++) {
48             sum=0;
49             dfs(i,1);
50         }
51     }
52     puts("");
53 
54     return 0;
55 }
View Code

在这基础上,直接把每次得到的序列求和,可以完成这题,

技术图片
 1 #include <iostream>
 2 #include <cstring>
 3 #include <queue>
 4 #include <cstdio>
 5 #include <cmath>
 6 #include <map>
 7 #include <algorithm>
 8 typedef long long ll;
 9 using namespace std;
10 const int m=25;
11 ll a[m];
12 bool t=0;
13 ll sum=0;
14 int n;
15 ll k;
16 //用数组下标输出组合数!!!
17 //C(n,k)=C(n=1,k-1)
18 
19 
20 //1 2 3 4 5 6
21 //5 22 2 4 6 8 10
22 //5  2 4 6 8 10
23 ll b[m];
24 //b[m]看作一个栈
25 int g=1;
26 ll r;
27 
28 
29 
30 void f(int no,int now) {
31     if(t==1)return;
32     b[now]=a[no];//更新入栈,
33     if(now==r) { //栈满检查
34         sum=0;
35         for(int i=1; i<=r&&t==0; i++) {
36             sum+=b[i];
37             if(sum==k)t=1;
38         }
39     } else {
40         for(int i=no+1; i<=n; i++) {
41             f(i,now+1);
42         }
43     }
44 }
45 
46 int main() {
47 
48     scanf("%d%lld",&n,&k);
49     for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
50     for(r=1; r<=n; r++) {
51         for(int i=1; i<=n-r+1; i++) {
52             sum=0;
53             f(i,1);
54         }
55     }
56     if(t==1)cout<<"YE5";
57     else cout<<"N0";
58 
59     puts("");
60 
61 
62     return 0;
63 }
View Code

当然,求和,其实是不需要求这个序列的,遍历每一种可能,之后不需要把序列存下来,直接检查和,就可以满足了,

不含媒介数组的办法(注意第 44 行,sum减回来当前值的操作在每次到底之后都要执行)

技术图片
 1 #include <iostream>
 2 #include <cstring>
 3 #include <queue>
 4 #include <cstdio>
 5 #include <cmath>
 6 #include <map>
 7 #include <algorithm>
 8 typedef long long ll;
 9 using namespace std;
10 const int m=25;
11 ll a[m];
12 bool t=0;
13 ll sum=0;
14 int n;
15 ll k;
16 //用数组下标输出组合数!!!
17 //C(n,k)=C(n=1,k-1)
18 
19 
20 //1 2 3 4 5 6
21 //5 22 2 4 6 8 10
22 //5  2 4 6 8 10
23 ll b[m];
24 //b[m]看作一个栈
25 int g=1;
26 ll r;
27 
28 void f2(int no,int now) {
29     sum+=a[no];//更新入栈,
30     //cout<<"sum="<<sum<<" ";
31 
32     if(sum==k||t==1) {
33         t=1;
34         return;
35     }
36     if(now<r) { //栈满,
37         //cout<<"sum="<<sum<<"
";
38         //
39 
40         for(int i=no+1; i<=n; i++) {
41             f2(i,now+1);
42         }
43     }
44     sum-=a[no];//弹出!!!小心少了这一步 
45 }
46 int main() {
47 
48     scanf("%d%lld",&n,&k);
49     for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
50     
51     for(r=1; r<=n; r++) {
52         for(int i=1; i<=n-r+1; i++) {
53             sum=0;
54             f2(i,1);
55         }
56     }
57     if(t==1)cout<<"YE5";
58     else cout<<"N0";
59     puts("");
60     return 0;
61 }
View Code

另外的写法——来源:https://blog.csdn.net/randyjiawenjie/article/details/6784355

问题:求n个数中K个数的组合,假设函数原型为 int combination(int n,int k),其中 n的范围为 1……n,

例如:combination(5,3) 要求输出:543、542、541、531、532、521、432、431、421、321

如果输出时有用到数组,其空间需要在开始动态分配好,结束时释放。还是利用递归,关键:c(m,k) = c(m -1 , k- 1) + c(m - 2, k - 1) + ...+ c(k - 1.k - 1)

 

技术图片
# include <stdio.h>
# define MAXN 100
int a[MAXN];
/**
 * 组合问题
 *问题描述:找出从自然数1、2、……、m中任取k个数的所有组合。
 */
void comb(int m, int k) {
    int i, j;
    for (i = m; i >= k; i--) {
        a[k] = i;
        if (k > 1)
            comb(i - 1, k - 1);
        else {
            for (j=a[0];j>0;j--)
            printf("%4d",a[j]);
            printf("
");
        }
    }
}

int main() {
    a[0] = 3;
//    int a[] = {1,2,3,4,5};
    comb(5, 3);
    return 0;
}
View Code

凡是含—输出该序列—的题目都需要一个 “栈” 存放结果,区别在于,他把上界n-r+1写进了递归函数中,

表示为 for (i = m; i >= k; i--),思维更复杂,这个每次递归的时候上界都会变,

另一种写法:也是利用01标记法,但是实现写了一个表 + bfs实现   https://blog.csdn.net/cao2219600/article/details/79587306

同理也可以使用 01 标记+ dfs实现(待续)

 

 

以上是关于递归基础—组合数 / 排列数—输出的各种办法(dfs/递归/bfs)的主要内容,如果未能解决你的问题,请参考以下文章

递归:排列

20181030T1排列树树形结构+组合数

nyoj 32 组合数

递归基础_全排列(一般递推实现)

全排列 (递归求解+字典序) java 转载

BZOJ2111[ZJOI2010]Perm 排列计数 组合数