Solución:
Subset Sum es el primer problema NP-completo que aprendí en Macalester. Esta pregunta se ve más de 36000 veces, pero no veo una respuesta suficiente que explique el algoritmo en detalle con lógica. Así que pensé en intentarlo.
Suposición:
En aras de la simplicidad, primero asumí que el conjunto de entrada X
contiene solo 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 de este algoritmo o realmente cualquier problema de DP es desglosar el problema y comenzar simplemente desde un caso base. luego podemos construir sobre el caso base usando algunos conocimientos que conocemos:
- sabemos que si el set
X
está vacío, entonces no hay forma de que podamos sumar ningún valor dek
. - Si un conjunto
X
contienek
entonces tiene una suma de subconjunto parak
. - sabemos que si un subconjunto del conjunto
x1
quien es un subconjunto deX
suma ak1
luegoX
tendrá un subconjunto que suma ak1
a saberx1
. - tenemos un set
X = {x1, x1, x3, ......., xn, xn+1}
. Sabemos que tiene una suma de subconjunto parak1
six1 = {x1, x1, x3, ......., xn}
tiene una suma de subconjunto ak - k1
.
Ejemplo para ilustrar 1,2,3,4:
- es fácil. si tiene un conjunto vacío {}. no puede tener un subconjunto, por lo que no puede tener ninguna suma de subconjunto.
-
Un conjunto
X = {4}
tiene un subconjunto suma a 4 porque 4 en sí mismo es parte del conjunto -
di que tienes un set
x1 = {1,3,5}
quien es un subconjunto del conjuntoX = {1,3,5,2,8}
. six1
tiene una suma de subconjunto ak1 = 8
entonces eso significaX
también tiene un subconjunto suma a 8 porquex1
es un subconjunto deX
- di que tienes un set
X = {1,3,5,2,19}
y queremos saber si tiene una suma de subconjunto a 20. La tiene y una forma de saber si esx1 = {1,3,5,2}
puede sumar a (20 – 19) = 1. Dado que x1 tiene un subconjunto suma a 1, cuando agregamos 19 al conjunto x1 podemos tomar ese nuevo número 1 + 19 = 20 para crear nuestra suma deseada 20.
Construye dinámicamente una matriz
¡Frio! ahora utilicemos las cuatro lógicas anteriores y comencemos a construir desde el caso base. Vamos a construir una matriz m
. Definimos:
-
matriz
m
tienei+1
filas yk + 1
columnas. -
Cada celda de la matriz tiene valor
true
ofalse
. -
metro[i][s] devuelve verdadero o falso para indicar la respuesta a esta pregunta: “usando el primer
i
elementos en la matriz podemos encontrar una suma de subconjunto paras
? ”m[i][s]
devolucionestrue
por si yfalse
por no
(tenga en cuenta la respuesta de Wikipedia o la mayoría de las personas construyen una función m (i, s) pero pensé que la matriz es una forma fácil de entender la programación dinámica. Funciona bien cuando solo tenemos números positivos en el conjunto o matriz. Sin embargo, la matriz La ruta de función es mejor porque no tiene que lidiar con el índice fuera de rango, el índice de coincidencia de la matriz y la suma de la matriz …)
Construyamos la matriz usando un ejemplo:
X = {1,3,5,2,8}
k = 9
Vamos a construir la matriz fila por fila. En última instancia, queremos conocer la celda m[n][k] Contiene true
o false
.
Primera fila:
La lógica 1 nos dijo que la primera fila de la matriz debería ser 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 superior:
Luego, para la segunda fila o superior, podemos usar la lógica 2, 3, 4 para ayudarnos a poblar la matriz.
- la lógica 2 nos dice que
m[i][s] = (X[i-1] == s)
rememebr m[i] se refiere al i-ésimo elemento de X, que es X[i-1] - la lógica 3 nos dice que
m[i][s] = (m[i-1][s])
esto es mirar la celda directamente arriba. - La lógica 4 nos dice que
m[i][s] = (m[i-1][s - X[i-1]])
esto es mirar la fila arriba y a la izquierda de X[i-1] células.
Si alguno de estos es true
luego m[i][s]
es true
de 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 anteriores para poblar 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 tu pregunta:
mirar m[5][9]
que es la pregunta original. utilizando los primeros 5 elementos (que son todos los elementos) ¿podemos encontrar una suma de subconjunto a 9 (k)? y la respuesta está indicada por esa celda 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 bounds: -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 bounds. (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 m
toma O ((n + 1) (k + 1)) que es O (nk). parece que debería ser polinomial, ¡pero no lo es! En realidad, es un pseudo polinomio. Leer sobre esto aquí
Nuevamente, esto solo funciona si la entrada solo contiene números positivos. Sin embargo, puede modificarlo fácilmente para que funcione con números negativos. La matriz todavía tendría n + 1 filas pero B - A + 1
columnas. Dónde B
es el límite superior y A
es el límite inferior (+1 para incluir cero) .La matriz aún sería 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 comprender este problema.
Tenga en cuenta que en los ejemplos anteriores, las filas de la tabla DP están ordenadas. Ese no tiene por qué ser el caso.
Aquí hay una tabla de DP para el caso de la pregunta, es decir, dado un conjunto de {5, 3, 11, 8, 2}. Por brevedad, he omitido los valores falsos.
┌─────────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ (index) │ 0 │ 2 │ 3 │ 5 │ 7 │ 8 │ 10 │ 11 │ 13 │ 14 │ 15 │ 16 │
├─────────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤
│ 0 │ true │ │ │ │ │ │ │ │ │ │ │ │
│ 5 │ true │ │ │ true │ │ │ │ │ │ │ │ │
│ 3 │ true │ │ true │ true │ │ true │ │ │ │ │ │ │
│ 11 │ true │ │ true │ true │ │ true │ │ true │ │ true │ │ true │
│ 8 │ true │ │ true │ true │ │ true │ │ true │ true │ true │ │ true │
│ 2 │ true │ true │ true │ true │ true │ true │ true │ true │ true │ true │ true │ true │
└─────────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
A continuación se muestra una implementación en JavaScript que generará el conjunto de destino {5, 11}:
var subSetSum = function(input, sum) {
let y = input.length;
let x = sum;
if(input.length === 0) return 0;
let d = [];
//fill the rows
for (let i = 0; i <= y; i++) {
d[i] = [];
d[i][0] = true;
}
for (let j = 1; j <= y; j++) { //j row
for (let i = 1; i <= x; i++) { //i column
let num = input[j-1];
if(num === i) {
d[j][i] = true;
} else if(d[j-1][i]) {
d[j][i] = true;
} else if (d[j-1][i-num]) {
d[j][i] = true;
}
}
}
//console.table(d); //uncomment to see the table
if(!d[y][x]) return null;
let searchedSet = [];
for(let j=input.length, i=sum; j>0 && i != 0; j--) {
if(input[j-1] !== i) {
while(d[j-1][i]) { // go up
j--;
}
}
searchedSet.push(input[j-1]);
i = i-input[j-1];
}
return searchedSet;
};
console.log('searched set:'+ JSON.stringify(subSetSum([5, 3, 11, 8, 2], 16)));
Como parece que todos sus números son positivos, puede resolver esto usando programación dinámica:
Comenzará una matriz booleana possible
de tamaño K + 1 con el primer valor verdadero, el resto falso. El i-ésimo valor representará si es posible lograr un subconjunto de i. Para cada número n en su conjunto, recorra el possible
matriz y si el i-ésimo valor es verdadero, entonces establezca el i + n-ésimo valor en verdadero también.
Al final, si el k-ésimo valor en possible
es verdadero, entonces puede formar un subconjunto de k. Problema resuelto en tiempo O (NK).
La página de Wikipedia sobre el problema de la suma de subconjuntos tiene una explicación detallada de este algoritmo aplicado a conjuntos de números enteros que no se garantiza que sean positivos.
Sugeriría leer el algoritmo de Wiki. El algoritmo existe allí, ver Solución de programación dinámica de tiempo pseudopolinomial Para el O(P*n)
solución, La solución no es polinomio en tiempo, es polinomio en (p, n) pero no es polinomio en n + log P (tamaño de entrada) y porque P
puede ser muy grande como 2 ^ n, la solución P * n = (2 ^ n) * n no es una solución de tiempo polinomial en general, pero cuando p está limitado por alguna función polinomial de n es un algoritmo de tiempo polinomial.
Este problema es NPC, pero hay un Pseudo polynomial time
algoritmo para ello, y pertenece a weakly NP-Complete
problemas, también hay Strongly NP-Complete
problemas, lo que significa que no puede encontrar ningún pseudo polynomial time
algoritmo para ellos a menos que P = NP, y este problema no está en este rango de problemas, por lo 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 Muy NP-Completo o Débilmente NP-Completo.
Para obtener más detalles, consulte el capítulo 4 de Garey y Johnson.