簡體   English   中英

將數字列表分成2個相等的總和列表的算法

[英]Algorithm to Divide a list of numbers into 2 equal sum lists

有一個數字列表。

該列表將分為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/

動態編程是您正在尋找的解決方案。

[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:它被稱為背包問題

如果所有權重(w1,...,wn和W)都是非負整數,則可以使用動態編程在偽多項式時間內解決背包問題。

新解決方案

這是一種利用啟發式剔除的廣度優先搜索。 樹被限制在玩家的深度/ 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。

那么,你可以找到多項式時間百分比精度的解決方案,但實際上找到最優(絕對最小差異)解決方案,問題是NP完全的。 這意味着該問題沒有多項式時間解決方案。 因此,即使使用相對較小的數字列表,也難以解決計算密集問題。 如果您確實需要解決方案,請查看一些近似算法。

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

問:考慮到整數的多集S,有沒有與S划分為兩個子集 S1和S2,使得S1的數字之和等於S2中的數字之和的方法嗎?

A. 設置分區問題

祝你好運近似。 :)

他們顯然正在尋找動態編程背包解決方案。 所以在我的第一次努力(我認為是一個非常好的原始啟發式)和我的第二次努力(一種非常偷偷摸摸的精確組合解決方案,適用於短數據集,甚至設置多達100個元素,只要唯一值的數量是我終於屈服於同伴的壓力並寫下他們想要的那個(不太難 - 處理重復的條目是最棘手的部分 - 基於它的基礎算法只有在所有輸入都是獨特的情況下才有效 - 我很高興長長大到足以容納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;
}

請注意,它也是一種啟發式方法,我將其排除在函數之外。

 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)

您的方法不起作用的測試用例是

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

它實際上是PARTITION,這是KNAPSACK的一個特例。

它是NP完全的,具有偽多項式dp算法。 偽偽多項式指的是運行時間取決於權重范圍的事實。

通常,在您接受啟發式解決方案之前,您必須首先確定是否存在確切的解決方案。

在之前的評論中,我假設問題集合是易處理的,因為他們在分配的時間內仔細選擇了測試數據以與各種算法兼容。 結果並非如此 - 相反,它是問題約束 - 數字不高於450,最終設置不超過50個數字是關鍵。 這些與使用我在后面的文章中提出的動態編程解決方案解決問題是兼容的。 其他算法(組合模式生成器的啟發式或詳盡枚舉)都不可能工作,因為會有足夠大的測試用例或足以破壞這些算法的測試用例。 說實話是相當煩人的,因為那些其他解決方案更具挑戰性,當然也更有趣。 請注意,如果沒有大量的額外工作,動態編程解決方案只會說明對於任何給定的總和是否可以使用N / 2的解決方案,但它不會告訴您任一分區的內容。

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

為了提高性能,您可以通過將append()和sum()替換為運行總計來節省計算。

您可以使用以下方法將您的循環收緊一點:

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))

由於列表必須與我相同,因此問題根本不是NP。

我將排序列表拆分為模式t1 <-que(1st,last),t2 <-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"

編輯 :我想這也是一個錯誤的方法。 錯誤的結果!

經過一番思考后,對於不太大的問題,我認為最好的啟發式方法是這樣的:

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。

它主要解決了上面提到的所有問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM