如何在给出组合时计算指数(词典顺序)

时间:2011-03-15 03:29:52

标签: algorithm performance math combinatorics

我知道有一种算法允许,在给定数字组合(无重复,无顺序)的情况下,计算字典顺序的索引。
对我的应用来说,加速事情非常有用......

例如:

combination(10, 5)  
1 - 1 2 3 4 5  
2 - 1 2 3 4 6  
3 - 1 2 3 4 7  
....  
251 - 5 7 8 9 10  
252 - 6 7 8 9 10  

我需要算法返回给定组合的索引 es:index( 2, 5, 7, 8, 10 ) - >指数

编辑:实际上我正在使用生成所有组合C(53,5)的Java应用程序并将它们插入到TreeMap中。 我的想法是创建一个包含我可以用这个算法索引的所有组合(和相关数据)的数组 一切都是加速组合搜索。 但是我尝试了一些(不是全部)解决方案,你提出的算法比TreeMap中的get()慢 如果它有帮助:我的需求是从0到52的5从53的组合。

再次感谢大家: - )

12 个答案:

答案 0 :(得分:8)

这是一个可以完成工作的片段。

#include <iostream>

int main()
{
    const int n = 10;
    const int k = 5;

    int combination[k] = {2, 5, 7, 8, 10};

    int index = 0;
    int j = 0;
    for (int i = 0; i != k; ++i)
    {
        for (++j; j != combination[i]; ++j)
        {
            index += c(n - j, k - i - 1);
        }
    }

    std::cout << index + 1 << std::endl;

    return 0;
}

假设你有一个功能

int c(int n, int k);

将返回从n个元素中选择k个元素的组合数。 循环计算给定组合之前的组合数。 通过在最后添加一个,我们得到实际的索引。

对于给定的组合有 c(9,4)= 126个含有1的组合,因此以字典顺序排在前面。

在包含2作为最小数字的组合中有

c(7,3)= 35个组合,其中3为第二小数

c(6,3)= 20个组合,其中4为第二小数

所有这些都在给定的组合之前。

在包含2和5作为两个最小数字的组合中,有

c(4,2)= 6种组合,其中6为第三小数。

所有这些都在给定的组合之前。

如果在内部循环中放置一个print语句,您将获得数字 126,35,20,6,1。 希望能够解释代码。

答案 1 :(得分:6)

将您的号码选择转换为factorial base number。此数字将是您想要的索引。从技术上讲,这会计算所有排列的词典索引,但是如果你只给它组合,那么索引仍然是有序的,只是每个组合之间的所有排列都有一些大的间隙。

编辑:删除了伪代码,这是不正确的,但上面的方法应该有效。现在太累了想出正确的伪代码。

编辑2:这是一个例子。假设我们从一组10个元素中选择了5个元素的组合,就像上面的例子一样。如果组合为2 3 4 6 8,您将获得相关的阶乘基数,如下所示:

取出未选中的元素并计算你要经过的数量以获得你正在选择的元素。

1 2 3 4 5 6 7 8 9 10
2 -> 1
1 3 4 5 6 7 8 9 10
3 -> 1
1 4 5 6 7 8 9 10
4 -> 1
1 5 6 7 8 9 10
6 -> 2
1 5 7 8 9 10
8 -> 3

因此,因子基数中的指数为1112300000

在十进制基数中,它是

1*9! + 1*8! + 1*7! + 2*6! + 3*5! = 410040

答案 2 :(得分:3)

这是Kreher和Stinson的组合算法第44页的算法2.7 kSubsetLexRank

r = 0
t[0] = 0
for i from 1 to k
    if t[i - 1] + 1 <= t[i] - 1
        for j from t[i - 1] to t[i] - 1
            r = r + choose(n - j, k - i)
return r

数组t保存您的值,例如[5 7 8 9 10]。函数choose(n,k)计算数字“n选择k”。结果值r将是索引,示例为251。其他输入是n和k,例如它们将是10和5。

答案 3 :(得分:2)

零基,

# v: array of length k consisting of numbers between 0 and n-1 (ascending)
def index_of_combination(n,k,v):
    idx = 0
    for p in range(k-1):
        if p == 0: arrg = range(1,v[p]+1)
        else: arrg = range(v[p-1]+2, v[p]+1)
        for a in arrg:
            idx += combi[n-a, k-1-p]
    idx += v[k-1] - v[k-2] - 1
    return idx

答案 4 :(得分:1)

Null Set有正确的方法。索引对应于序列的阶乘基数。您可以像任何其他基数一样构建因子基数,但每个数字的基数都会减少。

现在,析因基数中每个数字的值是小于尚未使用的元素数。所以,对于组合(10,5):

(1 2 3 4 5) == 0*9!/5! + 0*8!/5! + 0*7!/5! + 0*6!/5! + 0*5!/5!
            == 0*3024 + 0*336 + 0*42 + 0*6 + 0*1
            == 0

(10 9 8 7 6) == 9*3024 + 8*336 + 7*42 + 6*6 + 5*1
             == 30239

