2010-04-08 11 views
5

Desafío de programación: dado un conjunto de enteros [1, 2, 3, 4, 5] Me gustaría generar todas las k-combinaciones posibles en orden de tamaño ascendente en Java; p.ej.k-combinaciones de un conjunto de números enteros en orden ascendente de orden

[1], [2], [3], [4], [5], [1, 2], [1, 3] ... [1, 2, 3, 4, 5] 

Es bastante fácil de producir una solución recursiva que genera todas las combinaciones y luego ordenarlos después, pero me imagino que hay una manera más eficiente que elimina la necesidad de la clase adicional.

+0

Publica un código? Dependiendo de cómo implemente esto, puede que solo requiera una pequeña modificación de lo que ya tiene. Por ejemplo, estás trabajando con una pila, ¿verdad? En cada paso recursivo, comience a iterar desde la pila [paso recursivo - 1] + 1 en lugar de desde 0. – IVlad

+0

Si publico un código, se arruinará el desafío de programación :-) – Adamski

+0

Bueno, lo siento, pero esto no es mucho desafío :). Lo he escrito cientos de veces para el CS de la escuela secundaria. – IVlad

Respuesta

1

Estaba pensando en esto un poco más y me di cuenta de que se puede hacer de manera eficiente utilizando un enfoque de programación dinámica. A continuación se muestra la solución iterativa que he producido, que utiliza un Queue para mantener el estado (aunque en su lugar se podría usar un Stack).

Creo que esto es mucho más eficiente que una búsqueda recursiva de profundización iterativa, ya que no implicará volver a visitar estados existentes durante la generación; recuerda los estados anteriores usando la cola y los usa para generar estados sucesivos.

Comparación de rendimiento

Algorithm     | 5 elems | 10 elems | 20 elems 
-------------------------------------------------------------------------- 
Recursive (#recursions)  | 62  | 2046  | 2097150 
Dynamic (#loop iterations) | 32  | 1024  | 1048576 

Código

public class Test { 
    private static class Pair { 
     private final List<Integer> result; 
     private final int index; 

     private Pair(List<Integer> result, int index) { 
      this.result = result; 
      this.index = index; 
     } 

     public List<Integer> getResult() { 
      return result; 
     } 

     public int getIndex() { 
      return index; 
     } 
    } 

    public static void main(String[] args) { 
     List<Integer> items = Arrays.asList(1, 2, 3, 4, 5); 
     foo(items); 
    } 

    private static void foo(List<Integer> items) { 
     Queue<Pair> queue = new LinkedList<Pair>(); 
     queue.add(new Pair(Collections.<Integer>emptyList(), 0)); 

     while (!queue.isEmpty()) { 
      Pair pair = queue.poll(); 

      System.err.println(pair.getResult()); 

      if (pair.getResult().size() < items.size()) { 
       for (int i=pair.getIndex(); i<items.size(); ++i) { 
        List<Integer> copy = new LinkedList<Integer>(pair.getResult()); 
        copy.add(items.get(i)); 
        queue.add(new Pair(copy, i + 1)); 
       } 
      } 
     } 
    } 
} 
+1

Esto es BFS, por lo tanto, usa espacio exponencial (es decir, muy ineficiente). DFS usa espacio lineal (eficiente), pero no imprime en orden de tamaño ascendente. Es por eso que se crea IDDFS: tiene características de ambos, ya que utiliza el espacio de manera eficiente, pero aún visitas en "orden de nivel" (es decir, imprime en orden ascendente en su escenario). Además, lea el artículo de wikipedia sobre la complejidad de IDDFS. Solo es dos veces más largo que BFS (que es lo que sugieren también tus números), para un uso mucho más eficiente del espacio. Es la clásica compensación de tiempo/espacio entre BFS y DFS, con IDDFS en el medio. – polygenelubricants

0

La generación de combinaciones lleva MUCHO más tiempo que la clasificación, y no toma tanto tiempo ordenar 100,000 números dado el tiempo de clasificación n*log(n). Estás pre-optimizando. Esto es malo.

+0

100,000 era un número redondeado de estadio. Si los números pudieran repetirse, estaría limitado por 5 ** 5 ~ = 3125, que es una cantidad trivial de elementos. –

+0

De acuerdo en que la optimización prematura es una pérdida de tiempo, pero esto no es tanto un problema del mundo real como un desafío para producir un algoritmo más eficiente. – Adamski

+0

No estoy de acuerdo. La ordenación de n elementos puede tomar n * log (n) time. Mi respuesta anterior los genera (en el orden incorrecto) exactamente en ese momento, en lugar de "MUCHO más". Tenga en cuenta que generar combinaciones de n elementos puede tomar 2^n tiempo, pero debe decidir si n es el número de elementos en la lista original o el número total de listas en el resultado. – John

9

Solo haz iterative deepening search.

void comb(int... items) { 
    Arrays.sort(items); 
    for (int k = 1; k <= items.length; k++) { 
     kcomb(items, 0, k, new int[k]); 
    } 
} 
public void kcomb(int[] items, int n, int k, int[] arr) { 
    if (k == 0) { 
     System.out.println(Arrays.toString(arr)); 
    } else { 
     for (int i = n; i <= items.length - k; i++) { 
      arr[arr.length - k] = items[i]; 
      kcomb(items, i + 1, k - 1, arr); 
     } 
    } 
} 

Luego llame, por ejemplo, comb(10,20,30). Se imprimirá:

[10] 
[20] 
[30] 
[10, 20] 
[10, 30] 
[20, 30] 
[10, 20, 30] 
+0

Genial - Nunca antes había escuchado sobre esa técnica de búsqueda. – Adamski

+3

Es el hijo bastardo de BFS y DFS. – polygenelubricants

+0

Este fue un gran punto de partida y me di cuenta de que podía mejorar recordando estados anteriores durante la generación (ver mi respuesta). – Adamski

3

Existen dos formas de interpretar el requisito "ascendente". La interpretación más flexible es "en cada lista, los enteros deben aparecer en orden ascendente". La interpretación más estricta es "las listas deben darse en orden". Supongo que es lo que quieres, pero se me ocurrió una forma iterativa simple de satisfacer el requisito más flexible.

Para n elementos, cuente a través de todos los números de n bits. Si el bit correspondiente a un elemento está allí, entonces está en la lista de resultados.

public static void displaySubsets(List<Integer> sortedInts) { 
    int n=sortedInts.size(); 
    long combinations = 1 << n; 
    for (int setNumber=0; setNumber<combinations; setNumber++) { 
     List<Integer> aResult = new ArrayList<Integer>(); 
     for (int digit=0; digit<n; digit++) { 
     if ((setNumber & (1<<digit)) > 0) { 
      aResult.add(sortedInts.get(digit)); 
     } 
     } 
     System.out.println(aResult.toString()+", "); 
    } 
    } 

Resultado para 1,2,3,4,5 es: [], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3], [4], [1, 4], [2, 4], [1, 2, 4], [3 , 4], [1, 3, 4], [2, 3, 4], [1, 2, 3, 4], [5], [1, 5], [2, 5 ], [1, 2, 5], [3, 5], [1, 3, 5], [2, 3, 5], [1, 2, 3, 5], [4, 5 ], [1, 4, 5], [2, 4, 5], [1, 2, 4, 5], [3, 4, 5], [1, 3, 4, 5] , [2, 3, 4, 5], [1, 2, 3, 4, 5]

Sí, lo sé, pierdo puntos por no usar recursividad.

+1

Buen intento, pero en realidad estaba buscando las listas de salida en orden de tamaño ascendente. – Adamski

+0

Eso pensé. Gracias. – John

Cuestiones relacionadas