生成固定长度整数分区的所有唯一排列的算法?

时间:2010-11-10 16:27:41

标签: algorithm integer data-partitioning

我正在寻找一种算法,该算法生成整数的固定长度分区的所有排列。订单无关紧要。

例如,对于n = 4且长度L = 3:

[(0, 2, 2), (2, 0, 2), (2, 2, 0),
 (2, 1, 1), (1, 2, 1), (1, 1, 2),
 (0, 1, 3), (0, 3, 1), (3, 0, 1), (3, 1, 0), (1, 3, 0), (1, 0, 3),
 (0, 0, 4), (4, 0, 0), (0, 4, 0)]

对于长度小于L的分区,我对整数分区+排列进行了喋喋不休;但这太慢了,因为我多次得到同一个分区(因为[0, 0, 1]可能是[0, 0, 1]的排列; - )

任何帮助表示赞赏,不,这不是作业 - 个人兴趣: - )

6 个答案:

答案 0 :(得分:3)

鉴于您对此感兴趣,您可能会对一个明确的答案感兴趣!它可以在Knuth的计算机编程艺术subvolume 4A)的“7.2.1.2 - 生成所有排列”中找到。

此外,可以找到3个具体算法here

答案 1 :(得分:2)

好。首先,忘记排列,然后生成长度为L的分区(由@Svein Bringsli建议)。请注意,对于每个分区,您可以对元素进行排序,例如>。现在只需“计数”,维持您的订购。对于n = 4,k = 3:

(4, 0, 0)
(3, 1, 0)
(2, 2, 0)
(2, 1, 1)

那么,如何实现呢?它看起来像:从位置i减去1并将其添加到下一个位置时保持我们的顺序,从位置i减去1,将1添加到位置i + 1,然后移动到下一个位置。如果我们处于最后位置,请退一步。

这是一个小蟒蛇,它就是这样做的:

def partition_helper(l, i, result):
    if i == len(l) - 1:
        return 
    while l[i] - 1 >= l[i + 1] + 1:
        l[i]        -= 1
        l[i + 1]    += 1
        result.append(list(l))
        partition_helper(l, i + 1, result)

def partition(n, k):
    l = [n] + [0] * (k - 1)
    result = [list(l)]
    partition_helper(l, 0, result)
    return result

现在您有了一个列表列表(实际上是一个多集的列表),并且生成列表中每个多集的所有排列为您提供了解决方案。我不打算这样,有一个递归算法,基本上说,对于每个位置,选择多集中的每个唯一元素,并附加多集的排列,这是因为从多集中删除了该元素。

答案 2 :(得分:2)

如@pbarranis所述,当 n 等于 k 时,@ rlibby的代码不包括所有列表。下面是包含所有列表的Python代码。此代码是非递归的,在内存使用方面可能更有效。

def successor(n, l):
  idx = [j for j in range(len(l)) if l[j] < l[0]-1]
  if not idx:
    return False

  i = idx[0]
  l[1:i+1] = [l[i]+1]*(len(l[1:i+1]))
  l[0] = n - sum(l[1:])
  return True

def partitions(n, k):
  l = [0]*k
  l[0] = n
  results = []
  results.append(list(l))
  while successor(n, l):
    results.append(list(l))
  return results

列表以连字顺序创建(算法和更多描述here)。

答案 3 :(得分:0)

就像我上面提到的,我无法获得@ rlibby的代码来满足我的需求,我需要代码,其中n = l,所以只需要你的一部分。这是我在C#下面的代码。我知道这不是上述问题的完美答案,但我相信你只需要修改第一种方法,使其适用于不同的l值;基本上添加相同的代码@rlibby,使长度为l的数组而不是长度为n。

public static List<int[]> GetPartitionPermutations(int n)
{
    int[] l = new int[n];

    var results = new List<int[]>();

    GeneratePermutations(l, n, n, 0, results);
    return results;
}

private static void GeneratePermutations(int[] l, int n, int nMax, int i, List<int[]> results)
{
    if (n == 0)
    {
        for (; i < l.Length; ++i)
        {
            l[i] = 0;
        }
        results.Add(l.ToArray());
        return;
    }

    for (int cnt = Math.Min(nMax, n); cnt > 0; --cnt)
    {
        l[i] = cnt;
        GeneratePermutations(l, (n - cnt), cnt, i + 1, results);
    }
}

答案 4 :(得分:0)

很多搜索导致了这个问题。这是一个包含排列的答案:

#!/usr/bin/python
from itertools import combinations_with_replacement as cr
def all_partitions(n, k):
    """
    Return all possible combinations that add up to n
    i.e. divide n objects in k DISTINCT boxes in all possible ways
    """
    all_part = []
    for div in cr(range(n+1), k-1):
        counts = [div[0]]
        for i in range(1, k-1):
            counts.append(div[i] - div[i-1])
        counts.append(n-div[-1])
        all_part.append(counts)
    return all_part

例如,OP提出的all_part(4,3)给出了:

