查找总计为n的集合的所有子集

时间:2014-01-02 16:10:23

标签: java algorithm time-complexity subset partitioning

以下是我提出的代码:

static void findNumbers(int[] list, int index, int current, int goal, String result)
{ 
  if (list.length < index || current>goal)
          return;
   for (int i = index; i < list.length; i++) {
      if (current + list[i] == goal)   {
         System.out.println(result + " " + String.valueOf(list[i]));
       }
       else if (current + list[i] < goal) {
           findNumbers(list, i + 1, current + list[i], goal, result + " " + String.valueOf(list[i]));
        }
   }
}

使用以下方式调用:

findNumbers(array, starting_index, current_sum_till_now, target_sum, "");

有人可以帮助我弄清楚这段代码的时间复杂性,我相信它是指数级的。

解决此问题的最佳方法是什么?它是否正在使用回溯?

4 个答案:

答案 0 :(得分:4)

有人指出我犯了一个错误。当我应该添加它们时,我正在增加递归调用的复杂性。所以C(N) = C(N-1) + C(N-2) + ...。这同样适用于C(N-1)C(N-2)等。这意味着复杂性不是'O(N!)

这让我从另一个角度思考算法。它正在检查每个可能的子集。由于有2^N - 1个可能的子集(不考虑空子集),因此复杂度为O(2^N),我认为这是您原来的赌注。

答案 1 :(得分:1)

您可以修改您的代码,使其工作原则是“如果数字是好的 - 添加它;忽略任何条件跳过当前数字”。在这种情况下,代码将是:

static void findNumbers(int[] list, int index, int current, int goal, String result)
{ 
  if (list.length <= index || current>goal) // I've added the "=" which is missing in your code.
          return;
  if (current + list[index] == goal)   {
      System.out.println(result + " " + String.valueOf(list[i]));
  }
  else if (current + list[index] < goal) {
      findNumbers(list, index + 1, current + list[i], goal, result + " " + String.valueOf(list[i]));
  }
  findNumbers(list, index + 1, current, goal, result);
}

在这种情况下,复杂度为O(2^n),对n=>5然后O(n!)更好。 正如所指出的,如果对数组进行排序,则复杂性会降低。这意味着您可以将第二个递归调用放在else if中,因为您将确保后面的所有数字都大于当前list[index],这意味着跳过此值没有用,因为此调用的所有后续分支都是如此不会生成任何有效的子集。

在这种情况下,最糟糕的情况是O(2^l),其中l是一个数字的索引,该数字大于您的目标并且在您的数组中,或n如果这样的数字不存在。

电话应该是:findNumbers(list,0,0,goal,"")

答案 2 :(得分:0)

正如刚刚指出的那样,它比N ^ 2更糟,事实上它看起来像是O(N!)。您可以保存一些,因为您可以提前退出某些循环,但保存程度取决于消除可能性的速度。

就更优化的解决方案而言,您将要努力,这是递归的一个很好的例子,因为任何基于循环的构造都将是可怕的。您可以通过预先对数据进行排序来节省一些时间,以便首先获得更大的值,从而更快地达到目标(这将基本上消除列表中立即大于目标的任何内容)。在消除了太大的条目后,我不确定它是否会直接帮助,因为您仍然需要将所有内容与所有内容进行比较,但它可能会改进处理器分支预测。

答案 3 :(得分:0)

这是一种使用动态编程和背包类比的方法: -

  1. 按升序对集合进行排序

  2. 评估子集直到list[i] <= N

  3. 解决背包的容量N和物品的价值和重量为list[i]

  4. 如果最终背包容量N ==最大利润,那么至少存在一个解决方案子集。

  5. 使用成本矩阵回溯所有背包解决方案并获取所有解决方案子集。

  6. 时间复杂度: O(|S|*N + K) |S|- length of set and K is number of subsets.这是伪多项式时间算法。

    注意:问题是NP-hard没有发现多项式时间算法。

    编辑: - 从布尔矩阵中回溯解决方案

    void retrace(int n,boolean[] solution,int target) {
    
       if(n>=0) {
    
            if(table[target][n-1]) {
    
                solution[n] = false;
                retrace(n-1,solution,target); 
            }
    
           if(table[target-numbers[n]][n-1]) {
    
                solution[n] = true;
                retrace(n-1,solution,target-numbers[n]);
           }
       }
    
       else {
           printf("\nsubset:-\n");
           for(int i=0;i<solution.length;i++) {
    
                if(solution[i]) {
                    printf(number[i]+" ");
                }
           }
    
       }
    
    
    }
    
    
    
      Call : - retrace(numbers.length-1,new boolean[numbers.length],target);