如何根据它的索引计算第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个线程中相等工作负载的组中。
答案 0 :(得分:2)
只需将二项式系数替换为(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
中减去我们在下面遇到的id
,id=19-6=13
。重复整个过程,我们发现5
(再次n==2
)是此行中小于13
的最大数字。
下一步:13-5=8
,此行中最大的4
(n==2
又一次)。
下一步:8-4=4
,此行中最大的3
(n==2
再一次)。
下一步: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
不匹配,因此我认为它们不是词典。下面的函数以与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字节并不是那么多内存。