[[0, 0, 4],
 [0, 1, 3],
 [0, 2, 2],
 [0, 3, 1],
 [0, 4, 0],
 [1, 0, 3],
 [1, 1, 2],
 [1, 2, 1],
 [1, 3, 0],
 [2, 0, 2],
 [2, 1, 1],
 [2, 2, 0],
 [3, 0, 1],
 [3, 1, 0],
 [4, 0, 0]]

答案 5 :(得分:0)

我发现使用递归函数对于较大的长度和整数并不好,因为它会占用太多RAM,并且使用生成器/可恢复函数(产生&#39;值)太慢了需要一个大型库来实现跨平台。

所以这里是C ++中的非递归解决方案,它以排序顺序生成分区(这也是排列的理想选择)。我发现这比看似聪明和简洁的递归解决方案快了10倍,我尝试了4或更大的分区长度,但对于1-3的长度,性能不一定更好(我不喜欢&#39;关心短长度,因为他们用这两种方法都很快。

// Inputs
unsigned short myInt = 10;
unsigned short len = 3;

// Partition variables.
vector<unsigned short> partition(len);
unsigned short last = len - 1;
unsigned short penult = last - 1;
short cur = penult; // Can dip into negative value when len is 1 or 2.  Can be changed to unsigned if len is always >=3.
unsigned short sum = 0;

// Prefill partition with 0.
fill(partition.begin(), partition.end(), 0);

do {
    // Calculate remainder.
    partition[last] = max(0, myInt - sum); // Would only need "myInt - sum" if partition vector contains signed ints.

    /*
     *
     * DO SOMETHING WITH "partition" HERE.
     *
     */

    if (partition[cur + 1] <= partition[cur] + 1) {
        do {
            cur--;
        } while (
            cur > 0 && 
            accumulate(partition.cbegin(), partition.cbegin() + cur, 0) + (len - cur) * (partition[cur] + 1) > myInt
        );

        // Escape if seeked behind too far.
        // I think this if-statement is only useful when len is 1 or 2, can probably be removed if len is always >=3. 
        if (cur < 0) {
            break;
        }

        // Increment the new cur position.
        sum++;
        partition[cur]++;

        // The value in each position must be at least as large as the 
        // value in the previous position.            
        for (unsigned short i = cur + 1; i < last; ++i) {
            sum = sum - partition[i] + partition[i - 1];
            partition[i] = partition[i - 1];
        }

        // Reset cur for next time.
        cur = penult;
    }
    else {
        sum++;
        partition[penult]++;
    }

} while (myInt - sum >= partition[penult]);

我写的做了什么&#34;分区&#34; HERE。是您实际使用该值的地方。 (在最后一次迭代中,代码将继续执行循环的其余部分,但我发现这比不断检查退出条件更好 - 它针对更大的操作进行了优化)

0,0,10
0,1,9
0,2,8
0,3,7
0,4,6
0,5,5
1,1,8
1,2,7
1,3,6
1,4,5
2,2,6
2,3,5
2,4,4
3,3,4

哦,我已经使用了&#34; unsigned short&#34;因为我知道我的长度和整数不会超过某些限制,如果它不适合你就改变它:)检查评论;一个变量(cur)必须被签名以处理1或2的长度,并且有一个相应的if语句,并且我在评论中还注意到,如果你的分区向量已经签名有一些可以简化的行。

要获得所有组合,在C ++中我会使用这种简单的排列策略,幸好不会产生任何重复:

do {
    // Your code goes here.
} while (next_permutation(partition.begin(), partition.end()));

将其嵌入 DO SOMETHING WITH&#34;分区&#34;在这里现场,你很高兴。

查找组合的替代方法(基于此处的Java代码https://www.nayuki.io/page/next-lexicographical-permutation-algorithm)如下所示。我发现这比next_permutation()更好。

// Process lexicographic permutations of partition (compositions).
composition = partition;
do {

    // Your code goes here.

    // Find longest non-increasing suffix
    i = last;
    while (i > 0 && composition[i - 1] >= composition[i]) {
        --i;
    }
    // Now i is the head index of the suffix

    // Are we at the last permutation already?
    if (i <= 0) {
        break;
    }

    // Let array[i - 1] be the pivot
    // Find rightmost element that exceeds the pivot
    j = last;
    while (composition[j] <= composition[i - 1])
        --j;
    // Now the value array[j] will become the new pivot
    // Assertion: j >= i

    // Swap the pivot with j
    temp = composition[i - 1];
    composition[i - 1] = composition[j];
    composition[j] = temp;

    // Reverse the suffix
    j = last;
    while (i < j) {
        temp = composition[i];
        composition[i] = composition[j];
        composition[j] = temp;
        ++i;
        --j;
    }
} while (true);

您会注意到那里有一些未声明的变量,只需在代码中的所有do循环之前在代码中声明它们:ijpos和{{1 (无符号短裤)和temp(与composition相同的类型和长度)。您可以重复使用partition的声明,以便在分区代码段中的for循环中使用它。还要注意使用i之类的变量,它假定这段代码嵌套在前面给出的分区代码中。

再次&#34;你的代码在这里&#34;是你为了自己的目的而消费构图的地方。

以下是我的标题。

last

尽管使用这些方法大大提高了速度,但对于任何相当大的整数和分区长度,这仍然会让你对你的CPU感到生气:)