我有X个学生,其中X是6的倍数。我现在想要将学生分成6个小组。
我有一个衡量"好"一组6是(让我们说它是一个现在在恒定时间运行的黑匣子)。通过分割学生,然后在每个小组上调用我的功能来衡量它的优点,然后总结每个小组的优点,我能够衡量好的"好"某组是一组。
我试图创建一种算法,以一种方式对学生进行分组,以便最大化所有群体的总体善良,并且没有任何群体具有低于某个值y的个人善良。换句话说,将学生分成6组,以便在所有群体都优于y的约束下最大化总体善良。
我期望运行此算法的学生人数(X)约为36人。
问题似乎是NP-Complete,所以我可以解决启发式算法问题。我对此没有太多的经验,但我认为某种遗传算法或模拟退火甚至贪婪算法都可行,但我不知道从哪里开始我的研究。
有人能指出我正确的方向吗?我做了一些研究,问题看起来几乎与旅行推销员问题相同(问题空间是学生/节点的所有排列)但我不认为我可以应用TSP算法,因为"节点的数量" (大约36岁)对于任何有效的事情都会非常大。
答案 0 :(得分:0)
我会从一个非常简单的随机搜索开始#34;算法:
start from a random solution (a partition of X to groups), call it S[0]
score[0] = black_box_socre(S[0])
i = 0
while (some condition):
i++
S[i] = some small permutation on S[i-1] # (1)
score[i] = black_box_score(S[i])
if score[i] < score[i-1]: # (2)
S[i] = S[i-1]
score[i] = score[i-1]
(1) - 在你的情况下可以进行小的排列,在两组之间切换2个人。
(2) - 如果我们做出改变使我们的解决方案变得更糟(得分更低),我们会拒绝它。你可以稍后用一些概率接受更糟糕的解决方案来替换它,使这个算法进入模拟退火。
首先简单地运行1000次迭代,然后将得分[i]作为i的函数绘制,以了解解决方案的改进速度。运行几次(尝试不同的随机起点)。
然后你可以使用不同的排列(1),使算法不那么贪婪(2),或者添加一些花哨的自动逻辑来停止搜索(例如,在最后T
次迭代中没有进展)。 / p>
答案 1 :(得分:0)
我们以36名学生分为6组为例。检查所有组合是不切实际的,因为有3,708,580,189,773,818,399,040。然而,通过检查成对组之间的每个学生分布来重复改进的策略应该是可行的。
有462种方法可以将12名学生分成2组,因此找到最佳的12→2分布只需要924次调用&#34;组质量&#34;功能。在6组中有15种可能的组配对,因此13,860次呼叫将揭示配对组的最佳方式,并在两组之间重新分配学生以获得最大的改进。
为了能够编写代码来测试这个策略,一个例子&#34;组质量&#34;我需要这个功能,所以我给1到36岁的学生编号并使用一个函数来增加相邻学生之间的距离。数字。所以例如小组[2,7,15,16,18,30]
的得分为5*8*1*2*12 = 960
。如果你想象编号是学生的排名&#39;能力,然后一个高质量的群体意味着一个混合能力小组。最佳分布是:
group A: [1, 7, 13, 19, 25, 31] group B: [2, 8, 14, 20, 26, 32] group C: [3, 9, 15, 21, 27, 33] group D: [4, 10, 16, 22, 28, 34] group E: [5, 11, 17, 23, 29, 35] group F: [6, 12, 18, 24, 30, 36]
每个小组得分为6*6*6*6*6 = 7776
,总得分为46656
。在实践中,我发现使用Log(score)
可以获得更好的结果,因为它有利于所有群体的小改进,而不是对一个或两个群体的大幅改进。 (有利于对几个群体或最低质量群体的改进,或只是选择最佳的整体改进,是您必须根据您的特定&#34;群组质量和功能进行微调的部分。 )
从随机分布开始,算法计算所有15组配对的最佳分布:AB,CD,EF,BC,DE,FA,AC,BD,CE,DF,EA,FB,AD,BE,CF
。然后,它比较所有15对组合的得分,以找到具有最高总得分的组合,例如, DE+AC+FB
。然后它重新分配学生,并返回新的总分。这构成了一个改进步骤。
令我惊讶的是,算法始终设法找到最佳解决方案,并且只需4到7步,这意味着小于100,000&#34;组质量&#34;进行函数调用。 &#34;团队质量&#34;我使用的算法当然非常简单,所以你必须用真实的东西来检查它,以便在你的具体情况下衡量这种方法的有用性。但很明显,这种算法只需几步即可彻底重新安排分布。
(下面的代码示例是为了简单起见,对36名学生进行了硬编码。每组中的学生分类都是为了简化质量功能。)
function improve(groups) {
var pairs = [[0,1],[1,2],[2,3],[3,4],[4,5],[5,0],[0,2],[1,3],[2,4],[3,5],[4,0],[5,1],[0,3],[1,4],[2,5]];
var combi = [[0,2,4],[1,3,5],[0,3,14],[1,4,12],[2,5,13],[0,8,9],[1,9,10],[2,10,11],[3,6,11],[4,6,7],[5,7,8],[7,10,14],[6,9,13],[8,11,12],[12,13,14]];
// FIND OPTIMAL DISTRIBUTION FOR ALL PAIRS OF GROUPS
var optim = [];
for (var i = 0; i < 15; i++) {
optim[i] = optimise(groups[pairs[i][0]], groups[pairs[i][1]]);
}
// FIND BEST COMBINATION OF PAIRS
var best, score = -1;
for (var i = 0; i < 15; i++) {
var current = optim[combi[i][0]].score + optim[combi[i][1]].score + optim[combi[i][2]].score;
if (current > score) {
score = current;
best = i;
}
}
// REDISTRIBUTE STUDENTS INTO GROUPS AND RETURN NEW SCORE
groups[0] = optim[combi[best][0]].group1.slice();
groups[1] = optim[combi[best][0]].group2.slice();
groups[2] = optim[combi[best][1]].group1.slice();
groups[3] = optim[combi[best][1]].group2.slice();
groups[4] = optim[combi[best][2]].group1.slice();
groups[5] = optim[combi[best][2]].group2.slice();
return score;
}
// FIND OPTIMAL DISTRIBUTION FOR PAIR OF GROUPS
function optimise(group1, group2) {
var optim = {group1: [], group2: [], score: -1};
var set = group1.concat(group2).sort(function(a, b) {return a - b});
var distr = [0,0,0,0,0,1,1,1,1,1,1];
// TRY EVERY COMBINATION
do {
// KEEP FIRST STUDENT IN FIRST GROUP TO AVOID SYMMETRIC COMBINATIONS
var groups = [[set[0]], []];
// DISTRIBUTE STUDENTS INTO GROUP 0 OR 1 ACCORDING TO BINARY ARRAY
for (var j = 0; j < 11; j++) {
groups[distr[j]].push(set[j + 1]);
}
// CHECK SCORE OF GROUPS AND STORE IF BETTER
var score = quality(groups[0]) + quality(groups[1]);
if (score > optim.score) {
optim.group1 = groups[0].slice();
optim.group2 = groups[1].slice();
optim.score = score;
}
} while (increment(distr));
return optim;
// GENERATE NEXT PERMUTATION OF BINARY ARRAY
function increment(array) {
var digit = array.length, count = 0;
while (--digit >= 0) {
if (array[digit] == 1) ++count
else if (count) {
array[digit] = 1;
for (var i = array.length - 1; i > digit; i--) {
array[i] = --count > 0 ? 1 : 0;
}
return true;
}
}
return false;
}
}
// SCORE FOR ONE GROUP ; RANGE: 0 ~ 8.958797346140275
function quality(group) {
// LOGARITHM FAVOURS SMALL IMPROVEMENTS TO ALL GROUPS OVER LARGE IMPROVEMENT TO ONE GROUP
return Math.log((group[5] - group[4]) * (group[4] - group[3]) * (group[3] - group[2]) * (group[2] - group[1]) * (group[1] - group[0]));
}
// SUM OF SCORES FOR ALL 6 GROUPS ; RANGE: 0 ~ 53.75278407684165
function overallQuality(groups) {
var score = 0;
for (var i = 0; i < 6; i++) score += quality(groups[i]);
return score;
}
// PREPARE RANDOM TEST DATA
var students = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36];
var groups = [[],[],[],[],[],[]];
for (var i = 5; i >=0; i--) {
for (var j = 5; j >= 0; j--) {
var pick = Math.floor(Math.random() * (i * 6 + j));
groups[i].push(students[pick]);
students[pick] = students[i * 6 + j];
}
groups[i].sort(function(a, b) {return a - b});
}
// DISPLAY INITIAL SCORE AND DISTRIBUTION
var score = overallQuality(groups);
document.write("<PRE>Initial: " + score.toFixed(2) + " " + JSON.stringify(groups) + "<BR>");
// IMPROVE DISTRIBUTION UNTIL SCORE NO LONGER INCREASES
var prev, step = 0;
do {
prev = score;
score = improve(groups);
document.write("Step " + ++step + " : " + score.toFixed(2) + " " + JSON.stringify(groups) + "<BR>");
} while (score > prev && score < 53.75278407684165);
if (score >= 53.75278407684165) document.write("Optimal solution reached.</PRE>");
&#13;