Подмножество Сумма Задача - PullRequest
7 голосов
/ 16 мая 2011

недавно я заинтересовался проблемой подмножества сумм, которая заключается в нахождении подмножества с нулевой суммой в супернаборе. Я нашел несколько решений для SO, кроме того, я наткнулся на конкретное решение , в котором используется подход динамического программирования. Я перевел его решение на Python на основе его качественных описаний. Я пытаюсь оптимизировать это для больших списков, которые съедают много моей памяти. Может ли кто-нибудь порекомендовать оптимизацию или другие методы для решения этой конкретной проблемы? Вот моя попытка в Python:

import random
from time import time
from itertools import product

time0 = time()

# create a zero matrix of size a (row), b(col)
def create_zero_matrix(a,b):
    return [[0]*b for x in xrange(a)]

# generate a list of size num with random integers with an upper and lower bound
def random_ints(num, lower=-1000, upper=1000):
    return [random.randrange(lower,upper+1) for i in range(num)]

# split a list up into N and P where N be the sum of the negative values and P the sum of the positive values.
# 0 does not count because of additive identity
def split_sum(A):
    N_list = []
    P_list = []
    for x in A:
        if x < 0:
            N_list.append(x)
        elif x > 0:
            P_list.append(x)
    return [sum(N_list), sum(P_list)]

# since the column indexes are in the range from 0 to P - N
# we would like to retrieve them based on the index in the range N to P
# n := row, m := col
def get_element(table, n, m, N):
    if n < 0:
        return 0
    try:
        return table[n][m - N]
    except:
        return 0

# same definition as above
def set_element(table, n, m, N, value):
    table[n][m - N] = value

# input array
#A = [1, -3, 2, 4]
A = random_ints(200)

[N, P] = split_sum(A)

# create a zero matrix of size m (row) by n (col)
#
# m := the number of elements in A
# n := P - N + 1 (by definition N <= s <= P)
#
# each element in the matrix will be a value of either 0 (false) or 1 (true)
m = len(A)
n = P - N + 1;
table = create_zero_matrix(m, n)

# set first element in index (0, A[0]) to be true
# Definition: Q(1,s) := (x1 == s). Note that index starts at 0 instead of 1.
set_element(table, 0, A[0], N, 1)

# iterate through each table element
#for i in xrange(1, m): #row
#    for s in xrange(N, P + 1): #col
for i, s in product(xrange(1, m), xrange(N, P + 1)):
    if get_element(table, i - 1, s, N) or A[i] == s or get_element(table, i - 1, s - A[i], N):
        #set_element(table, i, s, N, 1)
        table[i][s - N] = 1

# find zero-sum subset solution
s = 0
solution = []
for i in reversed(xrange(0, m)):
    if get_element(table, i - 1, s, N) == 0 and get_element(table, i, s, N) == 1:
        s = s - A[i]
        solution.append(A[i])

print "Solution: ",solution

time1 = time()

print "Time execution: ", time1 - time0

Ответы [ 6 ]

5 голосов
/ 17 мая 2011

Я не совсем уверен, является ли ваше решение точным или PTA (многовременное приближение).

Но, как кто-то отметил, эта проблема действительно NP-Complete.

Имеется в виду, что каждый известный (точный) алгоритм имеет экспоненциальное поведение по времени относительно размера ввода.

То есть, если вы можете обработать 1 операцию за 0,01 наносекунды, то для списка из 59 элементов он будетпринять:

2^59 ops -->     2^59     seconds -->     2^26      years -->      1 year
            --------------           ---------------
            10.000.000.000           3600 x 24 x 365

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

С другой стороны, если вы ограничите проблему (другой) используя оценки для значений чисел в множестве, то сложность задачи сводится к полиномиальному времени.Но даже тогда потребляемая память будет многочленом ОЧЕНЬ высокого порядка.
Потребляемая память будет намного больше, чем те несколько гигабайт, которые у вас в памяти.И даже намного больше, чем несколько терабайт на жестком диске.

(Это для малых значений границы для значений элементов в наборе)

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

Мне показалось, что вы использовали границу 1000 при построении своей матрицы инициализации.

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

Удачи!

4 голосов
/ 10 июня 2011

Кто-то из Hacker News предложил следующее решение проблемы, которое мне очень понравилось. Это просто происходит в Python:):

def subset_summing_to_zero (activities):
  subsets = {0: []}
  for (activity, cost) in activities.iteritems():
      old_subsets = subsets
      subsets = {}
      for (prev_sum, subset) in old_subsets.iteritems():
          subsets[prev_sum] = subset
          new_sum = prev_sum + cost
          new_subset = subset + [activity]
          if 0 == new_sum:
              new_subset.sort()
              return new_subset
          else:
              subsets[new_sum] = new_subset
  return []

Я провел с ним несколько минут, и он работал очень хорошо.

1 голос
/ 17 мая 2011

, 1-й бросается в глаза

def split_sum(A):
  N_list = 0
  P_list = 0
  for x in A:
    if x < 0:
        N_list+=x
    elif x > 0:
        P_list+=x
  return [N_list, P_list]

Некоторые советы:

  1. Попробуйте использовать 1D-список и использовать bitarray, чтобы уменьшить объем памяти как минимум (http://pypi.python.org/pypi/bitarray), поэтомувы просто измените get / set functon. Это должно уменьшить объем используемой памяти по крайней мере на 64 (целое число в списке - указатель на тип целочисленного типа, поэтому оно может иметь коэффициент 3 * 32)

  2. Избегайте использования try - catch, но вначале определитесь с правильными диапазонами, вы можете обнаружить, что наберете огромную скорость.

1 голос
/ 16 мая 2011

Интересная статья по оптимизации кода Python доступна здесь .По сути, основной результат заключается в том, что вы должны встроить свои частые циклы, поэтому в вашем случае вместо вызова get_element дважды в цикл поместите фактический код этой функции в цикл, чтобы избежать накладных расходов на вызов функции.1006 *

Надеюсь, это поможет!Приветствия

0 голосов
/ 27 мая 2017

Просто измените значения в вашем наборе w и, соответственно, сделайте массив x таким же большим, как len of w, затем передайте последнее значение в функции subsetsum как сумму, для которой вы хотите подмножества, и вы будете готовы (если вы хотите проверить, указав свои собственные значения).

def subsetsum(cs,k,r,x,w,d):
    x[k]=1
    if(cs+w[k]==d):
        for i in range(0,k+1):

            if x[i]==1:
                print (w[i],end=" ")
        print()

    elif cs+w[k]+w[k+1]<=d :
        subsetsum(cs+w[k],k+1,r-w[k],x,w,d)

    if((cs +r-w[k]>=d) and (cs+w[k]<=d)) :
        x[k]=0
        subsetsum(cs,k+1,r-w[k],x,w,d)
#driver for the above code
w=[2,3,4,5,0]
x=[0,0,0,0,0]

subsetsum(0,0,sum(w),x,w,7)     
0 голосов
/ 06 февраля 2017

Следующий код работает для Python 3.3+, я использовал модуль itertools в Python, который имеет несколько замечательных методов для использования.следующим образом:

<code>Enter the Elements 1 2 3 4
Enter the Sum You want 5
('1', '4')
('2', '3')
...