2008-09-17 12 views
12

Tengo un número decimal (vamos a llamarlo objetivo) y una serie de otros números decimales (Vamos a llamar a la matriz elementos) y necesito para encontrar todas las combinaciones de números de elementos que suman al objetivo.algoritmo para encontrar la que los números de una lista de tamaño n suma a otro número

Tengo una preferencia para una solución en C# (.Net 2.0) pero puede el mejor algoritmo ganar independientemente.

Su firma del método podría ser algo como:

public decimal[][] Solve(decimal goal, decimal[] elements) 

Respuesta

9

Interesantes respuestas. Gracias por los consejos para la wikipedia, aunque interesantes, en realidad no resuelven el problema, ya que estaba buscando coincidencias exactas, más un problema de borrado contable/libro que un problema tradicional de bin-packing/knapsack.

He estado siguiendo el desarrollo del desbordamiento de pila con interés y me he preguntado qué tan útil sería. Este problema surgió en el trabajo y me pregunté si el desbordamiento de la pila podría proporcionar una respuesta preparada (o una mejor respuesta) más rápido de lo que podría escribirlo yo mismo. Gracias también por los comentarios que sugieren que se etiquete la tarea. Supongo que es bastante precisa a la luz de lo anterior.

Para aquellos que estén interesados, aquí está mi solución que utiliza la recursividad (naturalmente) también he cambiado de opinión acerca de la firma del método y se fue para la lista> en lugar de decimales [] [] como el tipo de retorno:

public class Solver { 

    private List<List<decimal>> mResults; 

    public List<List<decimal>> Solve(decimal goal, decimal[] elements) { 

     mResults = new List<List<decimal>>(); 
     RecursiveSolve(goal, 0.0m, 
      new List<decimal>(), new List<decimal>(elements), 0); 
     return mResults; 
    } 

    private void RecursiveSolve(decimal goal, decimal currentSum, 
     List<decimal> included, List<decimal> notIncluded, int startIndex) { 

     for (int index = startIndex; index < notIncluded.Count; index++) { 

      decimal nextValue = notIncluded[index]; 
      if (currentSum + nextValue == goal) { 
       List<decimal> newResult = new List<decimal>(included); 
       newResult.Add(nextValue); 
       mResults.Add(newResult); 
      } 
      else if (currentSum + nextValue < goal) { 
       List<decimal> nextIncluded = new List<decimal>(included); 
       nextIncluded.Add(nextValue); 
       List<decimal> nextNotIncluded = new List<decimal>(notIncluded); 
       nextNotIncluded.Remove(nextValue); 
       RecursiveSolve(goal, currentSum + nextValue, 
        nextIncluded, nextNotIncluded, startIndex++); 
      } 
     } 
    } 
} 

Si quieres una aplicación para probar esto funciona, prueba este código de la aplicación de consola:

class Program { 
    static void Main(string[] args) { 

     string input; 
     decimal goal; 
     decimal element; 

     do { 
      Console.WriteLine("Please enter the goal:"); 
      input = Console.ReadLine(); 
     } 
     while (!decimal.TryParse(input, out goal)); 

     Console.WriteLine("Please enter the elements (separated by spaces)"); 
     input = Console.ReadLine(); 
     string[] elementsText = input.Split(' '); 
     List<decimal> elementsList = new List<decimal>(); 
     foreach (string elementText in elementsText) { 
      if (decimal.TryParse(elementText, out element)) { 
       elementsList.Add(element); 
      } 
     } 

     Solver solver = new Solver(); 
     List<List<decimal>> results = solver.Solve(goal, elementsList.ToArray()); 
     foreach(List<decimal> result in results) { 
      foreach (decimal value in result) { 
       Console.Write("{0}\t", value); 
      } 
      Console.WriteLine(); 
     } 


     Console.ReadLine(); 
    } 
} 

espero que esto ayude a alguien más tiene la respuesta más rápidamente (ya sea para hacer la tarea o de otra manera).

Saludos ...

+0

Una solución recursiva sería 1/3 de la longitud y 3 veces más elegante. – Haoest

+0

Es una solución recursiva. Me interesaría ver tu solución de 1/3 de longitud. ¿Tal vez debería examinar la solución antes de comentar? –

+0

Las coincidencias exactas son la coincidencia más cercana. Si la coincidencia más cercana no es la coincidencia exacta, entonces no hay una coincidencia exacta. – wnoise

3

Creo que tienes un bin packing problem en sus manos (que es NP-duro), así que creo que la única solución va a ser tratar cada combinación posible hasta que encuentre una que funcione.

Editar: No Como se señala en un comentario, se quiere siempre tienen que tratar cada combinación de cada conjunto de números que se encuentre. Sin embargo, cualquier método que se le ocurra tiene el peor conjunto de escenarios de casos en los que tendrá tiene que probar cada combinación, o al menos un subconjunto de combinaciones que crece exponencialmente con el tamaño del conjunto.

De lo contrario, no sería NP-difícil.

+1

No todos ... Una vez que la suma de una cierta combinación excede el número objetivo, puede dejar de calcular esa rama y pasar a la siguiente. – Haoest

2

Usted ha descrito un knapsack problem, la única solución verdadera es la fuerza bruta. Hay algunas soluciones de aproximación que son más rápidas, pero pueden no ajustarse a sus necesidades.

3

El problema de suma de subconjuntos y el problema levemente más general de la mochila se resuelven con la programación dinámica: no se requiere la enumeración de fuerza bruta de todas las combinaciones. Consulte Wikipedia o su referencia de algoritmos favoritos.

Aunque los problemas son NP-completos, son NP-completos muy "fáciles". La complejidad algorítmica en la cantidad de elementos es baja.

2

Aunque no es la solución del problema de la fuerza bruta (como otros ya mencionados) es posible que desee ordenar primero sus números, y luego ir a través de los posibles que quedan (ya que una vez que ha pasado el valor Suma , no puede agregar ningún número más grande que Objetivo - Suma).

Esto cambiará la forma de implementar el algoritmo (para ordenar solo una vez y luego omitir los elementos marcados), pero en promedio mejoraría el rendimiento.

+0

Adi, gracias por la sugerencia. Una buena mejora para el caso general. Mi solución en realidad funciona mejor para el problema que tengo, pero no le di suficiente información para saberlo. Los números están en orden de fecha y es más probable que sumen en ese orden ya que las fechas son importantes para los totales. –

-1
public class Logic1 { 
    static int val = 121; 
    public static void main(String[] args) 
    { 
     f(new int[] {1,4,5,17,16,100,100}, 0, 0, "{"); 
    } 

    static void f(int[] numbers, int index, int sum, String output) 
    { 
     System.out.println(output + " } = " + sum); 
     //System.out.println("Index value1 is "+index); 
     check (sum); 
     if (index == numbers.length) 
     { 
      System.out.println(output + " } = " + sum); 
      return; 
     } 

     // include numbers[index] 
     f(numbers, index + 1, sum + numbers[index], output + " " + numbers[index]); 
     check (sum); 
     //System.out.println("Index value2 is "+index); 
     // exclude numbers[index] 
     f(numbers, index + 1, sum, output); 
     check (sum); 
    } 

    static void check (int sum1) 
    { 
     if (sum1 == val) 
      System.exit(0); 
    } 
} 
Cuestiones relacionadas