查找幂集的加权子集和的最大值
Finding max value of a weighted subset sum of a power set
我有一个输入的稀疏幂集(即一些组合已被预先排除)。幂集中的每个条目都有一定的分数。我想找到涵盖所有点并使总分最大化的组合。
例如,假设输入生成如下:
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,
}
由于顺序无关紧要,我可以将组合转换为位掩码并将其用作密钥。所以要读 table:“3”的一个键是 011
以 2 为底,这意味着链接 0-1 会产生 36 分,而 0 单独 + 1 单独产生总和 11 ,因此链接 0-1
大于其各部分的总和 0,1
.
在这样做的过程中,我将其简化为加权子集求和问题,目标是找到总和为 15 的每个组合(相当于基数 2 中的 1111
),然后取最大限度。这就是我被困的地方。我尝试使用动态规划,但由于随机性,我看不出如何减少。例如,1-2
可能比1,2
好(在上面的table中,“3”比“1”+“2”得分高)。但是 1-3,2
可能比 1-2,3
或 1-2-3
) 更好。
我怎样才能有效地找到最佳组合? (蛮力是不可行的)。对于这个例子,解决方案是“11”+“4”,总共 515。
您想找到总和为 15 且没有任何重叠位的元素组合,使所选元素的分数最大化。
为此,定义一个函数 bestSubset(use, valid)
,输入一组需要使用的元素和一个有效包含但尚未考虑的元素子集。它通过考虑有效集合中的元素 s
递归操作,考虑使用 s
或不使用它的情况(如果使用它,则任何重叠位的元素都不能再被使用)使用过)。
这是一个 javascript 实现:
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 = [];
for (var prop in scores) {
S.push([parseInt(prop), scores[prop]]);
}
var n = 15; // Target sum
var k = S.length; // Number of weights
function bestSubset(use, valid) {
if (valid.length == 0) {
var weightSum = 0;
var scoreSum = 0;
var weights = [];
for (var ct=0; ct < use.length; ct++) {
weightSum += S[use[ct]][0];
weights.push(S[use[ct]][0]);
scoreSum += S[use[ct]][1];
}
if (weightSum == n) {
return [weights, scoreSum];
} else {
return false;
}
}
// Don't use valid[0]
var valid1 = [];
for (ct=1; ct < valid.length; ct++) {
valid1.push(valid[ct]);
}
var opt1 = bestSubset(use, valid1);
// Use valid[0]
var use2 = JSON.parse(JSON.stringify(use));
use2.push(valid[0]);
var valid2 = [];
for (ct=1; ct < valid.length; ct++) {
if ((S[valid[0]][0] & S[valid[ct]][0]) == 0) {
valid2.push(valid[ct]);
}
}
var opt2 = bestSubset(use2, valid2);
if (opt1 === false) {
return opt2;
} else if (opt2 === false || opt1[1] >= opt2[1]) {
return opt1;
} else {
return opt2;
}
}
var initValid = [];
for (var ct=0; ct < S.length; ct++) {
initValid.push(ct);
}
alert(JSON.stringify(bestSubset([], initValid)));
这 returns 组 [4, 11]
得分为 515,如您在原始 post 中所标识。
从非稀疏情况下的一些计算实验(又名 d
数字和目标 (2^d)-1
,包括所有数字 1, 2, ..., (2^d)-1
),我发现这在位数(它在递归函数顶部检查有效性的次数是O(e^(1.47d))
)。这比您分别考虑包括或不包括每个数字 1, 2, ..., (2^d)-1
的蛮力情况要快得多,后者在双指数运行时运行 -- O(2^2^d)
.
不同的方法(一如既往):
第一个想法:
您可以为总和小于单个权重的每个值获取一个权重。因此 wa + wb < wc 和 a + b = c,这导致 a简单的权重系统。
再三考虑:
为了更好地理解权重,它必须是自然数,也就是整数。
第三个想法:
为什么不直接使用数字本身进行小幅缩减,使总和小于单个权重。
在一起:
我取数字,取值作为权重。此外,我将它们的值减 1,因此:
a = 1, b = 2, c = 3 wa + wb < wc
wa = 0, wb = 1, wc = 2 => 0 + 1 < 2
公式:权重n = n - 1
证明:
对于每个被加数,您都会得到 -1 的 malus。所以,对于更多的加数,你得到的数字比原始数字的权重更小。
另一个例子:
weight15(14)应该大于weight4(3)和weight之和11 (10).
人数:14 > 3 + 10
我的意思是,这里不需要程序代码。
对于那些用谷歌搜索这个的人,我使用了@josilber 提供的答案,没有递归和重叠保护(见下文)。由于 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]));
我有一个输入的稀疏幂集(即一些组合已被预先排除)。幂集中的每个条目都有一定的分数。我想找到涵盖所有点并使总分最大化的组合。
例如,假设输入生成如下:
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,
}
由于顺序无关紧要,我可以将组合转换为位掩码并将其用作密钥。所以要读 table:“3”的一个键是 011
以 2 为底,这意味着链接 0-1 会产生 36 分,而 0 单独 + 1 单独产生总和 11 ,因此链接 0-1
大于其各部分的总和 0,1
.
在这样做的过程中,我将其简化为加权子集求和问题,目标是找到总和为 15 的每个组合(相当于基数 2 中的 1111
),然后取最大限度。这就是我被困的地方。我尝试使用动态规划,但由于随机性,我看不出如何减少。例如,1-2
可能比1,2
好(在上面的table中,“3”比“1”+“2”得分高)。但是 1-3,2
可能比 1-2,3
或 1-2-3
) 更好。
我怎样才能有效地找到最佳组合? (蛮力是不可行的)。对于这个例子,解决方案是“11”+“4”,总共 515。
您想找到总和为 15 且没有任何重叠位的元素组合,使所选元素的分数最大化。
为此,定义一个函数 bestSubset(use, valid)
,输入一组需要使用的元素和一个有效包含但尚未考虑的元素子集。它通过考虑有效集合中的元素 s
递归操作,考虑使用 s
或不使用它的情况(如果使用它,则任何重叠位的元素都不能再被使用)使用过)。
这是一个 javascript 实现:
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 = [];
for (var prop in scores) {
S.push([parseInt(prop), scores[prop]]);
}
var n = 15; // Target sum
var k = S.length; // Number of weights
function bestSubset(use, valid) {
if (valid.length == 0) {
var weightSum = 0;
var scoreSum = 0;
var weights = [];
for (var ct=0; ct < use.length; ct++) {
weightSum += S[use[ct]][0];
weights.push(S[use[ct]][0]);
scoreSum += S[use[ct]][1];
}
if (weightSum == n) {
return [weights, scoreSum];
} else {
return false;
}
}
// Don't use valid[0]
var valid1 = [];
for (ct=1; ct < valid.length; ct++) {
valid1.push(valid[ct]);
}
var opt1 = bestSubset(use, valid1);
// Use valid[0]
var use2 = JSON.parse(JSON.stringify(use));
use2.push(valid[0]);
var valid2 = [];
for (ct=1; ct < valid.length; ct++) {
if ((S[valid[0]][0] & S[valid[ct]][0]) == 0) {
valid2.push(valid[ct]);
}
}
var opt2 = bestSubset(use2, valid2);
if (opt1 === false) {
return opt2;
} else if (opt2 === false || opt1[1] >= opt2[1]) {
return opt1;
} else {
return opt2;
}
}
var initValid = [];
for (var ct=0; ct < S.length; ct++) {
initValid.push(ct);
}
alert(JSON.stringify(bestSubset([], initValid)));
这 returns 组 [4, 11]
得分为 515,如您在原始 post 中所标识。
从非稀疏情况下的一些计算实验(又名 d
数字和目标 (2^d)-1
,包括所有数字 1, 2, ..., (2^d)-1
),我发现这在位数(它在递归函数顶部检查有效性的次数是O(e^(1.47d))
)。这比您分别考虑包括或不包括每个数字 1, 2, ..., (2^d)-1
的蛮力情况要快得多,后者在双指数运行时运行 -- O(2^2^d)
.
不同的方法(一如既往):
第一个想法:
您可以为总和小于单个权重的每个值获取一个权重。因此 wa + wb < wc 和 a + b = c,这导致 a简单的权重系统。
再三考虑:
为了更好地理解权重,它必须是自然数,也就是整数。
第三个想法:
为什么不直接使用数字本身进行小幅缩减,使总和小于单个权重。
在一起:
我取数字,取值作为权重。此外,我将它们的值减 1,因此:
a = 1, b = 2, c = 3 wa + wb < wc
wa = 0, wb = 1, wc = 2 => 0 + 1 < 2
公式:权重n = n - 1
证明:
对于每个被加数,您都会得到 -1 的 malus。所以,对于更多的加数,你得到的数字比原始数字的权重更小。
另一个例子:
weight15(14)应该大于weight4(3)和weight之和11 (10).
人数:14 > 3 + 10
我的意思是,这里不需要程序代码。
对于那些用谷歌搜索这个的人,我使用了@josilber 提供的答案,没有递归和重叠保护(见下文)。由于 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]));