Алгоритм деления списка чисел на 2 списка равных сумм

существует список чисел.

список должен быть разделен на 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 ответов


Новое Решение

это широтно-первый поиск с отбором эвристики. Дерево ограничено глубиной игроков/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.


динамическое программирование - это решение, которое вы ищете.

пример с [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 - наше счастливое число! Backtracing, чтобы получить группу:

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}

все поля с числом возможные решения для одного мешка. Выберите тот, который находится дальше всего в нижней части правый угол.

кстати: это называется рюкзак-проблема.

если все веса (w1,..., WN и W), является неотрицательные целые числа, рюкзак проблема может быть решена в псевдополиномиальное время c использованием динамического программирование.


Ну, вы можете найти решение процентной точности в полиномиальное время, но на самом деле найти оптимальное (абсолютная минимальная разница) решение, задача NP-полная. Это означает, что полиномиальное временное решение проблемы отсутствует. В результате, даже с относительно небольшим списком чисел, он слишком интенсивен для вычисления. Если вам действительно нужно решение, взгляните на некоторые из алгоритмов аппроксимации для этого.

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


В. дал multiset s целых чисел, есть ли способ разбить S на два подмножества S1 и S2 такие, что в сумме из чисел в S1 равна сумме чисел в S2?

А.Установить Проблему Раздела.

удачи аппроксимации. : )


обратите внимание, что это также эвристика, и я переместил сортировку из функции.

 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)

Это на самом деле раздел, особый случай рюкзака.

Это NP полный, с псевдополиномиальными алгоритмами dp. Псевдо на псевдо-полиномиальное ссылается на то, что время выполнения зависит от диапазона Весов.

В общем, вам придется сначала решить, есть ли точное решение, прежде чем вы сможете принять эвристическое решение.


тестовый случай, когда ваш метод не работает, это

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, которые являются ответами, которые имеют смысл мне.

Edit: в обзоре я считаю, что это на самом деле не работает, хотя, в конце концов, я не совсем уверен, почему. Я опубликую свой тестовый код здесь, так как это может быть полезно. Тест просто генерирует случайную последовательность, которая имеет равные суммы, складывает их вместе и сравнивает (с печальными результатами).

Edit #2: на основе примера, указанного неизвестным,[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

они, очевидно, ищут решение для динамического программирования рюкзака. Поэтому после моего первого усилия (довольно хорошая оригинальная эвристика, я думал) и моего второго усилия (действительно подлое точное комбинаторное решение, которое работало для коротких наборов данных и даже для наборов до 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] == '');

  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;
}

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

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, если проблема больше.

Это решает все проблемы, упомянутые выше, в основном все время.


в более раннем комментарии я предположил, что проблема как набор была сговорчивой, потому что они тщательно выбрали тестовые данные, чтобы быть совместимыми с различными алгоритмами в течение выделенного времени. Это оказалось не так - вместо этого это проблема ограничений-числа не выше 450 и конечный набор не больше 50 чисел является ключевым. Они совместимы с решением проблемы с использованием решения динамического программирования, которое я разместил в более позднем посте. Никто другой. алгоритмы (эвристика или исчерпывающее перечисление комбинаторным генератором шаблонов) могут работать, потому что будут тестовые случаи, достаточно большие или достаточно жесткие, чтобы сломать эти алгоритмы. Это довольно раздражает, чтобы быть честным, потому что эти другие решения более сложные и, безусловно, более забавные. Обратите внимание, что без большой дополнительной работы решение динамического программирования просто говорит, Возможно ли решение с N / 2 для любой заданной суммы, но оно не говорит вам о содержании раздел.