2010-12-04 9 views
21

estoy trabajando en este problema:Subset Sum algoritmo

problema el subconjunto Suma toma como entrada un conjunto de X = {x1, x2 ,…, xn}n enteros y otro entero K. El problema es verificar si existe un subconjunto X' de X cuyos elementos se suman a K y encuentra el subconjunto si hay alguno. Por ejemplo, si X = {5, 3, 11, 8, 2} y K = 16, la respuesta es YES ya que el subconjunto X' = {5, 11} tiene una suma de 16. Implemente un algoritmo para la Suma del subconjunto cuyo tiempo de ejecución sea al menos O(nK).

Observe la complejidad O(nK). Creo que la programación dinámica puede ayudar.

He encontrado un algoritmo de tiempo exponencial, pero no ayuda.

¿Alguien me puede ayudar a resolver este problema?

Respuesta

1

No existe un algoritmo conocido para la suma de subconjuntos que se ejecute en menos de O (2^(n/2)), en el caso general.

+0

Probablemente este no sea el caso general. Ver mi respuesta – marcog

+9

-1: hay uno que se ejecuta en la complejidad que el OP quiere, por lo que su respuesta realmente no es útil y también irrelevante. – IVlad

+3

@ivlad Un poco duro, ya que @DeadMG es técnicamente correcto. OP no declaró que el conjunto de enteros siempre es positivo, lo que asume mi respuesta. – marcog

16

ya que parece que todos los números son positivos, se puede resolver esto utilizando programación dinámica:

salida será una matriz booleana possible de tamaño K + 1 con el primer valor verdadero, el resto falsa. El i-ésimo valor representará si es posible lograr una suma de subconjuntos de i. Para cada número n en su conjunto, recorra la matriz possible y si el valor ith es verdadero, establezca el valor de i + n en verdadero también.

Al final, si el valor kth en possible es verdadero, entonces puede formar una suma de subconjuntos de k. Problema resuelto en el tiempo O (NK).

Wikipedia's page on the subset sum problem tiene una explicación detallada de este algoritmo aplicado a conjuntos de enteros que no se garantiza que sean positivos.

+3

¿Es posible que 'i + n' sea mayor que' K + 1'? – MLister

8

Sugiero leer el algoritmo de Wiki. El algoritmo existe allí, consulte Solución de programación dinámica de tiempo pseudopolinomial para la solución O(P*n), La solución no es tiempo polinomial, es polinomial en (p, n) pero no es polinomial en n + log P (tamaño de entrada) y dado que P puede ser muy grande como 2^n, la solución P * n = (2^n) * n no es una solución polinómica temporal en general, pero cuando p está limitada por alguna función polinómica de n es un algoritmo de tiempo polinomial.

Este problema es de la APN, pero hay un algoritmo Pseudo polynomial time por ella, y pertenece a weakly NP-Complete problemas, también existen problemas Strongly NP-Complete, lo que significa, no se puede encontrar cualquier algoritmo pseudo polynomial time para ellos a menos que P = NP, y esto El problema no está en esta gama de problemas, así que de alguna manera es fácil.

Dije esto de la manera más simple posible, pero no es una definición exacta de un problema de Completamente NP-Completo o Débilmente NP-Completo.

para más detalles véase el capítulo 4. Garey and Johnson solución de DP

2
void subsetSum (int arr[], int size, int target) { 
    int i, j ; 
    int **table ; 
    table = (int **) malloc (sizeof(int*) * (size+1)) ; 
    for (i = 0 ; i <= size ; i ++) { 
    table[i] = (int *) malloc (sizeof(int) * (target+1)) ; 
    table[i][0] = 1 ; 
    } 
    for (j = 1 ; j <= target ; j ++) 
    table[0][j] = 0 ; 
    for (i = 1 ; i <= size ; i ++) { 
    for (j = 1 ; j <= target ; j ++) 
     table[i][j] = table[i-1][j] || (arr[i-1] <= j && table[i-1][j-arr[i-1]]) ; 
    } 
    if (table[size][target] == 1) 
    printf ("\ntarget sum found\n") ; 
    else printf ("\nTarget sum do not found!\n") ; 
    free (table) ; 
} 
+5

