【问题标题】:Counting huge permutations - counting elements and getting nth element计算巨大的排列 - 计算元素并获得第 n 个元素
【发布时间】:2019-01-15 07:30:24
【问题描述】:

我正在使用这个库进行组合: https://github.com/eoincampbell/combinatorics/

我需要的是找到相当大的集合(最多约 30 个元素)的第 n 个排列和计数元素,但我什至在开始之前就停止了,请查看以下代码:

int[] testSet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21};

var permutation = new Permutations<int>(testSet);
var test = permutation.Count;

直到 20 个元素的大集合,一切都很好,一旦我添加了第 21 个,排列就会停止正常工作,例如。 这是 permutation.Count 返回的内容:

-4249290049419214848

这远非正确的数字。

我假设这一切都归结为我使用了多么庞大的数字——库使用的整数/长整数溢出。这就是为什么,我要征求意见 - 有图书馆吗?方法?还是一种相当快速的实现方式来让组合学在大整数上工作?

谢谢!

【问题讨论】:

  • 您是否尝试将int 替换为System.Numeric.BigInteger?我认为这是 .net 知道的最大整数表示。
  • 您只需要特定的组合和数字吗?我假设您可以计算两者,而无需创建所有可能的排列的完整集。即使您切换到基于 BigInteger 的实现,这也会很快溢出您的内存。
  • 是的,我目前正在编写自己的基于 BigInteger 的解决方案,希望可能有一些库可以加快速度,但是哦,好吧 :) 我确实需要随机组合和计数。
  • 在您的示例中 permutation.Count 等于 21!超过一个长整数的最大值。要生成随机排列,您不需要知道第 n 个。您可以在您的号码列表中模拟无折扣抽奖,使用显示已抽奖号码的表格。

标签: c# permutation combinatorics large-data


【解决方案1】:

获取可能的排列数。

排列的数量由nPrn over r

定义
           n!     
P(n,r) = -------- 
         (n - r)!

地点:

  • n = 对象数
  • r = 结果集的大小

在您的示例中,您希望获取给定列表的所有排列。在这种情况下n = r

public static BigInteger CalcCount(BigInteger n, BigInteger r) 
{
    BigInteger result = n.Factorial() / (n - r).Factorial();
    return result;
}

public static class BigIntExtensions 
{
    public static BigInteger Factorial(this BigInteger integer) 
    {
        if(integer < 1) return new BigInteger(1);

        BigInteger result = integer;
        for (BigInteger i = 1; i < integer; i++)
        {
            result = result * i;
        }

        return result;
    }
}

获取第 n 个排列

这取决于您如何创建/枚举排列。通常要生成任何排列,您不需要知道所有先前的排列。换句话说,创建一个排列可以是一个纯函数,允许您直接创建nTh 排列,而无需创建所有可能的排列。

不过,这取决于所使用的算法。但是仅在需要时创建排列可能会快得多(与预先创建所有可能的排列相比 -> 性能和非常重的内存)。

这里是关于如何创建排列而不需要计算以前的排列的精彩讨论:https://stackoverflow.com/a/24257996/1681616

【讨论】:

  • 谢谢!提供的链接看起来很有趣,会看看:)
【解决方案2】:

评论太长了,但想跟进@Iqon 的上述解决方案。下面是一个检索 nth lexicographical 排列的算法:

public static int[] nthPerm(BigInteger myIndex, int n, int r, BigInteger total)
{
    int j = 0, n1 = n;
    BigInteger temp, index1 = myIndex;

    temp = total ;
    List<int> indexList = new List<int>();

    for (int k = 0; k < n; k++) {
           indexList.Add(k);
    }

    int[] res = new int[r];

    for (int k = 0; k < r; k++, n1--) {
        temp /= n1;
        j = (int) (index1 / temp);
        res[k] = indexList[j];
        index1 -= (temp *  j);
        indexList.RemoveAt(j);
    }

    return res;
}

这是一个测试用例以及使用@Iqon提供的代码调用nthPerm的结果。

public static void Main()
{
    int[] testSet = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21};
    BigInteger numPerms, n, r;
    n = testSet.Length;
    r = testSet.Length;
    numPerms = CalcCount(n, r);
    Console.WriteLine(numPerms);

    BigInteger testIndex = new BigInteger(1234567890987654321);
    int[] myNthIndex = nthPerm(testIndex, (int) n, (int) r, numPerms);
    int[] myNthPerm = new int[(int) r];

    for (int i = 0; i < (int) r; i++) {
           myNthPerm[i] = testSet[myNthIndex[i]];
    }

    Console.WriteLine(string.Join(",", myNthPerm));
}

// Returns 1,12,4,18,20,19,7,5,16,11,6,8,21,15,13,2,14,9,10,17,3

这里是ideone 的链接以及工作代码。

【讨论】:

  • 我应该注意到,如果您设置testIndex = 0,您将获得第一个排列(即myNthPerm 将与testSet 相同)。如果你设置testIndex = numPerms - 1myNthPerm 将是testSet 的倒序。
【解决方案3】:

你可以使用JNumberTools

   List<String> list = new ArrayList<>();
   //add elements to list;
    
   JNumberTools.permutationsOf(list)
       .uniqueNth(1000_000_000) //next 1 billionth permutation
       .forEach(System.out::println);

此 API 将直接按字典顺序生成下一个第 n 个排列。因此,您甚至可以生成 100 个项目的下十亿个排列。

用于生成给定大小的下一个第 n 个排列:

JNumberTools 的 maven 依赖是:

<dependency>
    <groupId>io.github.deepeshpatel</groupId>
    <artifactId>jnumbertools</artifactId>
    <version>1.0.0</version>
</dependency>

【讨论】:

    猜你喜欢
    • 2020-11-05
    • 2016-05-22
    • 1970-01-01
    • 1970-01-01
    • 2012-03-26
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多