【问题标题】:Divide a group into subgroups of size k将一个组划分为大小为 k 的子组
【发布时间】:2013-03-27 13:05:58
【问题描述】:

我遇到了一个问题: 找出将大小为“n”的组划分为大小为“k”的子组的所有可能方法。 (这里 n%k =0)

例如,设集合为 {1,2,3,4,5,6} 以分成 3 个子组(k = 3,n = 6),可能的集合是

a) {1,2,3},{4,5,6}

b) {1,3,5},{2,4,6}

c) {1,3,6},{2,4,5}

d) {1,3,4},{2,5,6} 等......

我尝试做的是,首先从集合中找到所有大小 k 的组合。 然后遍历这些组合,找出哪些组合可以组合在一起,找到子组列表。

但我认为这种方法的时间复杂度非常糟糕。有没有更好的方法来解决这个问题?

【问题讨论】:

  • {1,2,3},{4,5,6} 和 {4,5,6},{1,2,3} - 您是否认为是相同的划分?
  • 请向我们展示您的代码。你的描述对我来说没有意义。

标签: java algorithm time-complexity combinatorics


【解决方案1】:

我会使用递归方法。我认为这个具有最佳运行时间,因为它准确地产生了所有需要的子集。

public static void solve(int[] a, int k, int i, List<List<Integer>> subsets) {
    if (i == a.length) {
        for (List<Integer> subset : subsets) {
            System.out.print(subset);               
        }
        System.out.println();
    } else {
        // loop over all subsets and try to put a[i] in
        for (int j = 0; j < subsets.size(); j++) {                 
            if (subsets.get(j).size() < k) {
                // subset j not full
                subsets.get(j).add(a[i]);
                solve(a, k, i+1, subsets); // do recursion
                subsets.get(j).remove((Integer)a[i]);

                if (subsets.get(j).size() == 0) {
                     // don't skip empty subsets, so you won't get duplicates
                     break;
                }                    
            }
        }
    }
}

用法:

public static void main(String[] args) {
    int[] a = {1, 2, 3, 4, 5, 6};
    int k = 3;

    List<List<Integer>> subsets = new ArrayList<List<Integer>>(a.length / k);
    for (int i = 0; i < a.length / k; i++)
        subsets.add(new ArrayList<Integer>(k));
    solve(a, k, 0, subsets);
}

打印:

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

【讨论】:

  • 您可以指定 ArrayLists 在其构造函数中使用的容量,这样就不会进行昂贵的扩展。
  • @G.Bach 聪明!我会补充的
【解决方案2】:

综合考虑。如果是n % k != 0,则不能这样做,因为您最终会得到一组少于k 元素的集合,因此请先检查是否是这种情况。

之后,您需要做的就是从 n-i*k 集合中递归生成所有 i in [0; n/k] 的 k 组合。可以很容易地找到生成给定集合的所有 k 组合的算法。这个想法是:有(n个选择k)可能的这样的集合,你可以为你的第一个集合选择;从剩余的n-k元素中,你可以选择((n-k)选择k)个集合);从剩余的n-2k元素中,你可以选择((n-2k)选择k)个集合等等。假设您的集合的顺序无关紧要,您有(n choose k) * ((n-k) choose k) * ... * ((n-(n-1)k) choose k) / ((n/k)!) 选择您的集合的可能性,这取决于 k 可以是原始集合所具有的元素数量的指数,所以如果您真的想生产每一个其中,您不会低于指数复杂度。

【讨论】:

    【解决方案3】:

    我找到了可能的子分组数的公式,以防万一有人觉得它有趣。 (这会被认为太离题吗?我发布的内容正确吗?)
    首先让m = n/k 成为子组的#。现在让第一个子群固定为该群的前 k 个元素,第二个子群是下一个 k,依此类推。如果我们考虑组的所有可能排列,这将为我们提供所有不同的子组。 n 个元素有n! 排列,但我们不关心排序,因此我们分解出 m 个子组中每个子组的k! 排列和子组本身的m! 排列。这给了我们: n!/(m!*(k!)^m).
    作为检查,如果 k = 1 或 k = n,这给了我们 1 个子分组。在原始示例中,n = 6,k = 3,m = 2,我们得到 10 个可能的子分组(Heuster 的代码找到了)。
    现在,如果您将此表达式与 G. Bach 给出的表达式进行比较并使用 (n choose k) = n!/(k!*(n-k)!),您将看到所有 (n-k)! 术语都取消了,并简化为上面的表达式。
    奖励:如果您使用斯特林对n! 的近似值,那么表达式会很好地简化,并且您会发现子分组的数量可以缩放为(m^n)/m!。

    【讨论】:

    • 在您的公式中 n!/(m!*k!)^m) 为什么使用 ^m?对不起,我数学不好。
    • 没问题。 ^m 是因为有 m 个不同的子组。每个都有k!其元素的顺序不同,所以你想除以k!每个子组一次。因此,在 n = 6 和 k = 3 的原始示例中,有 6 个!元素的总排列,但我们将其除以 3!因为第一个子组中的元素如何排序并不重要,除以 3!再次为第二个子组,然后除以 2!因为子组本身的顺序并不重要。这给了我们 10 个。
    猜你喜欢
    • 2018-07-28
    • 2016-01-09
    • 2020-04-20
    • 2017-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-02
    • 1970-01-01
    相关资源
    最近更新 更多