将数字列表分成2个相等的总和列表的算法

时间:2009-05-20 20:49:51

标签: python algorithm dynamic-programming np-complete knapsack-problem

有一个数字列表。

该列表将分为2个相等大小的列表,总和之间的差异最小。必须打印总和。

#Example:
>>>que = [2,3,10,5,8,9,7,3,5,2]
>>>make_teams(que)
27 27

在某些情况下,以下代码算法是否存在错误?

如何优化和/或pythonize?

def make_teams(que):
    que.sort()
    if len(que)%2: que.insert(0,0)
    t1,t2 = [],[]
    while que:
    val = (que.pop(), que.pop())
    if sum(t1)>sum(t2):
        t2.append(val[0])
        t1.append(val[1])
    else:
        t1.append(val[0])
        t2.append(val[1])
    print min(sum(t1),sum(t2)), max(sum(t1),sum(t2)), "\n"

问题来自http://www.codechef.com/problems/TEAMSEL/

14 个答案:

答案 0 :(得分:29)

Dynamic programming是您正在寻找的解决方案。

[4,3,10,3,2,5]的例子:

X-Axis: Reachable sum of group.        max = sum(all numbers) / 2    (rounded up)
Y-Axis: Count elements in group.       max = count numbers / 2       (rounded up)

      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  |  |  | 4|  |  |  |  |  |  |  |  |  |  |       //  4
 2  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
 3  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  |  | 3| 4|  |  |  |  |  |  |  |  |  |  |       //  3
 2  |  |  |  |  |  |  | 3|  |  |  |  |  |  |  |
 3  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  |  | 3| 4|  |  |  |  |  |10|  |  |  |  |       // 10
 2  |  |  |  |  |  |  | 3|  |  |  |  |  |10|10|
 3  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  |  | 3| 4|  |  |  |  |  |10|  |  |  |  |       //  3
 2  |  |  |  |  |  | 3| 3|  |  |  |  |  |10|10|
 3  |  |  |  |  |  |  |  |  |  | 3|  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  | 2| 3| 4|  |  |  |  |  |10|  |  |  |  |       //  2
 2  |  |  |  |  | 2| 3| 3|  |  |  |  | 2|10|10|
 3  |  |  |  |  |  |  |  | 2| 2| 3|  |  |  |  |
      1  2  3  4  5  6  7  8  9 10 11 12 13 14
 1  |  | 2| 3| 4| 5|  |  |  |  |10|  |  |  |  |       //  5
 2  |  |  |  |  | 2| 3| 3| 5| 5|  |  | 2|10|10|
 3  |  |  |  |  |  |  |  | 2| 2| 3| 5| 5|  |  |
                                       ^

12是我们的幸运数字!回溯以获得小组:

12 - 5 = 7        {5}
 7 - 3 = 4        {5, 3}
 4 - 4 = 0        {5, 3, 4}

然后可以计算另一组:{4,3,10,3,2,5} - {5,3,4} = {10,3,2}

所有带数字的字段都是一个包的可能解决方案。选择右下角最远的那个。

BTW:它被称为knapsack-problem

  

如果所有权重(w1,...,wn和W)都是   非负整数,背包   问题可以解决   使用动态的伪多项式时间   编程。

答案 1 :(得分:6)

新解决方案

这是一种广泛优先搜索,具有启发式剔除功能。树被限制在玩家的深度/ 2。玩家总和限额为totalscores / 2。玩家池数为100,需要大约10秒才能解决。

def team(t):
    iterations = range(2, len(t)/2+1)

    totalscore = sum(t)
    halftotalscore = totalscore/2.0

    oldmoves = {}

    for p in t:
        people_left = t[:]
        people_left.remove(p)
        oldmoves[p] = people_left

    if iterations == []:
        solution = min(map(lambda i: (abs(float(i)-halftotalscore), i), oldmoves.keys()))
        return (solution[1], sum(oldmoves[solution[1]]), oldmoves[solution[1]])

    for n in iterations:
        newmoves = {}
        for total, roster in oldmoves.iteritems():
            for p in roster:
                people_left = roster[:]
                people_left.remove(p)
                newtotal = total+p
                if newtotal > halftotalscore: continue
                newmoves[newtotal] = people_left
        oldmoves = newmoves

    solution = min(map(lambda i: (abs(float(i)-halftotalscore), i), oldmoves.keys()))
    return (solution[1], sum(oldmoves[solution[1]]), oldmoves[solution[1]])

