从数组(Java)中获取所有大小 n 组合的算法? [关闭]

Posted

技术标签:

【中文标题】从数组(Java)中获取所有大小 n 组合的算法? [关闭]【英文标题】:Algorithm to get all the combinations of size n from an array (Java)? [closed] 【发布时间】:2015-07-06 18:23:19 【问题描述】:

现在我正在尝试编写一个函数,它接受一个数组和一个整数 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");
        
    

【问题讨论】:

这个 SO 问题可能会对您有所帮助 [Finding powerset][1] [1]:***.com/questions/1670862/… 【参考方案1】:

这是一个经过充分研究的生成所有 k 子集或k-combinations 的问题,无需递归即可轻松完成。

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

第一个索引序列将是[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;

【讨论】:

非常感谢!这是一个不错的优雅解决方案,我更喜欢递归。我也可能会尝试制作一个递归解决方案,然后出于好奇比较运行时。我最终使用的代码是您的代码和 Roney 提供的链接的组合。两种解决方案中算法的关键部分似乎是某种形式的 s[i] 不客气。顺便说一下,我投票支持重新开放,我不知道他们为什么搁置你的问题。当需要遍历树或遍历对象包含其他对象的比较器时,我喜欢递归,等等。比较时请注意! 在大 if 语句中,在 for 循环中,else 可以被删除,因为 break。这会稍微清理一下代码。 @SoS 当然。欢迎指正。【参考方案2】:

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

引用文章:

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

我们创建了一个临时数组“data[]”,将所有输出逐一存储 一。这个想法是从 data[] 中的第一个索引(索引 = 0)开始,一个 通过一个修复该索引处的元素并为其余索引重复出现。让 输入数组是 1, 2, 3, 4, 5 并且 r 是 3。我们首先在索引处固定 1 data[] 中的 0,然后对剩余的索引重复,然后我们在索引处修复 2 0 并且复发。最后,我们修复 3 并对剩余的索引进行递归。什么时候 data[] 中的元素数等于 r(a 的大小 组合),我们打印 data[].

方法2(包含和排除每个元素)

和上面的方法一样,我们创建一个临时数组data[]。这个想法 这里类似于子集和问题。我们一一考虑每一个 输入数组的元素,并在两种情况下重复:

    元素包含在当前组合中(我们将元素放在 data[] 中,并在 data[] 中增加下一个可用索引) 当前组合中排除了该元素(我们不放置该元素,不更改索引)

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

【讨论】:

【参考方案3】:

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

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

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

【讨论】:

int numArrays = (int)Math.pow(arr.length, n);--你确定吗?给定长度的子序列计数由二项式系数定义,而不是幂。 仅当位置不相关时。 IE。如果[1, 2] 被认为等于[2, 1] @Raniz:我将如何修改它以不允许重复? 这列出了排列,而不是组合。

以上是关于从数组(Java)中获取所有大小 n 组合的算法? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

java题目好难啊?求大侠解答,谢谢、、 设计算法求解从集合1...n中选取k(k<=n)个元素的所有组合。

PHP算法从单个集合中生成特定大小的所有组合

java字母和数字排列组合后

从多个嵌套数组中获取所有组合

遍历整数数组所有组合的算法

从 n 返回 k 个元素的所有组合的算法