【问题标题】:Print all partitions into disjoint combinations of fixed size将所有分区打印成固定大小的不相交组合
【发布时间】:2022-01-09 23:58:04
【问题描述】:

我有一个从 1 到 n 的数字数组,我需要找到所有可能的分区为 3 个数字的不相交组合。

也就是说,对于n = 9,情况如下:

数组:1、2、3、4、5、6、7、8、9;

3的可能组合:123、124 ... 245、246 ... 478、479等;

可能的分区为 3 个不相交的组合:123 456 789、123 457 689 ... 123 468 579 ... 127 458 369 等。

我找到了一种算法,可以从一组数字中找到 3 个数字的组合,这里是:https://www.geeksforgeeks.org/print-all-possible-combinations-of-r-elements-in-a-given-array-of-size-n /(其中甚至有 2 个,但我使用了第一个)。现在的问题是如何找到组合本身的组合,这已经造成了困难:在我看来,为此我需要再次处理递归,但是如何以及在何处使用它,我并不完全理解(也许重点是另一个)。此外,我还看到了一种非递归算法,它可以从给定的数字https://rosettacode.org/wiki/Combinations#C.23 中找到所有组合,但对它无能为力(我将我的工作附在其中)。你能帮帮我吗?

public static IEnumerable<int[]> Combinations(int[] a, int n, int m)
        {
            int[] result = new int[m];
            Stack<int> stack = new Stack<int>();
            stack.Push(0);
            while (stack.Count > 0)
            {
                int index = stack.Count - 1;
                int value = stack.Pop();
                while (value < n)
                {
                    result[index++] = ++value;
                    stack.Push(value);
                    if (index == m)
                    {
                        for (int i = 0; i < 3; i++)
                        {
                            a = a.Where(val => val != result[i]).ToArray();
                        }
                        return Combinations (a, n-3, m);
                        break;
                    }
                }
            }
        }

【问题讨论】:

标签: c# arrays algorithm recursion combinations


【解决方案1】:

假设n是3的倍数,有一个简单直观的递归算法。 (有效地编写它是一个挑战:-))。

在伪代码中,将 3 推广到 k:

# A must have a multiple of k elements
# I write  V \ C to mean "V without the values in C". Since producing
# copies is expensive, you should find a more efficient way of doing
# this.
Partition(A, k):
    If A has k elements, produce the partition consisting only of A
    Otherwise:
        Let m be the smallest element of A. 
        For each combination C of k-1 elements from A \ [m]:
            Add m to C
            For each partition P generated by Partition(A \ C, k):
                produce P with the addition of C

当然,这取决于您是否可以访问可以枚举列表的 k 组合的算法。 (更好的是一个函数,它在开始时使用不同的 k 组合生成列表的连续洗牌,同时保持列表的顺序。遗憾的是,很少有标准库提供这一点。)

还有另一种递归算法,可以通过维护显式堆栈轻松地将其制成迭代算法。它可能不是那么直观,尽管一旦你看到它,它的工作原理就很明显了,但它更容易有效地实现。它要求我们保持不变量,即分区中的每个集合都按升序存储,并且集合本身按其第一个元素按升序排序。 (顺序本身无关紧要,只要将元素保存在顺序不变的数据结构中,假设元素的原始顺序就是所需的排序是完全合理的。)

一旦您建立了该规则,您就可以首先将所有分区的集合设为空,然后使用遵守以下简单约束的每个可能位置按顺序放置每个连续元素:

  1. 一旦集合包含正确数量的元素,就不能再向其中添加任何元素。
  2. 每个元素都放置在集合的末尾(因为所有已放置的元素都较小,而所有尚未放置的元素都较大);
  3. 如果元素是分区中的第一个空集,则只能将其添加到空集(以保证集本身将被排序)。

为避免不断复制分区中的集合,您可以通过使用 k 行和 n 的固定大小二维数组来实现这一点k 列,其中每一行代表分区中的一组;然后有必要保留另一个 nk 个整数数组作为每个集合的当前长度。


第一种算法的一个优点是它可以相当明显地显示有多少可能的分区,因为内循环生成的分区数与外循环中选择的组合无关。因此,如果我们写 P(n, k) 表示 k-partitions 的数量n 个对象,我们可以看到

P(n, k) = C(n-1 , k-1) × P(n-k, k) 为n>0   (其中 C(n, k) 是binomial coefficient

这只是二项式系数的乘积:

P(n, k) = C(n-1 , k-1) × C(n-k-1, k −1) × C(n-2k-1, k-1) × … × C(k-1, k-1)

因为 C(n, k) 是 n! ⁄ k!(nk)!,可以简化为n! ⁄ (d! × k!d) 其中 d 是在每个分区中设置,即 d = nk。这个数字显然比n小很多!但它仍然增长得非常快,使得对配分函数的大论据变得不切实际。对于 k=3,前几个计数是:

P( 3, 3) = 1
P( 6, 3) = 10
P( 9, 3) = 280
P(12, 3) = 15,400
P(15, 3) = 1,401,400
P(18, 3) = 190,590,400
P(21, 3) = 36,212,176,000

因此,通常建议一次生成和使用一个可能的值,而不是尝试将它们全部存储到一个庞大的向量中,这会占用大量内存。

【讨论】:

  • 现在我记起来了,我把这个算法作为this question from 2017 的答案,这可能是重复的。另请参阅here 中描述的此类枚举问题的一般方法。
猜你喜欢
  • 1970-01-01
  • 2010-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-09
  • 1970-01-01
  • 1970-01-01
  • 2012-03-17
相关资源
最近更新 更多