我想在一组组合中为每个组合预先计算一些值。例如,当从0到12中选择3个数字时,我将为每个数字计算一些值:
>>> for n in choose(range(13), 3):
print n, foo(n)
(0, 1, 2) 78
(0, 1, 3) 4
(0, 1, 4) 64
(0, 1, 5) 33
(0, 1, 6) 20
(0, 1, 7) 64
(0, 1, 8) 13
(0, 1, 9) 24
(0, 1, 10) 85
(0, 1, 11) 13
etc...
我想将这些值存储在一个数组中,以便给定组合,我可以计算它并获取值。例如:
>>> a = [78, 4, 64, 33]
>>> a[magic((0,1,2))]
78
magic
会是什么?
最初我认为只是将它存储为尺寸为13 x 13 x 13的3维矩阵,因此我可以轻松地将其编入索引。虽然这对于13选择3来说是好的,但对于像13选择7这样的东西来说会有太大的开销。
我不想使用dict,因为最终这个代码将在C中,无论如何数组都会更有效。
更新:我也有类似的问题,但是使用重复的组合,所以任何关于如何获得这些排名的答案都会非常感激=)。
更新:为了说清楚,我正在努力节省空间。这些组合中的每一个实际上都指向占用大量空间的东西,比方说2千字节。如果我使用13x13x13阵列,那将是4兆字节,其中我只需要572千字节使用(13选3)点。
答案 0 :(得分:10)
这是一个概念性答案和基于lex排序如何工作的代码。 (所以我猜我的答案就像“白痴”的答案,除了我认为他的细节太少而且他的链接太多了。)我为你写了一个函数unchoose(n,S)
,假设S是一个range(n)
的有序列表子集。想法:S包含0或不包含0。如果是,则删除0并计算剩余子集的索引。如果没有,则它出现在包含0的binomial(n-1,k-1)
子集之后。
def binomial(n,k):
if n < 0 or k < 0 or k > n: return 0
b = 1
for i in xrange(k): b = b*(n-i)/(i+1)
return b
def unchoose(n,S):
k = len(S)
if k == 0 or k == n: return 0
j = S[0]
if k == 1: return j
S = [x-1 for x in S]
if not j: return unchoose(n-1,S[1:])
return binomial(n-1,k-1)+unchoose(n-1,S)
def choose(X,k):
n = len(X)
if k < 0 or k > n: return []
if not k: return [[]]
if k == n: return [X]
return [X[:1] + S for S in choose(X[1:],k-1)] + choose(X[1:],k)
(n,k) = (13,3)
for S in choose(range(n),k): print unchoose(n,S),S
现在,您可以缓存或散列两个函数的值,二项式和非二次函数。而这样做的好处在于,您可以在预先计算所有内容和预先计算任何内容之间做出妥协。例如,您只能预先计算len(S) <= 3
。
您还可以优化unchoose,以便在S[0] > 0
时使用循环添加二项式系数,而不是递减和使用尾递归。
答案 1 :(得分:5)
您可以尝试使用组合的词典索引。也许这个页面会有所帮助:http://saliu.com/bbs/messages/348.html
此MSDN页面包含更多详细信息:Generating the mth Lexicographical Element of a Mathematical Combination。
更具体一点:
当被视为元组时,您可以按字典顺序排列组合。
所以(0,1,2)&lt; (0,1,3)&lt; (0,1,4)等。
假设您有0到n-1的数字,并从中选择了k。
现在,如果第一个元素为零,你知道它是第一个n-1中的一个选择k-1。
如果第一个元素是1,那么它是下一个n-2中的一个选择k-1。
通过这种方式,您可以递归计算给定组合在词典排序中的确切位置,并使用它将其映射到您的数字。
这也是相反的,MSDN页面解释了如何做到这一点。
答案 2 :(得分:2)
我建议使用专门的哈希表。组合的散列应该是值的散列或散列。值的散列基本上是随机位模式。
您可以对表进行编码以应对冲突,但是导出最小完美哈希方案应该相当容易 - 没有两个三项组合给出相同的哈希值,以及哈希大小和表格 - 大小保持在最低限度。
这基本上是Zobrist hashing - 将“移动”视为添加或删除组合中的一项。
修改强>
使用哈希表的原因是查找性能O(n)其中n是组合中的项目数(假设没有冲突)。计算组合中的词典索引要慢得多,IIRC。
缺点显然是生成表格的前期工作。
答案 3 :(得分:1)
使用哈希表存储结果。一个像样的散列函数可能是这样的:
h(x) = (x1*p^(k - 1) + x2*p^(k - 2) + ... + xk*p^0) % pp
x1 ... xk
组合中的数字(例如(0, 1, 2)
有x1 = 0, x2 = 1, x3 = 2
),p
和pp
是素数。
所以你要存储Hash[h(0, 1, 2)] = 78
,然后你会以同样的方式检索它。
注意:哈希表只是一个大小为pp
的数组,而不是一个字典。
答案 4 :(得分:1)
目前,我已达成妥协:我有一个13x13x13数组,它只映射到组合的索引,占用13x13x13x2字节= 4千字节(使用短整数),再加上正常大小(13选3)* 2千字节= 572千字节,总共576千字节。比4兆更好,也比排名计算快!
我之所以这样做,部分原因是我似乎无法让Moron回答工作。这也是更具可扩展性的 - 我有一个需要重复组合的情况,我还没有找到计算那些等级的方法。
答案 5 :(得分:1)
您想要的是combinadics。以下是我在Python中实现的这个概念:
def nthresh(k, idx):
"""Finds the largest value m such that C(m, k) <= idx."""
mk = k
while ncombs(mk, k) <= idx:
mk += 1
return mk - 1
def idx_to_set(k, idx):
ret = []
for i in range(k, 0, -1):
element = nthresh(i, idx)
ret.append(element)
idx -= ncombs(element, i)
return ret
def set_to_idx(input):
ret = 0
for k, ck in enumerate(sorted(input)):
ret += ncombs(ck, k + 1)
return ret
答案 6 :(得分:1)
我编写了一个类来处理使用二项式系数的常用函数,这是您的问题所处的问题类型。它执行以下任务:
以任意N选择K到文件的格式输出所有K索引。 K索引可以用更具描述性的字符串或字母代替。这种方法使解决这类问题变得非常简单。
将K索引转换为已排序二项系数表中条目的正确索引。这种技术比依赖迭代的旧发布技术快得多,并且它不使用太多内存。它通过使用Pascal三角形中固有的数学属性来实现。我的论文谈到了这一点。我相信我是第一个发现和发布这种技术的人,但我可能错了。
将已排序的二项系数表中的索引转换为相应的K索引。
使用Mark Dominus方法计算二项式系数,这样就不太可能溢出并使用更大的数字。
该类是用.NET C#编写的,它提供了一种通过使用通用列表来管理与问题相关的对象(如果有)的方法。此类的构造函数采用名为InitTable的bool值,当为true时,将创建一个通用列表来保存要管理的对象。如果此值为false,则不会创建表。不需要创建表来执行上述4种方法。提供访问者方法来访问该表。
有一个关联的测试类,它显示了如何使用该类及其方法。它已经过2个案例的广泛测试,并且没有已知的错误。
要阅读此课程并下载代码,请参阅Tablizing The Binomial Coeffieicent。
将此类转换为C ++应该不难。