仅根据索引计算第N个多重组合(重复)

时间:2013-09-04 12:21:05

标签: combinations combinatorics factorial binomial-coefficients

如何根据它的索引计算第N个组合。 重复应该有(n + k-1)!/(k!(n-1)!)组合。

with n=2, k=5 you get:

0|{0,0,0,0,0}
1|{0,0,0,0,1}
2|{0,0,0,1,1}
3|{0,0,1,1,1}
4|{0,1,1,1,1}
5|{1,1,1,1,1}

所以black_magic_function(3)应该产生{0,0,1,1,1}。

这将进入GPU着色器,所以我希望每个工作组/线程都能够找出它们的排列子集,而不必全局存储序列。

with n=3, k=5 you get:
i=0, {0,0,0,0,0}
i=1, {0,0,0,0,1}
i=2, {0,0,0,0,2}
i=3, {0,0,0,1,1}
i=4, {0,0,0,1,2}
i=5, {0,0,0,2,2}
i=6, {0,0,1,1,1}
i=7, {0,0,1,1,2}
i=8, {0,0,1,2,2}
i=9, {0,0,2,2,2}
i=10, {0,1,1,1,1}
i=11, {0,1,1,1,2}
i=12, {0,1,1,2,2}
i=13, {0,1,2,2,2}
i=14, {0,2,2,2,2}
i=15, {1,1,1,1,1}
i=16, {1,1,1,1,2}
i=17, {1,1,1,2,2}
i=18, {1,1,2,2,2}
i=19, {1,2,2,2,2}
i=20, {2,2,2,2,2}

生成它的算法可以在http://www.martinbroadhurst.com/combinatorial-algorithms.html

看作MBnext_multicombination

更新

所以我想我会用(n+k-1)!/(k!(n-1)!)替换帕斯卡三角形中的二项式系数来看看它的外观。

(* Mathematica code to display pascal and other triangle *)
t1 = Table[Binomial[n, k], {n, 0, 8}, {k, 0, n}];
t2 = Table[(n + k - 1)!/(k! (n - 1)!), {n, 0, 8}, {k, 0, n}];
(*display*)
{Row[#, "\t"]} & /@ t1 // Grid
{Row[#, "\t"]} & /@ t2 // Grid

T1:

                1
              1   1
            1   2   1
          1   3   3   1
        1   4   6   4   1
      1   5   10  10  5   1
    1   6   15  20  15  6   1
  1   7   21  35  35  21  7   1
1   8   28  56  70  56  28  8   1

T2:

           Indeterminate
               1   1
             1   2   3
           1   3   6   10
         1   4   10  20   35
       1   5   15  35   70   126
     1   6   21  56  126   252  462
   1   7   28  84  210   462  924   1716
1   8   36  120  330   792  1716  3432  6435

与此帖子开头的n=3,k=5控制台输出进行比较:第三个对角线{3,6,10,15,21,28,36}给出每个翻转点{0,0,0,1,1} -> {0,0,1,1,1} -> {0,1,1,1,1}等的索引。对角线到它左边似乎显示了前一个块(diagonal[2][i] == diagonal[3][i] - diagonal[3][i-1]))中包含多少个值。如果您水平阅读金字塔的第5行,则会获得最大数量的组合,以便在(n+k-1)!/(k!(n-1)!) K=5中增加N的值。

可能有一种方法可以使用这些信息来确定任意索引的确切组合,而无需枚举整个集合,但我不确定我是否需要走那么远。最初的问题只是将完整的组合空间分解为相同的子集,可以在本地生成,并由GPU并行处理。因此,上面的三角形给出了每个块的起始索引,其中组合可以简单地导出,并且所有其连续元素都是递增枚举的。它还为我们提供了块大小以及我们拥有的总组合数。所以现在它变成了一个包装问题,即如何将不均匀大小的块装入X个线程中相等工作负载的组中。

2 个答案:

答案 0 :(得分:2)

参见以下示例: https://en.wikipedia.org/wiki/Combinatorial_number_system#Finding_the_k-combination_for_a_given_number

只需将二项式系数替换为(n+k-1)!/(k!(n-1)!)


假设n=3,k=5,假设我们想要计算第19个组合(id=19)。

 id=0, {0,0,0,0,0}
 id=1, {0,0,0,0,1}
 id=2, {0,0,0,0,2}
 ...
 id=16, {1,1,1,1,2}
 id=17, {1,1,1,2,2}
 id=18, {1,1,2,2,2}
 id=19, {1,2,2,2,2}
 id=20, {2,2,2,2,2}

我们正在寻找的结果是{1,2,2,2,2}

检查我们的'T2'三角形:n=3,k=5指向21,是第三个对角线的第五个数字(从上到下)(从左到右)。

            Indeterminate
                1   1
              1   2   3
            1   3   6   10
          1   4   10  20   35
        1   5   15  35   70   126
      1   6   21  56  126   252  462
    1   7   28  84  210   462  924   1716
 1   8   36  120  330   792  1716  3432  6435

我们需要找到不超过id=19值的此行中的最大数字(水平,不是对角线)。因此,从21向左移动,我们到达6(此操作由下面的largest函数执行)。由于6是此行中的第二个数字,因此它与n==2(或下面代码中的g[2,5] == 6)相对应。

现在我们在组合中找到了第5个数字,我们向上移动了金字塔中的一个楼层,所以k-1=4。我们还会从6中减去我们在下面遇到的idid=19-6=13。重复整个过程,我们发现5(再次n==2)是此行中小于13的最大数字。

下一步:13-5=8,此行中最大的4n==2又一次)。

下一步:8-4=4,此行中最大的3n==2再一次)。

下一步:4-3=1,此行中最大的1n==1

因此,在每个阶段收集指数,我们得到{1,2,2,2,2}


以下Mathematica代码完成了这项工作:

g[n_, k_] := (n + k - 1)!/(k! (n - 1)!)

largest[i_, nn_, kk_] := With[
    {x = g[nn, kk]}, 
    If[x > i, largest[i, nn-1, kk], {nn,x}]
]

id2combo[id_, n_, 0]  := {}
id2combo[id_, n_, k_] := Module[
    {val, offset}, 
    {val, offset} = largest[id, n, k];
    Append[id2combo[id-offset, n, k-1], val]
]

更新: MBnext_multicombination生成组合的顺序与id2combo不匹配,因此我认为它们不是词典。下面的函数以与id2combo相同的顺序生成它们,并匹配列表列表中mathematica的Sort[]函数的顺序。

void next_combo(unsigned int *ar, unsigned int n, unsigned int k)
{
    unsigned int i, lowest_i;

    for (i=lowest_i=0; i < k; ++i)
        lowest_i = (ar[i] < ar[lowest_i]) ? i : lowest_i;

    ++ar[lowest_i];

    i = (ar[lowest_i] >= n) 
        ? 0           // 0 -> all combinations have been exhausted, reset to first combination.
        : lowest_i+1; // _ -> base incremented. digits to the right of it are now zero.

    for (; i<k; ++i)
        ar[i] = 0;  
}

答案 1 :(得分:0)

我已就此问题做了一些初步分析。在我谈到我发现的低效解决方案之前,让我给你一篇关于如何将k索引(或组合)转换为与二项式系数相关的组合的排名或lexigraphic索引的论文链接:

http://tablizingthebinomialcoeff.wordpress.com/

我试图解决这个问题的方式也是一样的。我想出了以下代码,它为公式(n + k-1)中的每个k值使用一个循环!/ k!(n-1)!当k = 5时。如上所述,此代码将为n选择5的情况生成所有组合:

private static void GetCombos(int nElements)
{
   // This code shows how to generate all the k-indexes or combinations for any number of elements when k = 5.
   int k1, k2, k3, k4, k5;
   int n = nElements;
   int i = 0;
   for (k5 = 0; k5 < n; k5++)
   {
      for (k4 = k5; k4 < n; k4++)
      {
         for (k3 = k4; k3 < n; k3++)
         {
            for (k2 = k3; k2 < n; k2++)
            {
               for (k1 = k2; k1 < n; k1++)
               {
                  Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() + 
                     " " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " ");
                  i++;
               }
            }
         }
      }
   }
}