print team([90,200,100])
print team([2,3,10,5,8,9,7,3,5,2])
print team([1,1,1,1,1,1,1,1,1,9])
print team([87,100,28,67,68,41,67,1])
print team([1, 1, 50, 50, 50, 1000])

#output
#(200, 190, [90, 100])
#(27, 27, [3, 9, 7, 3, 5])
#(5, 13, [1, 1, 1, 1, 9])
#(229, 230, [28, 67, 68, 67])
#(150, 1002, [1, 1, 1000])

另请注意,我尝试使用GS的描述解决此问题,但仅通过存储运行总计来获取足够的信息是不可能的。如果您存储了两者项目数和总数,那么它将与此解决方案相同,除非您保留了不必要的数据。因为你只需要将n-1和n次迭代保持到numplayers / 2。

我有一个基于二项式系数的旧的详尽的(在历史中看)。它解决了长度为10的示例性问题,但后来我看到竞争对手的人数达到了100个。

答案 2 :(得分:2)

那么,你可以在多项式时间内找到百分比精度的解决方案,但是为了实际找到最优(绝对最小差异)解决方案,问题是NP完全的。这意味着该问题没有多项式时间解决方案。因此,即使使用相对较小的数字列表,也难以解决计算密集问题。如果您确实需要解决方案,请查看一些近似算法。

http://en.wikipedia.org/wiki/Subset_sum_problem

答案 3 :(得分:2)

Q值。给定多整数S的整数,有没有办法将S分为两个子集 S1和S2,以便 S1中数字的总和等于S2中数字的总和?

一个。Set Partition Problem

祝你好运近似。 :)

答案 4 :(得分:1)

它实际上是PARTITION,这是KNAPSACK的一个特例。

NP完全,使用伪多项式dp算法。伪伪多项式是指运行时间取决于权重范围的事实。

一般情况下,您必须首先确定是否存在确切的解决方案,然后才能接受启发式解决方案。

答案 5 :(得分:1)

他们显然正在寻找动态编程背包解决方案。所以经过我的第一次努力(我认为是一个非常好的原始启发式)和我的第二次努力(一种非常偷偷摸摸的精确组合解决方案,适用于短数据集,甚至设置多达100个元素,只要的数量独特的值很低),我终于屈服于同伴的压力并写下他们想要的那个(不太难 - 处理重复的条目是最棘手的部分 - 基于它的基础算法只有在所有输入都是唯一的情况下才有效 - 我很高兴 long long 足够容纳50位!)。

因此,对于我在测试前两个工作时放在一起的所有测试数据和尴尬边缘情况,它给出了相同的答案。至少对于我用组合求解器检查的那些,我知道它们是正确的。但是我仍然没有提交错误答案!

我不是要求任何人在这里修改我的代码,但如果有人能找到下面代码生成错误答案的案例,我会非常感激。

谢谢,

格雷厄姆

PS此代码始终在时间限制内执行,但优化后。我保持简单直到它通过测试,然后我有一些想法加快它,可能是10倍或更多。

#include <stdio.h>

#define TRUE (0==0)
#define FALSE (0!=0)

static int debug = TRUE;

//int simple(const void *a, const void *b) {
//  return *(int *)a - *(int *)b;
//}

