一组9人在3个不同的2,3和4人小组中工作的方式有多少?如何通过javascript回溯生成所有可能性。
示例:
Gs = group([aldo,beat,carla,david,evi,flip,gary,hugo,ida],[2,2,5]);
console.log(Gs); // [[aldo,beat],[carla,david],[evi,flip,gary,hugo,ida]], ...
请注意,我不想要小组成员的排列;即[[aldo,beat],...]与[[beat,aldo],...]的解决方案相同。然而,[[aldo,beat],[carla,david],...]和[[carla,david],[aldo,beat],...]之间存在差异。
*请不要图书馆。
答案 0 :(得分:4)
如果您只需要一组9人的方式可以分成3个子组,分别为2,3和4人,那么使用C
(计算数量的函数)可以很容易地进行数学计算组合)。
C(9, 2)
。C(7, 3)
。C(4, 4)
。但C(n, n)
始终为1。因此,将一组9人分成2个,3个和4个人的3个子组的方法数量为C(9, 2) * C(7, 3) * C(4, 4)
。这可以简化为C(9, 2) * C(7, 3)
,36 * 35
即1260
。
我们可以编写一个函数来为我们计算:
function ways(n) {
var l = arguments.length, w = 1;
for (var i = 1; i < l; i++) {
var m = arguments[i];
w *= combinations(n, m);
n -= m;
}
return w;
}
要使此功能起作用,我们需要定义函数combinations
:
function combinations(n, k) {
return factorial(n) / factorial(n - k) / factorial(k);
}
最后,我们需要定义factorial
的函数:
function factorial(n) {
var f = n;
while (--n) f *= n;
return f;
}
然后我们按如下方式计算方式的数量:
alert(ways(9, 2, 3)); // 1260
您可以在此处查看演示:http://jsfiddle.net/bHSuh/
请注意,我们不需要指定4个人的最后一个子组,因为这是隐含的。
但是我相信你想要生成每种可能的方式。这是amb
运算符最适合的类型。所以我们要做的第一件事就是在JavaScript中编写amb
运算符:
function amb(options, callback) {
var length = options.length;
for (var i = 0; i < length; i++) {
try {
callback(options[i]); // try the next option
return; // no problem, quit
} catch (e) {
continue; // problem, next
}
}
throw new Error("amb tree exhausted"); // throw a tantrum
}
接下来,我们将编写一个函数,从索引列表中选择一组给定的项目:
function pick(list, items) {
var length = list.length, selected = [], rest = [];
for (var i = 0; i < length; i++) {
if (items.indexOf(i) < 0) rest.push(list[i]);
else selected.push(list[i]);
}
return [selected, rest];
}
我们还需要一个能生成索引列表的函数:
function getIndices(length) {
var indices = [];
for (var i = 0; i < length; i++)
indices.push(i);
return indices;
}
最后,我们将递归地实现group
函数:
function group(options, divisions) {
var subgroup = [], groups = [], n = 0;
var indices = getIndices(options.length);
var division = divisions.shift(), remaining = divisions.length;
try {
amb(indices, select);
} catch (e) {
return groups;
}
function select(index) {
subgroup.push(index);
if (++n < division) {
try { amb(indices.slice(index + 1), select); }
catch (e) { /* we want to continue processing */ }
} else {
var subgroups = pick(options, subgroup);
if (remaining) {
var children = group(subgroups.pop(), divisions.slice());
var length = children.length;
for (var i = 0; i < length; i++)
groups.push(subgroups.concat(children[i]));
} else groups.push(subgroups);
}
n--;
subgroup.pop();
throw new Error;
}
}
现在您可以按如下方式使用它:
var groups = group([
"aldo", "beat", "carla",
"david", "evi", "flip",
"gary", "hugo", "ida"
], [2, 3]);
再次注意,您不需要指定4个人的最后一个子群,因为它是隐含的。
现在让我们看看输出是否与我们预期的一样:
console.log(groups.length === ways(9, 2, 3)); // true
你去吧。正好有1260种方法可以将一组9人分成3个小组,分别为2,3和4人。
现在我知道我的group
函数看起来有点令人生畏,但它实际上非常简单。尝试阅读并了解发生了什么。
想象一下,你是9个人的老板。你会如何将他们分成2个,3个和4个人的3个小组?这正是我group
函数的工作方式。
如果您在一段时间后仍然无法理解逻辑,那么我将更新我的答案并详细解释group
函数。祝你好运。
BTW我刚刚意识到,对于这个问题,你真的不需要amb
。您只需使用forEach
即可。由于缺少try-catch块,生成的代码会更快:
function group(options, divisions) {
var subgroup = [], groups = [], n = 0;
var indices = getIndices(options.length);
var division = divisions.shift(), remaining = divisions.length;
indices.forEach(select);
return groups;
function select(index) {
subgroup.push(index);
if (++n < division) indices.slice(index + 1).forEach(select);
else {
var subgroups = pick(options, subgroup);
if (remaining) {
var children = group(subgroups.pop(), divisions.slice());
var length = children.length;
for (var i = 0; i < length; i++)
groups.push(subgroups.concat(children[i]));
} else groups.push(subgroups);
}
subgroup.pop();
n--;
}
}
由于我们不再使用amb
,程序的执行时间减少了十倍。自己查看结果:http://jsperf.com/amb-vs-foreach
此外,我终于创建了上述程序的演示小提琴:http://jsfiddle.net/Ug6Pb/
答案 1 :(得分:1)
我确信有更快的公式,但我在数学方面从未如此出色,如果我正确理解问题,这似乎有效:
function combo(r, ops){
function unq(r){return r.filter(function(a,b,c){return !this[a] && (this[a]=1);},{}); }
var combos={}, pairs=[];
r.forEach(function(a,b,c){
combos[a]=r.filter(function not(a){return a!=this && !combos[a]}, a);
});
Object.keys(combos).forEach(function(k){
combos[k].forEach(function(a){
pairs.push([k, a]+'');
});
});
return unq(unq(
pairs.map(function(a){
return unq(a.split(",")).sort();
})).map(function(a){
return a.length==ops && a;
}).filter(Boolean))
.sort();
}//end combo
var r="aldo,beat,carla,david,evi,flip,gary,hugo,ida".split(",");
// find groups of different lengths:
combo(r, 2) // 2 folks == 36 combos
combo( combo(r, 2), 3) // 3 folks == 84 combos
combo( combo( combo(r, 2), 3), 4) // 4 folks == 126 combos
我没有费心去递归函数,因为你只需要进入4-in并且lispy调用工作,但是如果我必须更进一步,我想写一个额外的外包装来夹住调用...
答案 2 :(得分:1)
回溯算法的核心实现很简单(参见下面的函数doBacktrack)。通常,复杂性在于特定回溯问题的细节
以下是我为您的问题实现的回溯算法。它基于Steven Skiena的算法设计手册中的回溯算法描述(或者我记得的)。
我没有添加修剪算法(因为它已经花了我比我想象的要长:)但是如果你想提高它的性能,只需为完成的函数添加一个合理的实现()以防止继续对候选人的处理可以推断为不可行的解决方案
function backtrack() {
var people =
['aldo','beat','carla','david','evi','flip','gary','hugo','ida'];
var initial_state =
[[], [], []];
var groups =
[2, 3, 4];
var data =
{groups: groups, people: people, people_idx_for_name: {}};
people.forEach(function(e, i) {
data['people_idx_for_name'][e] = i;
});
var solutions = [];
doBacktrack(initial_state, solutions, data);
return solutions;
}
function doBacktrack(candidate, solutions, data) {
// console.log('processing: ' + candidate);
if (isSolution(candidate, data)) {
processSolution(candidate, solutions);
}
if (done(candidate, solutions, data)) {
return;
}
var new_candidates = calculateNewCandidates(candidate, data);
for (var i=0; i<new_candidates.length; i++) {
doBacktrack(new_candidates[i], solutions, data);
}
}
function calculateNewCandidates(candidate, data) {
var groups = data['groups'];
var i = 0;
while (i<groups.length && candidate[i].length == groups[i]) { i++; }
if (i < groups.length) {
//determine list of not yet selected people
var not_yet_selected = determineNotYetSelectedPeople(candidate, data, i);
var results = [];
for (var j=0; j<not_yet_selected.length; j++) {
var candidate_copy = candidate.slice(0);
for (var k=0; k<candidate_copy.length; k++) {
candidate_copy[k] = candidate_copy[k].slice(0);
}
candidate_copy[i].push(not_yet_selected[j])
results.push(candidate_copy);
}
return results;
} else {
return [];
}
}
function determineNotYetSelectedPeople(candidate, data, group) {
var people = data['people'];
var people_idx_for_name = data['people_idx_for_name'];
var selected_people = {};
var results = [];
var max = -Number.MAX_VALUE;
candidate.forEach(function(candidate_group, i) {
candidate_group.forEach(function(already_selected_person_name) {
var already_selected_person_idx = people_idx_for_name[already_selected_person_name];
if (max < already_selected_person_idx && i==group) { max = already_selected_person_idx; }
selected_people[already_selected_person_name] = true;
});
});
for (var i=0; i<people.length; i++) {
if (!selected_people[people[i]] && i > max) { results.push(people[i]); }
}
return results;
}
function isSolution(candidate, data) {
var groups = data['groups'];
for (var i=0; i<groups.length; i++) {
if (candidate[i].length != groups[i]) {return false;}
}
return true;
}
function processSolution(candidate, solutions) {
var solution = [];
candidate.forEach(function(e) {
var l = [];
solution.push(l);
e.forEach(function(f) {
l.push(f);
});
});
solutions.push(solution);
}
//use this to improve performance with prunning if possible
function done() {
return false;
}
var solutions = backtrack();
console.log(solutions);
console.log(solutions.length);