¿Puedes dar una explicación ... por favor? –

+0

Sea S [i, j] definido como verdadero si hay un subconjunto de los elementos A [1. . . i] eso suma a j. Entonces S [n, T] es la solución a nuestro problema. En general: S [i, j] = S [i - 1, j - A [i]] ∨ S [i - 1, j] Las condiciones iniciales son S [i, 0] = Verdadero y S [0, j] = Falso, para j> 0. – Psycho

+1

Dado que calcula los valores en 'tabla [i]' utilizando solo valores en 'tabla [i-1]', puede ahorrar espacio al hacer que su dimensión externa sea solo 2 de 'size', e indexarlo con' i% 2' en lugar de 'i'. Es decir. intercambie la matriz "actual" en cada iteración externa. –

0

con una matriz unidimensional (orden de procesamiento de señal DP sí importa aquí).

bool subsetsum_dp(vector<int>& v, int sum) 
{ 
    int n = v.size(); 
    const int MAX_ELEMENT = 100; 
    const int MAX_ELEMENT_VALUE = 1000; 
    static int dp[MAX_ELEMENT*MAX_ELEMENT_VALUE + 1]; memset(dp, 0, sizeof(dp)); 

    dp[0] = 1; 

    for (int i = 0; i < n; i++) 
    { 
     for (int j = MAX_ELEMENT*MAX_ELEMENT_VALUE; j >= 0; j--) 
     { 
      if (j - v[i] < 0) continue; 
      if (dp[j - v[i]]) dp[j] = 1; 
     } 
    } 

    return dp[sum] ? true : false; 
} 
0

deje que M sea la suma de todos los elementos. Nota que K < = M

let m be a Boolean array [0...M] 
set all elements of m to be False 
m[0]=1 
for all numbers in the set let a[i] be the ith number 
    for j = M to a[i] 
     m[j] = m[j] | m[j-a[i]]; 

Después, simplemente prueba para m [k]

+0

para la inicial es correcto marcar 'm [0]' como verdadero, pero también debe marcar 'm [x]' para que sea verdadero si x está en la matriz '[0 .... M]' –

0
boolean hasSubset(int arr[],int remSum,int lastElem){ 
    if(remSum==0) return true; 
    else if(remSum!=0 && lastElem<0) return false; 

    if(arr[lastElem]>remSum) return hasSubset(arr, remSum, lastElem-1); 
    else return (hasSubset(arr, remSum, lastElem-1) ||hasSubset(arr, remSum-arr[lastElem], lastElem-1)); 
} 

Considere i-ésimo elemento. O contribuirá para la suma del subconjunto o no. si contribuye para la suma, entonces el "valor de suma" se reduce en el valor igual al elemento i-ésimo. Si no contribuye, entonces necesitamos buscar el "valor de suma" en los elementos restantes.

5

Esta pregunta se ve más de 36000 veces, pero no veo una respuesta suficiente que explique el algoritmo en detalle con la lógica. Así que pensé en hacer un intento de hacerlo.

Supuesto:

En aras de la simplicidad principio me hizo la suposición de que el conjunto de entrada X contiene sólo números enteros positivos y k es positivo. Sin embargo, podemos ajustar el algoritmo para manejar enteros negativos y el caso si k es negativo.

Lógica:

La clave para este algoritmo o realmente ningún problema DP se aBREAK minimizar el problema y empezar simplemente de un caso base. entonces podemos construir sobre el caso base usar algún conocimiento que sabemos:

  1. sabemos que si el conjunto X está vacía, entonces no hay manera de que podamos sumar a cualquier valor de k.
  2. Si un conjunto X contiene k, entonces tiene un subconjunto de suma de k.
  3. sabemos que si un subconjunto de conjunto x1 que es un subconjunto de X suma a continuación k1X tendrá un subconjunto de ese sub k1 a saber x1.
  4. tenemos un conjunto X = {x1, x1, x3, ......., xn, xn+1}. Sabemos que tiene una suma de subconjuntos de k1 si x1 = {x1, x1, x3, ......., xn} tiene un subconjunto de suma de k - k1.