int main(int argc, char **argv) {
  int p[101];
  char *s, line[128];
  long long mask, c0[45001], c1[45001];
  int skill, players, target, i, j, tests, total = 0;

  debug = (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'd' && argv[1][2] == '\0');

  s = fgets(line, 127, stdin);
  tests = atoi(s);
  while (tests --> 0) {

    for (i = 0; i < 45001; i++) {c0[i] = 0LL;}

    s = fgets(line, 127, stdin); /* blank line */
    s = fgets(line, 127, stdin); /* no of players */
    players = atoi(s);
    for (i = 0; i < players; i++) {s = fgets(line, 127, stdin); p[i] = atoi(s);}

    if (players == 1) {
      printf("0 %d\n", p[0]);
    } else {

    if (players&1) p[players++] = 0; // odd player fixed by adding a single player of 0 strength
    //qsort(p, players, sizeof(int), simple);

    total = 0; for ( i = 0; i < players; i++) total += p[i];
    target = total/2; // ok if total was odd and result rounded down - teams of n, n+1
    mask = 1LL << (((long long)players/2LL)-1LL);

    for (i = 0; i < players; i++) {
      for (j = 0; j <= target; j++) {c1[j] = 0LL;} // memset would be faster
      skill = p[i];
      //add this player to every other player and every partial subset
      for (j = 0; j <= target-skill; j++) {
        if (c0[j]) c1[j+skill] = c0[j]<<1;  // highest = highest j+skill for later optimising
      }
      c0[skill] |= 1; // so we don't add a skill number to itself unless it occurs more than once
      for (j = 0; j <= target; j++) {c0[j] |= c1[j];}
      if (c0[target]&mask) break; // early return for perfect fit!
    }

    for (i = target; i > 0; i--) {
      if (debug || (c0[i] & mask)) {
        fprintf(stdout, "%d %d\n", i, total-i);
        if (debug) {
          if (c0[i] & mask) printf("******** ["); else
          printf("         [");
          for (j = 0; j <= players; j++) if (c0[i] & (1LL<<(long long)j)) printf(" %d", j+1);
          printf(" ]\n");
        } else break;
      }
    }
    }
    if (tests) printf("\n");
  }
  return 0;
}

答案 6 :(得分:1)

请注意,它也是一种启发式方法,我将其排除在函数之外。

 def g(data):
   sums = [0, 0]
   for pair in zip(data[::2], data[1::2]):
     item1, item2 = sorted(pair)
     sums = sorted([sums[0] + item2, sums[1] + item1])
   print sums

data = sorted([2,3,10,5,8,9,7,3,5,2])
g(data)

答案 7 :(得分:1)

您的方法不起作用的测试用例

que = [1, 1, 50, 50, 50, 1000]

问题在于你是成对分析事物,在这个例子中,你希望所有50个都在同一个组中。如果您删除配对分析方面并且一次只执行一个输入,则应该解决此问题。

以下是执行此操作的代码

def make_teams(que):
    que.sort()
    que.reverse()
    if len(que)%2: que.insert(0,0)
    t1,t2 = [],[]
    while que:
        if abs(len(t1)-len(t2))>=len(que):
            [t1, t2][len(t1)>len(t2)].append(que.pop(0))
        else:
            [t1, t2][sum(t1)>sum(t2)].append(que.pop(0))
    print min(sum(t1),sum(t2)), max(sum(t1),sum(t2)), "\n"

if __name__=="__main__":
    que = [2,3,10,5,8,9,7,3,5,2]
    make_teams(que)
    que = [1, 1, 50, 50, 50, 1000]
    make_teams(que)

这给了27,27和150,1002这些对我有意义的答案。

编辑:在审核中,我发现这实际上并不起作用,但最后,我不太清楚为什么。我会在这里发布我的测试代码,因为它可能有用。测试只生成具有相等总和的随机序列,将它们放在一起并进行比较(结果令人遗憾)。

编辑#2:根据Unknown [87,100,28,67,68,41,67,1]指出的示例,很明显为什么我的方法不起作用。具体来说,要解决此示例,需要将两个最大的数字添加到同一序列中以获得有效的解决方案。

