我有以下问题:
给定N个对象(N <30)的不同值的倍数“k”常数,即k,2k,3k,4k,6k,8k,12k,16k,24k和32k,我需要一个将分配的算法M玩家的所有项目(M <= 6),使得每个玩家获得的对象的总价值尽可能均匀(换句话说,我想以最公平的方式将所有对象分发给所有玩家) )。
编辑:通过最公平的分配我的意思是任何两个玩家获得的对象的价值之间的差异是最小的。 另一个类似的情况是:我有N个不同价值的硬币,我需要在M个玩家之间平分它们;有时他们并没有完全分开,我需要找到下一个最好的分配案例(没有玩家生气,因为另一个人得到了太多钱)。
我不需要(伪)代码来解决这个问题(也就是说,这不是一个功课:)),但我会感谢任何能够解决这个问题的算法的想法或链接。
谢谢!
答案 0 :(得分:9)
问题是NP完全。这意味着无法在合理的时间内确保正确的解决方案。 (见3-partition-problem,谢谢保罗)。
相反,你会想要一个好的近似解决方案生成器。这些通常可以在很短的时间内非常接近最佳答案。我可以推荐Simulated Annealing技术,你也可以用它来解决其他很多NP完全问题。
这个想法是这样的:
这个解决方案比许多人建议的“贪婪”算法强大得多。贪婪算法是您不断向“最差”玩家添加最大项目的算法。贪婪失败的测试用例的示例是[10,9,8,7,7,5,5]
。
我为你做了SA的实施。出于教育目的,严格遵循维基文章。如果你优化它,我会说100倍的改进不会是不现实的。
from __future__ import division
import random, math
values = [10,9,8,7,7,5,5]
M = 3
kmax = 1000
emax = 0
def s0():
s = [[] for i in xrange(M)]
for v in values:
random.choice(s).append(v)
return s
def E(s):
avg = sum(values)/M
return sum(abs(avg-sum(p))**2 for p in s)
def neighbour(s):
snew = [p[:] for p in s]
while True:
p1, p2 = random.sample(xrange(M),2)
if s[p1]: break
item = random.randrange(len(s[p1]))
snew[p2].append(snew[p1].pop(item))
return snew
def P(e, enew, T):
if enew < e: return 1
return math.exp((e - enew) / T)
def temp(r):
return (1-r)*100
s = s0()
e = E(s)
sbest = s
ebest = e
k = 0
while k < kmax and e > emax:
snew = neighbour(s)
enew = E(snew)
if enew < ebest:
sbest = snew; ebest = enew
if P(e, enew, temp(k/kmax)) > random.random():
s = snew; e = enew
k += 1
print sbest
更新:在使用Branch'n'Bound后,我现在相信这种方法更优越,因为它可以在一秒钟内为N = 30,M = 6的情况提供完美的结果。但是我想你可以同样使用模拟退火方法。
答案 1 :(得分:2)
一些人建议的贪婪解决方案似乎是最好的选择,我用一些随机值运行了很多次,似乎每次都是正确的。
如果它不是最佳的,它至少非常接近,并且它以O(nm)左右运行(我现在不能费心去做数学)
C#实施:
static List<List<int>> Dist(int n, IList<int> values) { var result = new List<List<int>>(); for (int i = 1; i <= n; i++) result.Add(new List<int>()); var sortedValues = values.OrderByDescending(val => val); foreach (int val in sortedValues) { var lowest = result.OrderBy(a => a.Sum()).First(); lowest.Add(val); } return result; }
答案 2 :(得分:1)
怎么样:
订购k值。 命令球员。
循环k值,将下一个给下一个玩家。 当你到达球员的最后,转身并继续向相反方向的球员提供k值。
答案 3 :(得分:1)
将具有最大值的可用对象反复给予分配给他的对象总值最小的玩家。
答案 4 :(得分:1)
这是Justin Peel回答的直接实现:
M = 3
players = [[] for i in xrange(M)]
values = [10,4,3,1,1,1]
values.sort()
values.reverse()
for v in values:
lowest=sorted(players, key=lambda x: sum(x))[0]
lowest.append(v)
print players
print [sum(p) for p in players]
我是Python的初学者,但似乎工作正常。此示例将打印
[[10], [4, 1], [3, 1, 1]]
[10, 5, 5]
答案 5 :(得分:0)
30 ^ 6不是那么大(它不到10亿)。完成所有可能的分配,并通过您定义的任何度量选择最公平的分配。
答案 6 :(得分:0)
编辑:
目的是使用贪婪的解决方案,在实现方面有很小的改进,这在C#中可能是透明的:
static List<List<int>> Dist(int n, IList<int> values)
{
var result = new List<List<int>>();
for (int i = 1; i <= n; i++)
result.Add(new List<int>());
var sortedValues = values.OrderByDescending(val => val);//Assume the most efficient sorting algorithm - O(N log(N))
foreach (int val in sortedValues)
{
var lowest = result.OrderBy(a => a.Sum()).First();//This can be done in O(M * log(n)) [M - size of sortedValues, n - size of result]
lowest.Add(val);
}
return result;
}
关于这个阶段:
var lowest = result.OrderBy(a => a.Sum()).First();//This can be done in O(M * log(n)) [M - size of sortedValues, n - size of result]
这个想法是列表总是排序的(在这段代码中,它由OrderBy完成)。最终,这种排序不会超过O(log(n)) - 因为我们只需要将一个项目最多插入一个排序列表 - 这应该与二进制搜索相同。 因为我们需要为sortedValues.Length次重复此阶段,所以整个算法在O(M * log(n))中运行。
所以,用语言来说,它可以改为:
重复以下步骤,直到完成Values
值:
1.为最小的玩家增加最大的价值
2.检查此播放器是否仍具有最小的总和
3.如果是,请转到步骤1。
4.将最后获得的玩家插入已排序的玩家列表
步骤4是O(log(n))步骤 - 因为列表总是排序。