计算组合的等级?

时间:2010-06-29 17:22:13

标签: algorithm language-agnostic hash combinatorics combinations

我想在一组组合中为每个组合预先计算一些值。例如,当从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)点。

7 个答案:

答案 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),ppp是素数。

所以你要存储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)

我编写了一个类来处理使用二项式系数的常用函数,这是您的问题所处的问题类型。它执行以下任务:

  1. 以任意N选择K到文件的格式输出所有K索引。 K索引可以用更具描述性的字符串或字母代替。这种方法使解决这类问题变得非常简单。

  2. 将K索引转换为已排序二项系数表中条目的正确索引。这种技术比依赖迭代的旧发布技术快得多,并且它不使用太多内存。它通过使用Pascal三角形中固有的数学属性来实现。我的论文谈到了这一点。我相信我是第一个发现和发布这种技术的人,但我可能错了。

  3. 将已排序的二项系数表中的索引转换为相应的K索引。

  4. 使用Mark Dominus方法计算二项式系数,这样就不太可能溢出并使用更大的数字。

  5. 该类是用.NET C#编写的,它提供了一种通过使用通用列表来管理与问题相关的对象(如果有)的方法。此类的构造函数采用名为InitTable的bool值,当为true时,将创建一个通用列表来保存要管理的对象。如果此值为false,则不会创建表。不需要创建表来执行上述4种方法。提供访问者方法来访问该表。

  6. 有一个关联的测试类,它显示了如何使用该类及其方法。它已经过2个案例的广泛测试,并且没有已知的错误。

  7. 要阅读此课程并下载代码,请参阅Tablizing The Binomial Coeffieicent

    将此类转换为C ++应该不难。