def make_sequence():
    """return the sums and the sequence that's devided to make this sum"""
    while 1:
        seq_len = randint(5, 200)
        seq_max = [5, 10, 100, 1000, 1000000][randint(0,4)]
        seqs = [[], []]
        for i in range(seq_len):
            for j in (0, 1):
                seqs[j].append(randint(1, seq_max))
        diff = sum(seqs[0])-sum(seqs[1])
        if abs(diff)>=seq_max: 
            continue
        if diff<0:
            seqs[0][-1] += -diff
        else:
            seqs[1][-1] += diff
        return sum(seqs[0]), sum(seqs[1]), seqs[0], seqs[1]

if __name__=="__main__":

    for i in range(10):
        s0, s1, seq0, seq1 = make_sequence()
        t0, t1 = make_teams(seq0+seq1)
        print s0, s1, t0, t1
        if s0 != t0 or s1 != t1:
            print "FAILURE", s0, s1, t0, t1

答案 8 :(得分:0)

由于列表必须与我相等,所以问题根本不是NP。

我将排序列表拆分为模式t1&lt; -que(1st,last),t2&lt; -que(2nd,last-1)...

def make_teams2(que):
    que.sort()
    if len(que)%2: que.insert(0,0)
    t1 = []
    t2 = []
    while que:
        if len(que) > 2:
            t1.append(que.pop(0))
            t1.append(que.pop())
            t2.append(que.pop(0))
            t2.append(que.pop())
        else:
            t1.append(que.pop(0))
            t2.append(que.pop())
    print sum(t1), sum(t2), "\n"

编辑:我认为这也是一种错误的方法。错误的结果!

答案 9 :(得分:0)

在之前的评论中,我假设问题集合是易处理的,因为他们在分配的时间内仔细选择了测试数据以与各种算法兼容。结果并非如此 - 相反,它是问题约束 - 数字不高于450,最终设置不超过50个数字是关键。这些与使用我在后面的文章中提出的动态编程解决方案解决问题是兼容的。其他算法(组合模式生成器的启发式或详尽枚举)都不可能工作,因为会有足够大的测试用例或足以破坏这些算法的测试用例。说实话是相当烦人的,因为那些其他解决方案更具挑战性,当然也更有趣。请注意,如果没有大量的额外工作,动态编程解决方案只会说明对于任何给定的总和是否可以使用N / 2的解决方案,但它不会告诉您任何一个分区的内容。

答案 10 :(得分:0)

经过一番思考,对于不太大的问题,我认为最好的启发式会是这样的:

import random
def f(data, nb_iter=20):
  diff = None
  sums = (None, None)
  for _ in xrange(nb_iter):
    random.shuffle(data)
    mid = len(data)/2
    sum1 = sum(data[:mid])
    sum2 = sum(data[mid:])
    if diff is None or abs(sum1 - sum2) < diff:
      sums = (sum1, sum2)
  print sums

如果问题较大,您可以调整nb_iter。

它主要解决上述所有问题。

答案 11 :(得分:0)

class Team(object):
    def __init__(self):
        self.members = []
        self.total = 0

    def add(self, m):
        self.members.append(m)
        self.total += m

    def __cmp__(self, other):
        return cmp(self.total, other.total)


def make_teams(ns):
    ns.sort(reverse = True)
    t1, t2 = Team(), Team()

    for n in ns:
        t = t1 if t1 < t2 else t2
        t.add(n)

    return t1, t2

if __name__ == "__main__":
    import sys
    t1, t2 = make_teams([int(s) for s in sys.argv[1:]])
    print t1.members, sum(t1.members)
    print t2.members, sum(t2.members)

>python two_piles.py 1 50 50 100
[50, 50] 100
[100, 1] 101

答案 12 :(得分:0)

对于性能,您可以通过将append()和sum()替换为运行总计来保存计算。

答案 13 :(得分:0)

您可以使用以下方法将您的循环收紧一点:

def make_teams(que):
    que.sort()
    t1, t2 = []
    while que:
        t1.append(que.pop())
        if sum(t1) > sum(t2):
            t2, t1 = t1, t2
    print min(sum(t1),sum(t2)), max(sum(t1),sum(t2))