【问题标题】:Compute rank of a combination?计算组合的等级?
【发布时间】:2021-02-19 02:33:06
【问题描述】:

我想为一组组合中的每个组合预先计算一些值。例如,当从 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-d 矩阵,这样我就可以轻松地索引它。虽然这对于 13 选择 3 来说很好,但对于像 13 选择 7 这样的东西来说,这将有太多的开销。

我不想使用字典,因为最终这段代码将在 C 中,而且数组会更有效。

更新:我也有类似的问题,但使用重复组合,所以任何关于如何获得这些排名的答案将不胜感激 =)。

更新:为了清楚起见,我正在尝试节省空间。这些组合中的每一个实际上都对某物进行索引占用了大量空间,比如说 2 KB。如果我要使用 13x13x13 数组,那将是 4 MB,其中我只需要 572 KB 使用(13 选择 3)点。

【问题讨论】:

  • 在排列、组合和分区中,文献术语是“排名”而不是“索引”。搜索“排名组合算法”。 :) 这是一个非常好的页面:home.hccnet.nl/david.dirkse/math/rank/ranking.html
  • 当你说“我不想使用字典”时……是否意味着你不想使用哈希表?
  • @belisarius: 是的,对不起python术语
  • 如果你想预先计算,你总是会得到“M选择N”元素。如果你想要一个不是数组的结构,你必须为每个节点实现至少 1 个指针。那么,为什么你认为有比数组更好的方法呢?
  • @belisarius:数组很好。当我只需要一个 286 个元素的数组时,我只是不想要一个 13 x 13 x 13 的数组。

标签: algorithm language-agnostic hash combinatorics combinations


【解决方案1】:

这是一个概念性答案和一个基于 lex 排序工作原理的代码。 (所以我想我的回答类似于“白痴”,除了我认为他的细节太少而他的链接太多。)我为你写了一个函数unchoose(n,S),假设 S 是一个有序列表。 range(n) 的子集。这个想法:要么 S 包含 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) &lt;= 3 进行预计算。

您还可以优化取消选择,以便它使用循环 if S[0] &gt; 0 添加二项式系数,而不是递减并使用尾递归。

【讨论】:

  • 啊太棒了,很有意义!你会碰巧知道与重复组合的解决方案吗?例如(0,0,0),(0,0,1),(0,0,2),...,(0,1,1),(0,1,2) 等等...跨度>
  • 与重复的组合是一个等效的问题。首先,您有公式 multibinomial(n,k) = binomial(n+k-1,k)。其次,您可以将组合分为两种,一种是使用 0 并在前,另一种是不使用 0 并在使用 multibinomial(n,k-1) 组合之后。代码将非常相似,我不会发布它。 (事实上​​,在 (n,k) 个有重复的组合和 (n+k-1,k) 个没有重复的组合之间有一个标准的双射,称为“星条”。它保留了 lex 的顺序。)
  • 我想我可以从那里弄清楚 - 感谢您的明确回答!你用 8 行代码和几句话解释了这一点,比整篇文章好得多。
【解决方案2】:

您可以尝试使用组合的字典索引。也许这个页面会有所帮助:http://saliu.com/bbs/messages/348.html

此 MSDN 页面有更多详细信息:Generating the mth Lexicographical Element of a Mathematical Combination

注意: MSDN 页面已停用。如果您在上述链接下载文档,您将在下载的 pdf 的第 10201 页找到文章。

更具体一点:

当被视为一个元组时,您可以按字典顺序对组合进行排序。

所以 (0,1,2)

假设你有数字 0 到 n-1 并从中选择了 k。

现在如果第一个元素为零,你知道它是前n-1个中的一个,选择k-1个。

如果第一个元素是1,那么它是接下来n-2个中的一个,选择k-1个。

通过这种方式,您可以递归地计算给定组合在词典排序中的确切位置,并将其映射到您的号码。

这也可以反过来,MSDN 页面解释了如何做到这一点。

