计算巨大的排列-计算元素并获取第n个元素

时间:2018-08-08 06:21:17

标签: c# permutation combinatorics large-data

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

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

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 / longs。这就是为什么我要征求意见-是否有图书馆?方法?还是一种相当快速的实现方法来在bigintegers上使用组合函数?

谢谢!

3 个答案:

答案 0 :(得分:2)

获取可能的排列次数。

排列数量由 nPr n相对于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;
    }
}

获取nTh排列

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

但是,这取决于所使用的算法。但是仅在需要时才创建置换可能会快很多(与预先创建所有可能的置换->性能和非常大的内存相反)。

这里是关于如何创建置换而无需计算先前的置换的精彩讨论:https://stackoverflow.com/a/24257996/1681616

答案 1 :(得分:1)

这个评论太长了,但想跟进@Iqon的上述解决方案。以下是检索 n th 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的链接。

答案 2 :(得分:1)

您可以使用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 将直接按字典顺序生成下一个排列。因此,您甚至可以生成 100 个项目的下一个十亿个排列。

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

JNumberTools 的 maven 依赖是:

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