web-dev-qa-db-fra.com

Algorithme pour obtenir toutes les combinaisons de taille n à partir d'un tableau (Java)?

En ce moment, j'essaie d'écrire une fonction qui prend un tableau et un entier n, et donne une liste de chaque combinaison de taille n (donc une liste de tableaux int.). Je suis capable de l'écrire en utilisant n boucles imbriquées, mais cela ne fonctionne que pour une taille spécifique de sous-ensemble. Je n'arrive pas à comprendre comment le généraliser pour qu'il fonctionne pour n'importe quelle taille de combinaison. Je pense que je dois utiliser la récursivité?

C'est le code pour toutes les combinaisons de 3 éléments, et j'ai besoin d'un algorithme pour n'importe quel nombre d'éléments.

import Java.util.List;
import Java.util.ArrayList;

public class combinatorics{
    public static void main(String[] args) {

        List<int[]> list = new ArrayList<int[]>();
        int[] arr = {1,2,3,4,5};
        combinations3(arr,list);
        listToString(list);
    }

    static void combinations3(int[] arr, List<int[]> list){
        for(int i = 0; i<arr.length-2; i++)
            for(int j = i+1; j<arr.length-1; j++)
                for(int k = j+1; k<arr.length; k++)
                    list.add(new int[]{arr[i],arr[j],arr[k]});
    }

    private static void listToString(List<int[]> list){
        for(int i = 0; i<list.size(); i++){ //iterate through list
            for(int j : list.get(i)){ //iterate through array
                System.out.printf("%d ",j);
            }
        System.out.print("\n");
        }
    }
}
28
Esostack

C'est un problème bien étudié de générer tous les k-sous-ensembles, ou k-combinaisons , ce qui peut être facilement fait sans récursivité.

L’idée est d’avoir un tableau de taille k de conserver une séquence de indices d’éléments du tableau d’entrée (qui sont des nombres de 0 à n - 1) en ordre croissant. ( Un sous-ensemble peut ensuite être créé en prenant les éléments de ces index dans le tableau initial.) Nous devons donc générer toutes ces séquences d'index.

La première séquence d'index sera [0, 1, 2, ... , k - 1], à la deuxième étape, il passe à [0, 1, 2,..., k], puis à [0, 1, 2, ... k + 1] etc. La dernière séquence possible sera [n - k, n - k + 1, ..., n - 1].

À chaque étape, l'algorithme recherche l'élément le plus proche de l'élément final pouvant être incrémenté, l'incrémente et remplit les éléments jusqu'à cet élément.

Pour illustrer, considérons n = 7 et k = 3. La première séquence d'index est [0, 1, 2], puis [0, 1, 3] et ainsi de suite ... À un moment donné, nous avons [0, 5, 6]:

[0, 5, 6] <-- scan from the end: "6" cannot be incremented, "5" also, but "0" can be
[1, ?, ?] <-- "0" -> "1"
[1, 2, 3] <-- fill up remaining elements

next iteration:

[1, 2, 3] <-- "3" can be incremented
[1, 2, 4] <-- "3" -> "4"

Ainsi, [0, 5, 6] est suivi par [1, 2, 3], puis va [1, 2, 4] etc.

Code:

int[] input = {10, 20, 30, 40, 50};    // input array
int k = 3;                             // sequence length   

List<int[]> subsets = new ArrayList<>();

int[] s = new int[k];                  // here we'll keep indices 
                                       // pointing to elements in input array

if (k <= input.length) {
    // first index sequence: 0, 1, 2, ...
    for (int i = 0; (s[i] = i) < k - 1; i++);  
    subsets.add(getSubset(input, s));
    for(;;) {
        int i;
        // find position of item that can be incremented
        for (i = k - 1; i >= 0 && s[i] == input.length - k + i; i--); 
        if (i < 0) {
            break;
        }
        s[i]++;                    // increment this item
        for (++i; i < k; i++) {    // fill up remaining items
            s[i] = s[i - 1] + 1; 
        }
        subsets.add(getSubset(input, s));
    }
}

// generate actual subset by index sequence
int[] getSubset(int[] input, int[] subset) {
    int[] result = new int[subset.length]; 
    for (int i = 0; i < subset.length; i++) 
        result[i] = input[subset[i]];
    return result;
}
48
Alex Salauyou

Si j'ai bien compris votre problème, this article semble indiquer ce que vous essayez de faire.

Pour citer l'article:

Méthode 1 (Corriger les éléments et répéter)

Nous créons un tableau temporaire ‘data []’ qui stocke toutes les sorties, une par une. L'idée est de commencer à partir du premier index (index = 0) dans data [], fixez un à un les éléments de cet index et revenez pour les index restants. Soit le tableau en entrée {1, 2, 3, 4, 5} et r en 3. Nous fixons d’abord 1 à l’indice 0 dans data [], puis répétons pour les index restants, puis nous fixons 2 à l’indice 0 et récurrents. Enfin, nous corrigeons 3 et récidivons pour les index restants. Lorsque le nombre d'éléments dans les données [] devient égal à r (taille d'une combinaison), nous imprimons les données [].

Méthode 2 (Inclure et exclure chaque élément)

Comme la méthode ci-dessus, nous créons un tableau de données temporaire []. L'idée ici est similaire à celle du problème de sous-ensemble. Nous considérons un par un chaque élément du tableau en entrée et nous le répétons dans deux cas:

  1. L'élément est inclus dans la combinaison en cours (nous mettons l'élément dans data [] et incrémentons le prochain index disponible dans data [])
  2. L'élément est exclu dans la combinaison en cours (nous ne mettons pas l'élément et ne changeons pas d'index)

Lorsque le nombre d'éléments dans les données [] devient égal à r (taille d'une combinaison), nous l'imprimons.

5
Roney Michael

Vous pouvez le faire définitivement avec itération.

Voici une solution qui calcule le nombre de tableaux à créer, puis les construit en utilisant les mathématiques pour calculer quel élément du tableau source doit se trouver à quel endroit:

public static void combinations(int n, int[] arr, List<int[]> list) {
    // Calculate the number of arrays we should create
    int numArrays = (int)Math.pow(arr.length, n);
    // Create each array
    for(int i = 0; i < numArrays; i++) {
        int[] current = new int[n];
        // Calculate the correct item for each position in the array
        for(int j = 0; j < n; j++) {
            // This is the period with which this position changes, i.e.
            // a period of 5 means the value changes every 5th array
            int period = (int) Math.pow(arr.length, n - j - 1);
            // Get the correct item and set it
            int index = i / period % arr.length;
            current[j] = arr[index];
        }
        list.add(current);
    }
}

Mise à jour:

Voici une version optimisée qui réduit considérablement le nombre d'appels à Math.pow

public static void combinations(int n, int[] arr, List<int[]> list) {
    // Calculate the number of arrays we should create
    int numArrays = (int)Math.pow(arr.length, n);
    // Create each array
    for(int i = 0; i < numArrays; i++) {
        list.add(new int[n]);
    }
    // Fill up the arrays
    for(int j = 0; j < n; j++) {
        // This is the period with which this position changes, i.e.
        // a period of 5 means the value changes every 5th array
        int period = (int) Math.pow(arr.length, n - j - 1);
        for(int i = 0; i < numArrays; i++) {
            int[] current = list.get(i);
            // Get the correct item and set it
            int index = i / period % arr.length;
            current[j] = arr[index];
        }
    }
}
2
Raniz