逐步计算索引应该很容易。

答案 5 :(得分:1)

还有另一种方法可以做到这一切。您可以生成所有可能的组合并将它们写入二进制文件,其中每个梳子由从零开始的索引表示。然后,当您需要查找索引并给出组合时,可以对文件应用二进制搜索。这是功能。它是用VB.NET 2010为我的乐透程序编写的,它与以色列彩票系统配合使用,因此有一个奖励(第7个)号码;只是忽略它。

Public Function Comb2Index( _
ByVal gAr() As Byte) As UInt32
 Dim mxPntr As UInt32 = WHL.AMT.WHL_SYS_00     '(16.273.488)
 Dim mdPntr As UInt32 = mxPntr \ 2
 Dim eqCntr As Byte
 Dim rdAr() As Byte

    modBinary.OpenFile(WHL.WHL_SYS_00, _
    FileMode.Open, FileAccess.Read)

    Do
    modBinary.ReadBlock(mdPntr, rdAr)
RP: If eqCntr = 7 Then GoTo EX

        If gAr(eqCntr) = rdAr(eqCntr) Then
           eqCntr += 1
           GoTo RP

        ElseIf gAr(eqCntr) < rdAr(eqCntr) Then
            If eqCntr > 0 Then eqCntr = 0
               mxPntr = mdPntr
               mdPntr \= 2

        ElseIf gAr(eqCntr) > rdAr(eqCntr) Then
            If eqCntr > 0 Then eqCntr = 0
            mdPntr += (mxPntr - mdPntr) \ 2
        End If

    Loop Until eqCntr = 7

EX: modBinary.CloseFile()
    Return mdPntr

End Function

P.S。在Core 2 Duo上生成1600万个梳子需要5到10分钟。在SATA驱动器上使用文件二进制搜索查找索引需要397毫秒。

答案 6 :(得分:1)

对于我的项目我也需要相同的,我找到的最快的解决方案是(Python):

import math

def nCr(n,r):
    f = math.factorial
    return f(n) / f(r) / f(n-r)

def index(comb,n,k):
    r=nCr(n,k)
    for i in range(k):
        if n-comb[i]<k-i:continue
        r=r-nCr(n-comb[i],k-i)
    return r

我的输入“comb”包含按升序排列的元素您可以使用例如:

来测试代码
import itertools
k=3
t=[1,2,3,4,5]
for x in itertools.combinations(t, k):
    print x,index(x,len(t),k)

如果梳子=(a1,a2,a3 ......,ak)(按递增顺序),则不难证明:

index = [nCk-(n-a1 + 1)Ck] + [(n-a1)C(k-1) - (n-a2 + 1)C(k-1)] + ... =     nCk - (n-a1)Ck - (n-a2)C(k-1) - .... - (n-ak)C1

答案 7 :(得分:0)

编辑:没关系。这是完全错误的。


让你的组合成为( 1 2 ,..., k-1 k < / sub>)其中a 1 &lt; a 2 &lt; ......&lt;一个<子>ķ。如果a> = b则选择(a,b)= a!/(b!*(a-b)!),否则为0。然后,您要查找的索引是

  

选择(a k -1,k)+选择(a k-1 -1,k-1)+选择(a k-2 -1,k-2)+ ... +选择(a 2 -1,2)+选择( 1 -1,1)+ 1

第一项计算k元素组合的数量,使得最大元素小于 k 。第二项计算(k-​​1)元素组合的数量,使得最大元素小于 k-1 。等等。

请注意,要从(在您的示例中为10)中选择的元素的Universe大小在索引的计算中不起作用。你能明白为什么吗?

答案 8 :(得分:0)

假设最大setSize不是太大,您可以简单地生成一个查找表,其中输入以这种方式编码:

  int index(a,b,c,...)
  {
      int key = 0;
      key |= 1<<a;
      key |= 1<<b;
      key |= 1<<c;
      //repeat for all arguments
      return Lookup[key];
  } 

要生成查找表,请查看this "banker's order" algorithm。生成所有组合,并存储每个nItem的基本索引。 (对于p6上的示例,这将是[0,1,5,11,15])。请注意,通过以与示例相反的顺序存储答案(LSB首先设置)您将只需要一个表,大小为最大可能集。

通过遍历执行Lookup[combination[i]]=i-baseIdx[nItems]

的组合来填充查找表

答案 9 :(得分:0)

如果你有一组正整数0&lt; = x_1&lt; X_2&LT; ......&lt; x_k,那么你可以使用一种叫做压扁顺序的东西:

I = sum(j=1..k) Choose(x_j,j)

压扁顺序的美妙之处在于它独立于父集中的最大值。

压扁的订单不是您要查找的订单,但它是相关的。

使用压扁顺序来获取{1,...,n)的k子集集合中的词典顺序是

1 <= x1 < ... < x_k <=n

计算

 0 <= n-x_k < n-x_(k-1) ... < n-x_1