【讨论】:

  • +1 我从来没有在 msdn 页面上看到过它的解释以及它在 msdn 页面上的解释(我也从未想过在那里搜索类似的东西)。这样他就可以将字典索引用作数组索引,并且实际上得到了一个完美的散列。
  • @IVlad:是的,我很惊讶在 MSDN 上找到它!
  • 嗯,它似乎不起作用。例如(0, 1, 4) 应该有等级 2:(0,1,2),(0,1,3),(0,1,4),但是做 (4 选择 3) + (1 选择 2) + (0选1)给4..?
  • 你没有添加你在中间得到的数字。如果它是最小的,则添加一个零(即什么都不做)。不过,这可能会变得很棘手。
  • 我在我的博客上重新托管了 MSDN 文章,在我关于 k 组合的数学和算法的最新文章的末尾(包括所有代码):redperegrine.net/2021/04/10/…
【解决方案3】:

使用哈希表来存储结果。一个像样的哈希函数可能是这样的:

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 的数组,而不是字典。

【讨论】:

  • 我自己也想知道。这就是为什么我的答案的自卫编辑,这显然与你的非常相似。
  • 不知道投反对票。看起来相当不错,除了你可能需要找到 p >= n (我想 pp 可能更小)。
【解决方案4】:

我建议使用专门的哈希表。组合的散列应该是值的散列的异或。值的散列基本上是随机位模式。

您可以对表格进行编码以应对冲突,但推导出一个最小的完美散列方案应该相当容易——其中没有两个三项组合给出相同的散列值,并且散列大小和表-尺寸保持在最低限度。

这基本上是Zobrist hashing - 将“移动”视为添加或删除组合中的一项。

编辑

使用哈希表的原因是查找性能 O(n),其中 n 是组合中的项目数(假设没有冲突)。 IIRC,将字典索引计算到组合中要慢得多。

缺点显然是生成表格的前期工作。

【讨论】:

  • 我不同意字典索引生成会明显慢于哈希。如果您有 N 的查找表,请选择 K,查找词典索引也是 O(k),实际上可能更快,但谁知道,直到我们测量 :-) 事实上,我们甚至可能不需要查找表如果我们巧妙地做到这一点。
  • 好的 - 我承认,我认为计算排名的速度比现在慢。我应该先检查一下。
  • @Moron:不是真的 - 我没有说,但我想复杂度会像 O(nm) 或 O(n log m) 其中 m 是要处理的项目数从中选择。我想我可以争辩说算术运算是 O(log m) - 考虑到大集合所需的位宽。不过,这并不完全是一个 sane 论点。
  • 我已经为这个答案想到了一个(稍微)理智的理由。要计算词典排名,您需要选择要排序的项目。否则,您需要非恒定时间检查来解决已处理项目在您的范围内留下的空白。哈希方法不需要这个。因此,如果项目尚未排序,则词典排名为O(n log n)(由于排序)。在 Discworld 巨魔中计数,鉴于 n 既不是 1 也不是 2,而是很多,那么n log n 一定是很多。看 - 完全理智;-)
  • @Steve314:呵呵,没错。我通过预先计算任何元素排序的索引,以我的骇人听闻的方式解决了这个特定问题..
【解决方案5】:

现在,我达成了妥协:我有一个 13x13x13 数组,它只映射到组合的 索引,占用 13x13x13x2 字节 = 4 KB(使用短整数),加上正常大小(13 选择 3)* 2 KB = 572 KB,总共 576 KB。比 4 兆字节好得多,也比排名计算快!

我这样做的部分原因是我似乎无法让白痴的答案起作用。这也更具可扩展性 - 我有一个需要重复组合的情况,但我还没有找到一种方法来计算它们的等级。

【讨论】:

    【解决方案6】:

    你想要的被称为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
    

    【讨论】:

      【解决方案7】:

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

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

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

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

      4. 使用Mark Dominus 方法计算二项式系数,该方法不太可能溢出并且适用于较大的数字。

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

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

      要了解该课程并下载代码,请参阅Tablizing The Binomial Coeffieicent

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

      【讨论】:

        猜你喜欢
        • 2019-10-10
        • 1970-01-01
        • 2021-12-14
        • 2012-01-31
        • 2011-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多