ejemplo para ilustrar 1,2,3,4:

  1. es fácil. si tienes un conjunto vacío {}. no puede tener un subconjunto, por lo tanto , no puede tener ninguna suma de subconjuntos.
  2. Un conjunto X = {4} tiene una suma de subconjuntos a 4, porque 4 en sí es parte del conjunto

  3. decir que tiene un conjunto x1 = {1,3,5} que es un subconjunto del conjunto X = {1,3,5,2,8}.Si x1 tiene una suma de subconjuntos a k1 = 8 entonces eso significa X también tiene una suma de subconjuntos a 8 porque x1 es un subconjunto de X

  4. decir que tiene un conjunto X = {1,3,5,2,19} y queremos saber qué tiene una suma de subconjuntos a 20. Sí, y una forma de saber si eso es x1 = {1,3,5,2} puede sumar (20 - 19) = 1. Dado que x1 tiene un subconjunto suma a 1, entonces cuando agreguemos 19 al conjunto x1 podemos tomar ese nuevo número 1 + 19 = 20 para crear nuestra suma deseada 20.

construir dinámicamente una matriz ¡Genial! ahora usemos las tres lógicas anteriores y comencemos a construir desde el caso base. Vamos a construir una matriz m. Definimos:

  • matriz m tiene i+1 filas y columnas k + 1.

  • Cada celda de la matriz tiene el valor true o false.

  • m [i] [s] devuelve verdadero o falso para indicar la respuesta a esta pregunta: "? Usando los primeros i elementos de la matriz podemos encontrar una suma de subconjuntos a s" m[i][s] vuelve true para sí y false sin

(tenga en cuenta la respuesta Wikipedia o la mayoría de las personas a construir una función m (i, s), pero pensé que la matriz es una manera fácil de entender la programación dinámica. funciona bien cuando sólo tenemos positivo números en el conjunto o matriz. Sin embargo, la ruta de función es mejor porque no tiene que tratar con índice fuera de rango, índice de coincidencia de la matriz y suma a la matriz .....)

Vamos a construir la matriz mediante un ejemplo:

X = {1,3,5,2,8} 
k = 9 

Vamos a construir la matriz fila por fila. En última instancia, deseamos saber que la celda m [n] [k] contiene true o false.

Primera Fila: Lógica 1. Nos dijo que la primera fila de la matriz debe ser todos false.

0 1 2 3 4 5 6 7 8 9 
    _ _ _ _ _ _ _ _ _ _ 
0| F F F F F F F F F F 
1| 
2| 
3| 
4| 
5| 

segunda fila y arriba: Luego de la segunda fila o por encima, podemos usar la lógica 2,3,4 para ayudarnos a poblar la matriz.

  • lógica 2 nos dice que m[i][s] = (X[i-1] == s) rememebr m [i] se refiere al elemento i-ésimo en X que es X [i-1]
  • lógica 3 nos dice que m[i][s] = (m[i-1][s]) este está mirando a la célula direclty de encima .
  • lógica 4 nos dice que m[i][s] = (m[i-1][s - X[i-1]]) esto está mirando la fila arriba y a la izquierda de las celdas X [i-1].

Si cualquiera de ellos es true continuación m[i][s] es true lo contrario false. para que podamos reescribir 2,3,4 en m[i][s] = (m[i-1][s] || a[i-1] == s || m[i-1][s - a[i-1]])

Utilice estas lógicas para llenar la matriz m. En nuestro ejemplo, se ve así.

0 1 2 3 4 5 6 7 8 9 
    _ _ _ _ _ _ _ _ _ _ 