此方法的输出为:

i = 0, 0 0 0 0 0
i = 1, 0 0 0 0 1
i = 2, 0 0 0 0 2
i = 3, 0 0 0 1 1
i = 4, 0 0 0 1 2
i = 5, 0 0 0 2 2
i = 6, 0 0 1 1 1
i = 7, 0 0 1 1 2
i = 8, 0 0 1 2 2
i = 9, 0 0 2 2 2
i = 10, 0 1 1 1 1
i = 11, 0 1 1 1 2
i = 12, 0 1 1 2 2
i = 13, 0 1 2 2 2
i = 14, 0 2 2 2 2
i = 15, 1 1 1 1 1
i = 16, 1 1 1 1 2
i = 17, 1 1 1 2 2
i = 18, 1 1 2 2 2
i = 19, 1 2 2 2 2
i = 20, 2 2 2 2 2

这与您在编辑的答案中提供的值相同。我也尝试过4选5,看起来它也会产生正确的组合。

我是用C#编写的,但您应该可以在没有太多编辑的情况下将它与其他语言(如C / C ++,Java或Python)一起使用。

对于效率稍低的解决方案,一个想法是修改GetCombos以接受k作为输入。由于k被限制为6,因此可以对k进行测试。因此,为n选择k情形生成所有可能组合的代码将如下所示:

private static void GetCombos(int k, int nElements)
{
   // This code shows how to generate all the k-indexes or combinations for any n choose k, where k <= 6.
   //
   int k1, k2, k3, k4, k5, k6;
   int n = nElements;
   int i = 0;
   if (k == 6)
   {
      for (k6 = 0; k6 < n; k6++)
      {
         for (k5 = 0; k5 < n; k5++)
         {
            for (k4 = k5; k4 < n; k4++)
            {
               for (k3 = k4; k3 < n; k3++)
               {
                  for (k2 = k3; k2 < n; k2++)
                  {
                     for (k1 = k2; k1 < n; k1++)
                     {
                        Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() +
                           " " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " ");
                        i++;
                     }
                  }
               }
            }
         }
      }
   }
   else if (k == 5)
   {
      for (k5 = 0; k5 < n; k5++)
      {
         for (k4 = k5; k4 < n; k4++)
         {
            for (k3 = k4; k3 < n; k3++)
            {
               for (k2 = k3; k2 < n; k2++)
               {
                  for (k1 = k2; k1 < n; k1++)
                  {
                     Console.WriteLine("i = " + i.ToString() + ", " + k5.ToString() + " " + k4.ToString() +
                        " " + k3.ToString() + " " + k2.ToString() + " " + k1.ToString() + " ");
                     i++;
                  }
               }
            }
         }
      }
   }
   else if (k == 4)
   {
      // One less loop than k = 5.
   }
   else if (k == 3)
   {
      // One less loop than k = 4.
   }
   else if (k == 2)
   {
      // One less loop than k = 3.
   }
   else
   {
      // k = 1 - error?
   }
}

因此,我们现在有一种方法可以生成所有感兴趣的组合。但是,问题是从该组合位于该集合中的词典顺序或等级获得特定组合。因此,这可以通过简单计数完成,然后在达到指定值时返回正确的组合。因此,为了适应这一点,需要将一个表示排名的额外参数添加到方法中。因此,执行此操作的新功能如下所示:

private static int[] GetComboOfRank(int k, int nElements, int Rank)
{
   // Gets the combination for the rank using the formula (n+k-1)!/k!(n-1)! where k <= 6.
   int k1, k2, k3, k4, k5, k6;
   int n = nElements;
   int i = 0;
   int[] ReturnArray = new int[k];
   if (k == 6)
   {
      for (k6 = 0; k6 < n; k6++)
      {
         for (k5 = 0; k5 < n; k5++)
         {
            for (k4 = k5; k4 < n; k4++)
            {
               for (k3 = k4; k3 < n; k3++)
               {
                  for (k2 = k3; k2 < n; k2++)
                  {
                     for (k1 = k2; k1 < n; k1++)
                     {
                        if (i == Rank)
                        {
                           ReturnArray[0] = k1;
                           ReturnArray[1] = k2;
                           ReturnArray[2] = k3;
                           ReturnArray[3] = k4;
                           ReturnArray[4] = k5;
                           ReturnArray[5] = k6;
                           return ReturnArray;
                        }
                        i++;
                     }
                  }
               }
            }
         }
      }
   }
   else if (k == 5)
   {
      for (k5 = 0; k5 < n; k5++)
      {
         for (k4 = k5; k4 < n; k4++)
         {
            for (k3 = k4; k3 < n; k3++)
            {
               for (k2 = k3; k2 < n; k2++)
               {
                  for (k1 = k2; k1 < n; k1++)
                  {
                     if (i == Rank)
                     {
                        ReturnArray[0] = k1;
                        ReturnArray[1] = k2;
                        ReturnArray[2] = k3;
                        ReturnArray[3] = k4;
                        ReturnArray[4] = k5;
                        return ReturnArray;
                     }
                     i++;
                  }
               }
            }
         }
      }
   }
   else if (k == 4)
   {
      // Same code as in the other cases, but with one less loop than k = 5.
   }
   else if (k == 3)
   {
      // Same code as in the other cases, but with one less loop than k = 4.
   }
   else if (k == 2)
   {
      // Same code as in the other cases, but with one less loop than k = 3.
   }
   else
   {
      // k = 1 - error?
   }
   // Should not ever get here.  If we do - it is some sort of error.
   throw ("GetComboOfRank - did not find rank");
}

ReturnArray返回与排名关联的组合。所以,这段代码应该适合你。但是,如果进行表查找,它将比可以实现的速度慢得多。 300选择6的问题是:

300 choose 6 = 305! / (6!(299!) = 305*304*303*302*301*300 / 6! = 1,064,089,721,800

这可能是存储在内存中的太多数据。所以,如果你可以通过预处理得到n,那么你将会看到总数:

20 choose 6 = 25! / (6!(19!)) = 25*24*23*22*21*20 / 6! = 177,100
20 choose 5 = 24! / (5!(19!)) = 24*23*22*21,20 / 5!    =  42,504
20 choose 4 = 23! / (4!(19!)) = 23*22*21*20 / 4!       =   8,855
20 choose 3 = 22! / (3!(19!)) = 22*21*20 / 3!          =   1,540
20 choose 2 = 21! / (2!(19!)) = 22*21 / 2!             =     231
                                                         =======
                                                         230,230

如果组合的每个值使用一个字节,那么用于存储表的字节总数(通过锯齿状数组或可能是5个单独的表)可以计算为:

177,100 * 6 = 1,062,600
 42,504 * 5 =   212,520
  8,855 * 4 =    35,420
  1,540 * 3 =     4,620
    231 * 2 =       462
              =========
              1,315,622

这取决于目标机器和可用内存量,但是当今许多机器有可用内存时,1,315,622字节并不是那么多内存。