然后计算(n-x_k,...,n-k_1)的压扁顺序索引

然后从Choose(n,k)中减去压扁的顺序索引,得到你的结果,即词典索引。

如果你有一个相对较小的n和k值,你可以缓存所有的值用a选择(a,b)

请参阅Anderson, Combinatorics on Finite Sets, pp 112-119

答案 10 :(得分:0)

样本解决方案:

class Program
{
    static void Main(string[] args)
    {
        // The input
        var n = 5;
        var t = new[] { 2, 4, 5 };

        // Helping transformations
        ComputeDistances(t);
        CorrectDistances(t);

        // The algorithm
        var r = CalculateRank(t, n);

        Console.WriteLine("n = 5");
        Console.WriteLine("t = {2, 4, 5}");
        Console.WriteLine("r = {0}", r);

        Console.ReadKey();
    }

    static void ComputeDistances(int[] t)
    {
        var k = t.Length;
        while (--k >= 0)
            t[k] -= (k + 1);
    }

    static void CorrectDistances(int[] t)
    {
        var k = t.Length;
        while (--k > 0)
            t[k] -= t[k - 1];
    }

    static int CalculateRank(int[] t, int n)
    {
        int k = t.Length - 1, r = 0;

        for (var i = 0; i < t.Length; i++)
        {
            if (t[i] == 0)
            {
                n--;
                k--;
                continue;
            }

            for (var j = 0; j < t[i]; j++)
            {
                n--;
                r += CalculateBinomialCoefficient(n, k);
            }

            n--;
            k--;
        }

        return r;
    }

    static int CalculateBinomialCoefficient(int n, int k)
    {
        int i, l = 1, m, x, y;

        if (n - k < k)
        {
            x = k;
            y = n - k;
        }
        else
        {
            x = n - k;
            y = k;
        }

        for (i = x + 1; i <= n; i++)
            l *= i;

        m = CalculateFactorial(y);

        return l/m;
    }

    static int CalculateFactorial(int n)
    {
        int i, w = 1;

        for (i = 1; i <= n; i++)
            w *= i;

        return w;
    }
}

幕后的想法是将k子集与从n尺寸集中绘制k元素的操作相关联。它是一种组合,因此可能的项目总数将为(n k)。我们可以在Pascal Triangle中寻求解决方案,这是一个线索。经过一段时间将手动编写的示例与Pascal三角形中的相应数字进行比较后,我们将找到模式,从而找到算法。

答案 11 :(得分:0)

我使用了user515430的答案并将其转换为python3。另外,它支持非连续值,因此您可以将[1,3,5,7,9]作为您的池而不是range(1,11)

from itertools import combinations
from scipy.special import comb
from pandas import Index

debugcombinations = False

class IndexedCombination:
    def __init__(self, _setsize, _poolvalues):
        self.setsize = _setsize
        self.poolvals = Index(_poolvalues)
        self.poolsize = len(self.poolvals)
        self.totalcombinations = 1
        fast_k = min(self.setsize, self.poolsize - self.setsize)
        for i in range(1, fast_k + 1):
            self.totalcombinations = self.totalcombinations * (self.poolsize - fast_k + i) // i

        #fill the nCr cache
        self.choose_cache = {}
        n = self.poolsize
        k = self.setsize
        for i in range(k + 1):
            for j in range(n + 1):
                if n - j >= k - i:
                    self.choose_cache[n - j,k - i] = comb(n - j,k - i, exact=True)
        if debugcombinations:
            print('testnth = ' + str(self.testnth()))

    def get_nth_combination(self,index):
        n = self.poolsize
        r = self.setsize
        c = self.totalcombinations
        #if index < 0 or index >= c:
        #    raise IndexError
        result = []
        while r:
            c, n, r = c*r//n, n-1, r-1
            while index >= c:
                index -= c
                c, n = c*(n-r)//n, n-1
            result.append(self.poolvals[-1 - n])
        return tuple(result)

    def get_n_from_combination(self,someset):
        n = self.poolsize
        k = self.setsize
        index = 0
        j = 0
        for i in range(k):
            setidx = self.poolvals.get_loc(someset[i])
            for j in range(j + 1, setidx + 1):
                index += self.choose_cache[n - j, k - i - 1]
            j += 1
        return index

    #just used to test whether nth_combination from the internet actually works
    def testnth(self):
        n = 0
        _setsize = self.setsize
        mainset = self.poolvals
        for someset in combinations(mainset, _setsize):
            nthset = self.get_nth_combination(n)
            n2 = self.get_n_from_combination(nthset)
            if debugcombinations:
                print(str(n) + ': ' + str(someset) + ' vs ' + str(n2) + ': ' + str(nthset))
            if n != n2:
                return False
            for x in range(_setsize):
                if someset[x] != nthset[x]:
                    return False
            n += 1
        return True

setcombination = IndexedCombination(5, list(range(1,10+1)))
print( str(setcombination.get_n_from_combination([2,5,7,8,10])))

返回188