2

我如何仅根据它的索引计算第 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}

生成它的算法可以MBnext_multicombinationhttp://www.martinbroadhurst.com/combinatorial-algorithms.html看到

更新:

所以我想我会用帕斯卡三角形中的二项式系数来代替(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 in (n+k-1)!/(k!(n-1)!)where的值K=5

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

4

2 回答 2

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,是第三条对角线(从左到右)的第 5 个数字(从上到下)。

            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我们在下面遇到的id,所以id=19-6=13。重复整个过程,我们发现5n==2再次)是小于13这一行的最大数。

下一个:13-5=8,最大的是4在这一行(n==2又一次)。

Next: 8-4=4, 最大的3在这一行(n==2再一次)。

Next: 4-3=1, 最大1在这一行 ( n==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,所以我认为它们不是字典顺序的。下面的函数以与列表列表中mathematica 函数的顺序相同id2combo的顺序生成它们并匹配它们。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;  
}
于 2013-09-11T01:03:59.443 回答
0

我对这个问题做了一些初步的分析。在我谈论我发现的低效解决方案之前,让我给你一个链接,指向我写的一篇论文,该论文是关于如何将 k 索引(或组合)转换为与二项式系数相关的组合的等级或字典索引:

http://tablizingthebinomialcoeff.wordpress.com/

我以同样的方式开始尝试解决这个问题。我想出了以下代码,它对公式 (n+k-1)!/k!(n-1)! 中的每个 k 值使用一个循环!当 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,那么您将看到总共:

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 字节并不是那么多内存。

于 2013-09-09T04:35:28.857 回答