0| F F F F F F F F F F 
1| F T F F F F F F F F 
2| F T F T T F F F F F 
3| F T F T T T T F T T 
4| F T T T T T T T T T 
5| F T T T T T T T T T 

Ahora usa la matriz para responder a su pregunta:

vistazo a m[5][9] que es la pregunta original. usando los primeros 5 ítems (que son todos los ítems) ¿podemos encontrar una suma de subconjuntos en 9 (k)? y la respuesta se indica por esa célula que es true

Aquí está el código:

import java.util.*; 

public class SubSetSum { 

    public static boolean subSetSum(int[] a, int k){ 

     if(a == null){ 
      return false; 
     } 

     //n items in the list 
     int n = a.length; 
     //create matrix m 
     boolean[][] m = new boolean[n + 1][k + 1]; //n + 1 to include 0, k + 1 to include 0 

     //set first row of matrix to false. This also prevent array index out of bounce: -1 
     for(int s = 0; s <= k; s++){ 
      m[0][s] = false; 
     } 

     //populate matrix m 
     for(int i = 1; i <= n; i++){ 
      for(int s = 0; s <= k; s++){  
       if(s - a[i-1] >= 0){ //when it goes left we don't want it to go out of bouce. (logic 4) 
        m[i][s] = (m[i-1][s] || a[i-1] == s || m[i-1][s - a[i-1]]); 
       } else { 
        m[i][s] = (m[i-1][s] || a[i-1] == s); 
       }  

      } 
     } 

     //print matrix 
     print(m); 

     return m[n][k]; 

    } 

    private static void print(boolean[][] m){ 
     for(int i = 0; i < m.length; i++){ 
      for(int j = 0; j < m[i].length; j++){ 
       if(m[i][j]){ 
        System.out.print("T"); 
       } else { 
        System.out.print("F"); 
       }   
      } 
      System.out.print("\n"); 
     } 
    } 

    public static void main(String[] args){ 
     int[] array = {1,3,5,2,8}; 
     int k = 9; 

     System.out.println(subSetSum(array,k)); 

    } 
} 

Para construir la matriz de m toma O ((n + 1) (k + 1)), que es O (nk). parece que debería ser un polinomio, ¡pero no lo es! En realidad es un pseudo polinomio. Lea al respecto here

De nuevo, esto solo funciona si la entrada solo contiene números positivos. Usted puede modificarlo fácilmente para trabajar con números negativos aunque. La matriz todavía tendría n + 1 filas pero B - A + 1 columnas. Donde B es el límite superior y A es el límite inferior (+1 para incluir cero). La matriz aún sería Usted tendría que compensar s con el límite inferior.

Es bastante difícil explicar el problema de DP sobre el texto de principio a fin. Pero espero que esto ayude a aquellos que intentan entender este problema.

0

Las respuestas anteriores son geniales, pero realmente no ofrecen la descripción más amplia de cómo algo así podría funcionar para números positivos y negativos.

Dado un conjunto ordenado de números enteros, Definir dos variables X e Y tal que

X = suma de elementos negativos

Y = suma de elementos positivos

y operar en su conjunto inicial como Si estaba recurriendo a través de un árbol binario aplicando estas reglas en este orden

  1. Si el elemento de la derecha es igual a la suma que está intentando verificar para el retorno verdadera
  2. Recurse izquierda si el hacerlo no dejar el vacío conjunto, deje caer el elemento más a la derecha de su matriz ordenada
  3. Si hay un elemento que queda en su conjunto y no es la suma return false
  4. vez de manera recursiva derecha, comprobar la suma de todos los elementos en el array q, si X < = B < = y luego regresar verdadera, de no volver falsa
  5. Si el subárbol izquierdo o el derecho 'recursión' devueltos cierto entonces devolver verdadero al padre

Las respuestas anteriores son más detalladas y precisas, pero para obtener una visión muy amplia de cómo debería funcionar, dibuje un árbol binario. ¿Qué sugiere la longitud de esto sobre el tiempo de ejecución?