查找功率集的加权子集和的最大值

时间:2015-09-09 17:59:12

标签: javascript algorithm optimization mathematical-optimization subset-sum

我为输入设置了稀疏功率集(即已预先排除了一些组合)。电源组中的每个条目都有一定的分数。我希望找到涵盖所有要点并最大化总分的组合。

例如,假设输入生成如下:

function powerset(ary) {
  var ps = [[]];
  for (var i = 0; i < ary.length; i++) {
    for (var j = 0, len = ps.length; j < len; j++) {
      ps.push(ps[j].concat(ary[i]));
    }
  }
  return ps;
}

function generateScores() {
  var sets = powerset([0, 1, 2, 3]);
  sets.pop() //remove the last entry to make it "sparse"
  var scores = {};
  for (var i = 1; i < sets.length; i++) { //skip 0-len
    var set = sets[i];
    var val = 0;
    for (var j = 0; j < set.length; j++) {
      val |= (1 << set[j]);
    }
    scores[val] = ~~Math.pow(((Math.random()+1)*4),set.length);
  }
  return scores;
}
var scores = generateScores();

输出看起来像这样:

{
  "1": 7,
  "2": 4,
  "3": 36,
  "4": 5,
  "5": 32,
  "6": 50,
  "7": 84,
  "8": 4,
  "9": 30,
  "10": 50,
  "11": 510,
  "12": 47,
  "13": 73,
  "14": 344,
}

由于顺序无关紧要,我可以将组合转换为位掩码&amp;用它作为关键。所以要阅读表格:“3”的关键是011是基数2,这意味着链接0-1得到36分,而0个别+ 1个单独产生总和11,因此链接,0-1,大于其各个部分的总和0,1

在这样做的过程中,我将其减少为加权子集和问题,其目标是找到总和为15的每个组合(相当于基数2中的1111)&amp;然后采取最大这就是我被困住的地方。我尝试使用动态编程,但由于随机性,我不知道如何进行任何减少。例如,1-2可能优于1,2(在上表中,“3”的得分高于“1”+“2”)。但是1-3,2可能比1-2,31-2-3更好。

我如何才能有效地找到最佳组合? (蛮力不可行)。对于此示例,解决方案将是“11”+“4”,总共515。

2 个答案:

答案 0 :(得分:0)

一种不同的方法(一如既往):

首先想到:

您可以获得总和小于单个重量的每个值的权重。因此w a + w b &lt; w c 和a + b = c,这导致了一个简单的权重系统。

第二个想法:

为了更好地理解权重,它必须是自然数,也就是整数。

第三个想法:

为什么不使用数字本身进行少量缩减以使总和小于单个权重。

合:

我取数字并取值作为重量。另外,我将它们的值减少1,所以:

  

a = 1,b = 2,c = 3 w a + w b &lt;瓦特<子> C
  w a = 0,w b = 1,w c = 2 =&gt; 0 + 1&lt; 2

公式:weight n = n - 1

证明:

对于每个加数,你得到-1的malus。因此,对于更多的加数,您得到的数字小于原始数字的权重。

另一个例子:

权重 15 (14)应该大于权重 4 (3)和权重 11 (10)之和。 / p>

数字:14&gt; 3 + 10

我的意思是,这里不需要程序代码。

答案 1 :(得分:0)

对于那些使用Google搜索的人,我使用了@josilber提供的答案,没有递归&amp;具有重叠保护(见下文)。由于JS中的递归深度限制为1000,我不得不使用循环。不幸的是,对于我的用例,我仍然没有内存,所以看起来我必须使用一些启发式方法。

var scores = {1: 7, 2: 4, 3: 36, 4: 5, 5: 32, 6: 50, 7: 84, 8: 4, 9: 30, 10: 50, 11: 510, 12: 47, 13: 73, 14: 344};
var S = [];
var keys = Object.keys(scores);
for (i = 0; i < keys.length; i++) {
  S.push([parseInt(keys[i]), scores[keys[i]]]);
}

var n = Math.pow(2,range.length) -1;  // Target sum
var k = S.length;  // Number of weights

// best[i, j] is scored in position i*(k+1) + j
var best = [];

// Base case
for (var j = 0; j <= k; j++) {
  best.push([[], 0]);
}

// Main loop
for (var i = 1; i <= n; i++) { 
  best.push(false);  // j=0 case infeasible
  for (j = 1; j <= k; j++) {
    var opt1 = best[i * (k + 1) + j - 1];
    var opt2 = false;
    if (S[j - 1][0] <= i) {
      var parent = best[(i - S[j - 1][0]) * (k + 1) + j - 1];
      if (parent !== false) {
        opt2 = [parent[0].slice(), parent[1]];
        var child = S[j - 1];
        var opt2BitSig = 0;
        for (var m = 0; m < opt2[0].length; m++) {
          opt2BitSig |= opt2[0][m];
        }
        if ((opt2BitSig & child[0])) {
          opt2 = false;
        } else {
          opt2[0].push(child[0]);
          opt2[1] += child[1];
        }
      }
    }
    if (opt1 === false) {
      best.push(opt2);
    } else if (opt2 === false || opt1[1] >= opt2[1]) {
      best.push(opt1);
    } else {
      best.push(opt2);
    }
  }
}

console.log(JSON.stringify(best[n * (k + 1) + k]));