web-dev-qa-db-ja.com

サイズnのすべての組み合わせを配列から取得するアルゴリズム(Java)

今、私は配列と整数nを受け取り、各サイズnの組み合わせのリスト(int配列のリスト)を与える関数を書き込もうとしています。 n個のネストされたループを使用して作成できますが、これは特定のサイズのサブセットでのみ機能します。どのようなサイズの組み合わせでも機能するように一般化する方法がわかりません。再帰を使用する必要があると思いますか?

これは、3つの要素のすべての組み合わせのコードであり、任意の数の要素のアルゴリズムが必要です。

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

これは、すべてのkサブセットまたは k-combinations を生成するというよく研究された問題であり、再帰なしで簡単に実行できます。

アイデアは、サイズがkの配列にindicesの入力配列の要素のシーケンス(_0_から_n - 1_までの番号)を昇順で保持することです。 。 (サブセットは、初期配列からこれらのインデックスによってアイテムを取得することで作成できます。)そのため、このようなインデックスシーケンスをすべて生成する必要があります。

最初のインデックスシーケンスは_[0, 1, 2, ... , k - 1]_になり、2番目のステップで_[0, 1, 2,..., k]_に、次に_[0, 1, 2, ... k + 1]_に、というように切り替わります。最後の可能なシーケンスは_[n - k, n - k + 1, ..., n - 1]_です。

各ステップで、アルゴリズムは、増分できる最終アイテムに最も近いものを探し、増分し、そのアイテムのすぐ近くにアイテムを埋めます。

説明のために、_n = 7_および_k = 3_を検討してください。最初のインデックスシーケンスは_[0, 1, 2]_、次に_[0, 1, 3]_などです...ある時点で_[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"
_

したがって、_[0, 5, 6]_の後に_[1, 2, 3]_が続き、_[1, 2, 4]_などになります。

コード:

_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

私があなたの問題を正しく理解していれば、 this の記事はあなたがやろうとしていることを指しているようです。

記事から引用するには:

方法1(要素を修正して繰り返し)

すべての出力を1つずつ保存する一時配列「data []」を作成します。このアイデアは、data []の最初のインデックス(インデックス= 0)から開始し、このインデックスの要素を1つずつ修正し、残りのインデックスについて繰り返します。入力配列を{1、2、3、4、5}、rを3とする最後に、3を修正し、残りのインデックスについて繰り返します。 data []の要素数がr(組み合わせのサイズ)と等しくなると、data []を出力します。

方法2(すべての要素を含めるおよび除外する)

上記のメソッドと同様に、一時配列data []を作成します。ここでの考え方は、サブセット合計問題に似ています。入力配列のすべての要素を1つずつ検討し、2つの場合に繰り返します。

  1. 要素は現在の組み合わせに含まれています(要素をdata []に入れ、次に利用可能なインデックスをdata []に増やします)
  2. 要素は現在の組み合わせから除外されます(要素を配置せず、インデックスを変更しません)

Data []の要素数がr(組み合わせのサイズ)と等しくなると、それを出力します。

5
Roney Michael

繰り返しでこれを確実に行うことができます。

作成する配列の数を計算し、数学を使用してそれらを構築し、ソース配列のどの項目をどの場所に配置するかを計算する1つのソリューションを次に示します。

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);
    }
}

更新:

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