递归基础_组合数_输出的各种办法(递归)_(持续更新)

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 , 递归函数内部 c=n , it = r  时停止遍历 ,注意第47行的条件;

组合数比全排列难的地方就在于这个限制 "重复" 条件,首先,对于任何一层的组合,

  1. 遍历首位不会超过n-r+1
  2. 遍历尾部不会超过n,
  3. 遍历总数不会超过r

因此分为两个部分,dfs函数是对尾部(分支)的遍历 , 利用条件3约束, main函数内是对首位穷举, 方式是循环, 利用条件3约束

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

(用全排列改写的组合数输出)

 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 int n;
11 int r;
12 int a[11];
13 bool x[11];
14 void f(int it,int num) {
15 //num表示此时已经排列了几层(栈的深度)
16 //it表示下一个要压入的数
17     a[num]=it;//入栈
18     x[it]=1;//mark
19     if(num==r) {
20         for(int j=1; j<=r; j++) {
21             cout<<a[j]<<" ";
22         }
23         printf("\n");
24     } else {
25         for(int i=it+1; i<=n; i++) {
26             if(x[i]==1)continue;
27             else f(i,num+1);
28         }
29     }
30     x[it]=0;
31 }
32 
33 int main () {
34     cin>>n>>r;
35     memset(x,0,sizeof(x));
36     for(int i=1; i<=n-r+1; i++) {
37         f(i,1);
38     }
39 
40     return 0;
41 }
View Code

进一步化简,  从num=0,开始记录,

 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 int n;
11 int r;
12 int a[11];
13 bool x[11];
14 void f(int it,int num) {
15 //num表示此时已经排列了几层(栈的深度)
16 //it表示下一个要压入的数
17     a[num]=it;//入栈
18     x[it]=1;//mark
19     if(num==r) {
20         for(int j=1; j<=r; j++) {
21             cout<<a[j]<<" ";
22         }
23         printf("\n");
24     } else {
25         for(int i=it+1; i<=n; i++) {
26             if(x[i]==1)continue;
27             else f(i,num+1);
28         }
29     }
30     x[it]=0;
31 }
32 
33 int main () {
34     cin>>n>>r;
35     memset(x,0,sizeof(x));
36     
37     f(0,0);
38     
39 
40     return 0;
41 }
View Code

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

以下为— 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\n",b[r]);
34         //cout<<g++<<" "<<it<<"\n";
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

从0开始更加简洁;

 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\n",b[r]);
34         //cout<<g++<<" "<<it<<"\n";
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 
48         dfs(0,0);
49 
50     }
51     puts("");
52 
53     return 0;
54 }
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<<"\n";
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("\n");
        }
    }
}

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实现(待续)

 

 

posted @ 2020-03-20 04:38  KID-yln  阅读(715)  评论(0编辑  收藏  举报