从数组中获取大小为n的所有组合的算法(Java)?

时间:2015-04-28 04:25:03

标签: java combinations combinatorics

现在我正在尝试编写一个带有数组和整数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");
        }
    }
}

3 个答案:

答案 0 :(得分:45)

这是一个充分研究的生成所有k子集的问题,或k-combinations,这可以很容易地完成,而不会递归。

我们的想法是让大小k的数组保持输入数组中元素的索引序列(从0n - 1的数字)按顺序递增。 ( Subset 然后可以通过从初始数组中获取这些索引来创建。)所以我们需要生成所有这样的索引序列。

第一个索引序列为[0, 1, 2, ... , k - 1],第二个步骤切换为[0, 1, 2,..., k],然后切换为[0, 1, 2, ... k + 1],依此类推。最后一个可能的序列是[n - k, n - k + 1, ..., n - 1]

在每个步骤中,算法会查找最接近最终项目的值,该项目可以递增,递增并将项目填充到该项目。

为了说明,请考虑n = 7k = 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;
}

答案 1 :(得分:5)

如果我正确理解您的问题,this文章似乎指出了您要做的事情。

引用文章:

  

方法1(修复元素和重复)

     

我们创建一个临时数组'data []',它可以存储所有输出   一。我们的想法是从data []中的第一个索引(index = 0)开始,一个   通过此索引处的一个修复元素并重复其余索引。让   输入数组为{1,2,3,4,5},r为3.我们首先在索引处修复1   数据[]中为0,然后重复剩余索引,然后我们在索引处修复2   0并重复。最后,我们修复3并重复剩余的索引。什么时候   data []中的元素数量等于r(a的大小)   组合),我们打印数据[]。

     

方法2(包括并排除每个元素)

     

与上述方法类似,我们创建一个临时数组数据[]。这个想法   这里类似于子集和问题。我们一个接一个地考虑每一个   输入数组的元素,以及两种情况的重复:

     
      
  1. 元素包含在当前组合中(我们将元素放在data []中并递增data []中的下一个可用索引)
  2.   
  3. 该元素在当前组合中被排除(我们不放置元素而不更改索引)
  4.         

    当data []中的元素数量等于r(a的大小)时   组合),我们打印出来。

答案 2 :(得分:2)

你可以通过迭代来做到这一点。

这是一个解决方案,计算我们应该创建多少个数组,然后使用数学来构建它们来计算源数组中的哪个项应该在哪个位置:

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