在c或c ++中具有负值的子集和

时间:2017-03-28 19:19:29

标签: c algorithm subset-sum

我有这个代码用于查找正值的子集和我搜索到的任何地方我只看到正整数或用高级写的java编写的程序。我想知道如何实现我的C程序将使用负数。实际上,我希望它找到0的总和。我有一个想法

  1. 取集合中的最小值,称之为k
  2. 使用k的绝对值添加集合中的每个元素。
  3. k
  4. 的绝对值加总和
  5. 执行算法。
  6. 但我发现这不行。取集(-5,10)并查看是否有任何子集加起来5.我们将转换(-5,10) - > (0,15)和5-> 10。 -5 + 10 = 5,但0 + 15!= 10

    我在互联网上搜索了很多想法但却找不到答案。

    #include <stdio.h>
    
    typedef int bool;
    #define true 1
    #define false 0
    
    bool isSubsetSum(int set[], int n, int sum) {
        // Base Cases
        if (sum == 0)
            return true;
        if (n == 0 && sum != 0)
            return false;
    
        if (set[n - 1] > sum)
            return isSubsetSum(set, n - 1, sum);
    
        return isSubsetSum(set, n - 1, sum) ||
               isSubsetSum(set, n - 1, sum - set[n - 1]);
    }
    
    int main() {
        int set[] = { -3, 34, -2, 12, 5, 8 };
        int sum = 0;
        int i;
        int n = sizeof(set) / sizeof(set[0]);
        if (isSubsetSum(set, n, sum) == true)
            printf("Found a subset");
        else
            printf("No subset");
        return 0;
    }
    

3 个答案:

答案 0 :(得分:1)

我猜你可以通过删除溢出测试来尝试强力尝试:

#include <stdio.h>

int isSubsetSum(int set[], int n, int sum, int empty_ok) {
    // Base Cases
    if (sum == 0 && empty_ok)
        return 1;

    if (n == 0)
        return 0;

    return isSubsetSum(set, n - 1, sum, empty_ok) ||
           isSubsetSum(set, n - 1, sum - set[n - 1], 1);
}

int main(void) {
    int set[] = { 3, 34, 2, 12, 5, 8 };
    int n = sizeof(set) / sizeof(set[0]);
    int sum = 6;

    if (isSubsetSum(set, n, sum, 0) == true)
        printf("Found a subset");
    else
        printf("No subset");
    return 0;
}

不幸的是,此解决方案的时间复杂度为 O(2 n

这是一个非递归解决方案,最多可设置64个元素:

int isSubsetSum(int set[], int n, int sum) {
    unsigned long long last;

    if (n == 0)
        return sum == 0;

    last = ((1ULL << (n - 1)) << 1) - 1;

    // only find non empty subsets for a 0 sum
    for (unsigned long long bits = 1;; bits++) {
         int s = 0;
         for (int i = 0; i < n; i++) {
             s += set[i] * ((bits >> i) & 1);
         }
         if (s == sum)
             return 1;
         if (bits == last)
             return 0;
    }
}

说明:类型unsigned long long保证至少有64个值位。 bits1last不等,并且除{all} off之外,采用n位的所有可能位模式。对于bits的每个值,我对相应位设置的元素求和,因此测试所有可能的非空子集。

答案 1 :(得分:1)

我真的不了解你的策略。您不应该使用绝对值。 a+b的总和与|a|+|b|的总和没什么关系(好吧有一些关系,但是如果你在某个地方使用它们那么我就错过了它;)。

如果您的算法可以找到加起来为x的正整数的子集,那么您也可以将它用于负数。它不会那么有效,但是通过一个小技巧它可以工作....

首先,为所有数字添加偏移量,使它们全部为正数。现在,您需要查找最多x+y*offset的子集,其中y是子集的大小。例如。你有

A = -1, -3, -2, 6 12, 48

并且您正在寻找一个加起来为0的子集,然后您首先为所有数字添加3,

b = 2, 0, 1, 9, 15, 51

然后尝试查找大小为1的子集,其总和为3,大小为2的子集加起来为6,.... ,大小为4的子集,加起来为12,即

12 = 2+0+1+9    ie    0 = -1 + -3 + -2 + 6

这样做非常有效,因为您必须应用算法N - 次(N =输入大小)。但是,如果您的积极算法允许您修复子集的大小,则可以补偿这种效率损失。

答案 2 :(得分:1)

代码有TBD错误

然而OP要求它留下来。我稍后修好或明天取下。

OP的代码有问题,因为它正在搜索错误的sum

通过找到最小值并抵消set[]的每个元素,问题就变成了正数之一 - 显然OP先前已经解决了。

诀窍是目标sum需要被n*offset

抵消
#include <stdio.h>
#include <stdbool.h>
//typedef int bool;
//#define true 1
//#define false 0

bool isSubsetSum(int set[], int n, int sum, int offset) {
    // Base Cases
    if ((sum  + n*offset) == 0)
        return true;
    if (n == 0 && (sum  + n*offset) != 0)
        return false;

    if (set[n - 1] > sum + n*offset)
        return isSubsetSum(set, n - 1, sum, offset);

    return isSubsetSum(set, n - 1, sum, offset) ||
           isSubsetSum(set, n - 1, sum - set[n - 1], offset);
}

int main() {
    int set[] = { -3, 34, -2, 12, 5, 8 };
    int sum = 0;
    int i;
    int n = sizeof(set) / sizeof(set[0]);
    int min = -3;  // TBD code to find minimum
    for (i = 0; i<6; i++) set[i] -= min;

    if (isSubsetSum(set, n, sum, -min) == true)
        printf("Found a subset");
    else
        printf("No subset");
    return 0;
}
Found a subset