子集和问题(动态规划解法)

/*给出一个集合,以及一个测试数,求给集合的子集的和是否等于该数(如:给出 7 10 11 20 9,以及16,则输出ture,给出15则输出false)*/
第一种:(递归版)

#include<stdio.h>
#include<string.h>
int solve_dp(int*p,int n,int key);
int solve_dp(int*p,int n,int key)
{
    if(key==0)
        return 1;//当key==0时,说明有这样一个子集(当然,若输入的key为0,则没有意义)
   else if(n==0)
        return p[0]==key;//当找到第0个时,key还不为0,判断p[0]==key?,是则存在,否则不存在
    else if(p[n]>key)
        return solve_dp(p,n-1,key);//如果第n个数大于key则放弃这个数,跳到下一个数
    else
        return solve_dp(p,n-1,key-p[n])||solve_dp(p,n-1,key);//否则若第n个数比key小,则有两条路,
//选或不选,选择这个数就让key减去它,否则跳过这个数,只要有一条路成立,则说明存在这样一个子集。
}
int main()
{
    int n,i,key;
    printf("Input the length\n");
    scanf("%d",&n);
    int *p = malloc(n*sizeof(int));
    printf("Input the array\n");
    for(i=0; i<n; i++)
        scanf("%d",&p[i]);
    for(i=0; i<100; i++)//进行多次测试
    {
        printf("Input the test number\n");
        scanf("%d",&key);
        if(solve_dp(p,n-1,key))//数组从0到n-1
            printf("True\n");
        else
            printf("False\n");
    }
    return 0;

第二种(动态规划版/查表法)
用一个二维数组来保存结果,避免重复过程。
如下图,填完后输出右下角值(opt[n-1][key])
(key值为5,p数组:10,5,2,7,9)

出口:1.当n==0时,判断p[0]==key?
          2.当key==0时,真
          3.当p[i]>key时,不选p[i]
          4.否则,选择p[i]||不选p[i]

P数组 \  key值

0

1

2

3

4

5

 

P[0]: 10

1

0

0

0

0

0

P[1]: 5

1

0.

0

0

0

1

P[2]: 2

1

0

1

0

0

1

P[3]: 7

1

0

1

0

0

1

P[4]: 9

1

0

1

0

0

1

#include<stdio.h>
#include<string.h>
int solve_dp(int *p,int n,int key);
int solve_dp(int *p,int n,int key)
{
    int i,j;
    int**opt=malloc(n*sizeof(int));//定义二级指针(行数)
    for(i=0; i<n; i++)   //(每行都有多少列)
        opt[i]=malloc((key+1)*sizeof(int *));  //注意二维数组的动态开辟方法
    for(i = 0; i <= key; i++)
        opt[0][i] = 0; //第一行(对应递归版n为0时的情况)
    for(i = 0; i < n; i++)
        opt[i][0] = 1; //当key为0时,该列都为1

    opt[0][p[0]]=1;// 但如果p[0]==key,则该值为1(覆盖掉原来的0,上面注释为红色部分)
    for(i=1; i<n; i++)//遍历p数组,以及二维数组的列
        for(j=1; j<=key; j++)//二维数组的行
        {
             if(p[i]>j)
                opt[i][j]=opt[i-1][j];
             else
                opt[i][j]=opt[i-1][j-p[i]]||opt[i-1][j];
        }
for(i=0;i<n;i++)
{
    for(j=0;j<=key;j++)
        printf("%d ",opt[i][j]);
    putchar('\n');
}//打印二维数组(用于验证)
    j=opt[n-1][key];//保存结果
    free(opt);//及时释放掉二维数组
    return j;
}
int main()
{
    int n,i,key;
    printf("Input the length\n");
    scanf("%d",&n);
    int *p=malloc(n*sizeof(int));
    printf("Input the array\n");
    for(i=0; i<n; i++)
        scanf("%d",&p[i]);
    for(i=0; i<20; i++)
    {
        printf("Input the test number\n");
        scanf("%d",&key);
        if(solve_dp(p,n,key))
            printf("True\n");
        else
            printf("False\n");
    }
    return 0;
}


  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,让我们来讨论使用回溯法解决子集问题。在子集问题中,给定一个集合以及一个目标总和,我们需要找出集合中所有元素的一个子集,其元素的和等于目标总和。 回溯法是一种找出所有(或一部分)解的经典算法。它通过递归地尝试所有可能的解决方案来解决问题。在具体实现中,回溯法采用的是深度优先搜索,即先访问一个节点的所有子节点,然后再回溯到父节点(也就是上一级节点)。 对于子集问题,我们可以使用如下的回溯法算法框架: 1. 定义一个dfs函数,参数包括当前的元素、当前的总和以及目标总和。 2. 判断当前元素是否是集合中的最后一个元素,如果是则判断当前总和是否等于目标总和,如果是则将当前元素集合加入到结果集中,否则直接返回即可。 3. 如果当前元素不是集合中的最后一个元素,则分两种情况讨论:选或不选当前元素。如果选当前元素,则在当前总和上加上当前元素的值,继续向下递归;如果不选当前元素,则直接向下递归。递归完成后,需要回溯到上一级节点,即撤销最后一次选择操作,并将当前元素集合从路径中删除。 4. 为了提高效率,我们可以在递归过程中记录当前元素的下标,每次递归从下标+1的元素开始。 下面是使用该算法框架的具体实现(使用Python语言): def backtrack(nums, target): def dfs(curr_list, curr_sum, start): if start == len(nums) and curr_sum == target: result.append(curr_list) if start == len(nums): return dfs(curr_list + [nums[start]], curr_sum + nums[start], start + 1) dfs(curr_list, curr_sum, start + 1) result = [] dfs([], 0, 0) return result 其中nums是给定的集合,target是目标总和。我们定义了一个内部函数dfs,用于递归计算所有符合要求的子集。curr_list表示当前的子集,curr_sum表示当前子集的元素和,start表示当前元素的下标。 在dfs函数中,首先判断是否到达集合的末尾,如果是则判断当前子集的元素和是否等于目标总和,如果是则将当前子集加入到结果集中。否则分两种情况讨论:选或不选当前元素。如果选当前元素,则在当前子集的元素和上加上当前元素的值,继续向下递归;如果不选当前元素,则直接向下递归。递归完成后,需要回溯到上一级节点,即撤销最后一次选择操作,并将当前元素从当前子集中删除。 最后,我们调用dfs函数,返回所有符合要求的子集。 注意,在实际应用中,需要考虑优化算法的速度和占用的空间。而对于子集问题,回溯法是最直观、最容易理解的解法,可以为其他问题解法提供启示。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值