代码之家  ›  专栏  ›  技术社区  ›  Sami

第n个组合

  •  12
  • Sami  · 技术社区  · 16 年前

    是否有一种直接的方法可以获得nCr所有组合的有序集合的第n个组合?

    示例:我有四个元素:[6,4,2,1]。一次取三个的所有可能组合为: [[6, 4, 2], [6, 4, 1], [6, 2, 1], [4, 2, 1]].

    是否有一种算法可以在有序结果集中给出例如第三个答案[6,2,1],而不枚举之前的所有答案?

    5 回复  |  直到 16 年前
        1
  •  17
  •   outis    16 年前

    注意:您可以通过递归地生成包含第一个元素的所有组合,然后生成不包含第一个元素的所有组合来生成序列。在这两种递归情况下,删除第一个元素以获得n-1个元素的所有组合。在Python中:

    def combination(l, r):
        if r == 0:
            yield []
        elif len(l) == r:
            yield l
        else:
            for c in (combination(l[1:], r-1)):
                yield l[0:1]+c
            for c in (combination(l[1:], r)):
                yield c
    

    th 元素,并将计数与k进行比较。如果k小于计数,则您做出该选择。否则,减去计数,然后在该点重复您可能做出的其他选择。如果总是有 b 选项中,您可以将其视为生成基数中的数字 B

    kth(k, choicePoints)
        if choicePoints is empty
            return empty list
        for each choice in head of choicePoints:
            if k < size of choice
                return choice and kth(k, tail of choicePoints)
            else
                k -= size of choice
        signal exception: k is out-of-bounds
    

    这将为您提供一个基于0的索引。如果要基于1,请将比较更改为 k <= size of choice .

    棘手的部分(伪代码中未指定的部分)是选择的大小取决于之前的选择。请注意,伪代码可用于解决比问题更一般的情况。

    对于这个特定的问题,有两种选择( =2)和第一选择的尺寸(即包括1 圣 元素)由 C r-1 . 这里有一个实现(需要合适的 nCr ):

    def kthCombination(k, l, r):
        if r == 0:
            return []
        elif len(l) == r:
            return l
        else:
            i=nCr(len(l)-1, r-1)
            if k < i:
                return l[0:1] + kthCombination(k, l[1:], r-1)
            else:
                return kthCombination(k-i, l[1:], r)
    

    如果你颠倒了选择的顺序,你就颠倒了顺序。

    def reverseKthCombination(k, l, r):
        if r == 0:
            return []
        elif len(l) == r:
            return l
        else:
            i=nCr(len(l)-1, r)
            if k < i:
                return reverseKthCombination(k, l[1:], r)
            else:
                return l[0:1] + reverseKthCombination(k-i, l[1:], r-1)
    

    使用它:

    >>> l = [6, 4, 2, 1]
    >>> [kthCombination(k, [6, 4, 2, 1], 3) for k in range(nCr(len(l), 3)) ]
    [[6, 4, 2], [6, 4, 1], [6, 2, 1], [4, 2, 1]]
    >>> powOf2s=[2**i for i in range(4,-1,-1)]
    >>> [sum(kthCombination(k, powOf2s, 3)) for k in range(nCr(len(powOf2s), 3))]
    [28, 26, 25, 22, 21, 19, 14, 13, 11, 7]
    >>> [sum(reverseKthCombination(k, powOf2s, 3)) for k in range(nCr(len(powOf2s), 3))]
    [7, 11, 13, 14, 19, 21, 22, 25, 26, 28]
    
        2
  •  14
  •   Jonathan Allan    10 年前
    • 太长,读不下去了只需滚动到最底部,即可查看我的 最终解决方案

    当我在寻找同时获得两个结果的方法时,我偶然发现了这个问题 特定的 结合 如果它位于 列表和 大对象集 在后者上找不到太多(你的问题的反面并不那么难以捉摸)。

    因为我也解决了(我认为是)你的确切问题,然后我想我会把我的解决方案发布到这里。

    **
    编辑:我的要求 是 你的要求也是-我看到了答案,认为递归很好。现在,经过六年的漫长岁月,你终于拥有了它;只需向下滚动。
    **

    def iterCombinations(n, k):
    if k==1:
        for i in range(n):
            yield [i]
        return
    result = []
    for a in range(k-1, n):
        for e in iterCombinations(n, k-1):
            if e[-1] == a:
                break
            yield e + [a]
    

    然后,您可以在按降序排列的集合中查找该项(或使用一些等效的比较方法),因此对于所讨论的情况:

    >>> itemsDescending = [6,4,2,1]
    >>> for c in iterCombinations(4, 3):
    ...     [itemsDescending[i] for i in c]
    ...
    [6, 4, 2]
    [6, 4, 1]
    [6, 2, 1]
    [4, 2, 1]
    

    但是,在Python中,这也可能是开箱即用的:

    >>> import itertools
    >>> for c in itertools.combinations(itemsDescending, 3):
    ...     c
    ...
    (6, 4, 2)
    (6, 4, 1)
    (6, 2, 1)
    (4, 2, 1)
    

    以下是我为满足我的要求所做的(实际上也是为了满足你的要求!)属于 该算法不创建或遍历任意方向的有序列表,而是使用简单但有效的非递归实现 N C R ,选择(n,k):

    def choose(n, k):
        '''Returns the number of ways to choose k items from n items'''
        reflect = n - k
        if k > reflect:
            if k > n:
                return 0
            k = reflect
        if k == 0:
            return 1
        for nMinusIPlus1, i in zip(range(n - 1, n - k, -1), range(2, k + 1)):
            n = n * nMinusIPlus1 // i
        return n
    

    以获取某个索引(从零开始)中的组合

    def iterCombination(index, n, k):
        '''Yields the items of the single combination that would be at the provided
        (0-based) index in a lexicographically sorted list of combinations of choices
        of k items from n items [0,n), given the combinations were sorted in 
        descending order. Yields in descending order.
        '''
        if index < 0 or index >= choose(n, k):
            return
        n -= 1
        for i in range(k):
            while choose(n, k) > index:
                n -= 1
            yield n
            index -= choose(n, k)
            n -= 1
            k -= 1
    

    颠倒 有序列表:

    def indexOfCombination(combination):
        '''Returns the (0-based) index the given combination would have if it were in
        a reverse-lexicographically sorted list of combinations of choices of
        len(combination) items from any possible number of items (given the
        combination's length and maximum value)
       - combination must already be in descending order,
         and it's items drawn from the set [0,n).
        '''
        result = 0
        for i, a in enumerate(combination):
            result += choose(a, i + 1)
        return result
    

    这对于你的例子来说太过分了(但我现在意识到这只是一个例子);这是每个索引的顺序:

    def exampleUseCase(itemsDescending=[6,4,2,1], k=3):
        n = len(itemsDescending)
        print("index -> combination -> and back again:")
        for i in range(choose(n, k)):
            c = [itemsDescending[j] for j in iterCombination(i, n, k)][-1::-1]
            index = indexOfCombination([itemsDescending.index(v) for v in c])
            print("{0} -> {1} -> {2}".format(i, c, index))
    
    >>> exampleUseCase()
    index -> combination -> and back again:
    0 -> [6, 4, 2] -> 0
    1 -> [6, 4, 1] -> 1
    2 -> [6, 2, 1] -> 2
    3 -> [4, 2, 1] -> 3
    

    这可以找到给定某个长列表的索引,或者返回列表中某个天文索引的组合 ,例如:

    >>> choose(2016, 37)
    9617597205504126094112265433349923026485628526002095715212972063686138242753600
    >>> list(iterCombination(_-1, 2016, 37))
    [2015, 2014, 2013, 2012, 2011, 2010, 2009, 2008, 2007, 2006, 2005, 2004, 2003,
    2002, 2001, 2000, 1999, 1998, 1997, 1996, 1995, 1994, 1993, 1992, 1991, 1990, 1989,
    1988, 1987, 1986, 1985, 1984, 1983, 1982, 1981, 1980, 1979]
    

    或者,因为这是最后一个,并且可能是由于选择(n,k)中的反射而快,这里有一个从右边在中间,看起来也一样快…

    >>> choose(2016, 37)//2
    4808798602752063047056132716674961513242814263001047857606486031843069121376800
    >>> list(iterCombination(_, 2016, 37))
    [1978, 1973, 1921, 1908, 1825, 1775, 1747, 1635, 1613, 1598, 1529, 1528, 1521,
    1445, 1393, 1251, 1247, 1229, 1204, 1198, 922, 901, 794, 699, 685, 633, 619, 598,
    469, 456, 374, 368, 357, 219, 149, 93, 71]
    

    最后一个例子暂停思考了一秒钟,但你不会吗?

    >>> import random
    >>> rSet = set(random.randint(0, 10000000) for i in range(900))
    >>> len(rSet)
    900
    >>> rList = sorted(rSet, reverse=True)
    >>> combinations.indexOfCombination(rList)
    61536587905102303838316048492163850175478325236595592744487336325506086930974887
    88085020093159925576117511028315621934208381981476407812702689774826510322023536
    58905845549371069786639595263444239118366962232872361362581506476113967993096033
    00541202874946853699568596881200225925266331936183173583581021914595163799417151
    30442624813775945054888304722079206982972852037480516813527237183254850056012217
    59834465303543702263588008387352235149083914737690225710105023486226582087736870
    38383323140972279867697434315252036074490127510158752080225274972225311906715033
    86851377357968649982293794242170046400174118714525559851836064661141086690326842
    25236658978135989907667078625869419802333512020715700514133380517628637151215549
    05922388534567108671308819960483147825031620798631811671493891643972220604919591
    22785587505280326638477135315176731640100473359830821781905546117103137944239120
    34912084544221250309244925308316352643060056100719194985568284049903555621750881
    39419639825279398618630525081169688672242833238889454445237928356800414839702024
    66807635358129606994342005075585962080795273287472139515994244684088406544976674
    84183671032002497594936116837768233617073949894918741875863985858049825755901232
    89317507965160689287607868119414903299382093412911433254998227245783454244894604
    83654290108678890682359278892580855226717964180806265176337132759167920384512456
    91624558534942279041452960272707049107641475225516294235268581475735143470692000
    78400891862852130481822509803019636619427631175355448729708451565341764545325720
    79277290914349746541071731127111532099038538549697091038496002102703737347343739
    96398832832674081286904287066696046621691978697914823322322650123025472624927566
    99891468668052668317066769517155581261265629289158798073055495539590686279250097
    27295943276536772955923599217742543093669565147228386873469711200278811335649924
    13587219640724942441913695193417732608127949738209466313175361161142601108707568
    19470026889319648128790363676253707359290547393198350533094409863254710237344552
    47692325209744353688541868412075798500629908908768438513508959321262250985142709
    19794478379412756202638771417821781240327337108495689300616872374578607430951230
    96908870723878513999404242546015617238957825116802801618973562178005776911079790
    22026655573872019955677676783191505879571719659770550759779880002320421606755826
    75809722478174545846409923210824885805972611279030267270741509747224602604003738
    30411365119180944456819762167312738395140461035991994771968906979578667047734952
    21981545694935313345331923300019842406900689401417602004228459137311983483386802
    30352489602769346000257761959413965109940729263098747702427952104316612809425394
    85037536245288888254374135695390839718978818689595231708490351927063849922772653
    26064826999661128817511630298712833048667406916285156973335575847429111697259113
    53969532522640227276562651123634766230804871160471143157687290382053412295542343
    14022687833967461351170188107671919648640149202504369991478703293224727284508796
    06843631262345918398240286430644564444566815901074110609701319038586170760771099
    41252989796265436701638358088345892387619172572763571929093224171759199798290520
    71975442996399826830220944004118266689537930602427572308646745061258472912222347
    18088442198837834539211242627770833874751143136048704550494404981971932449150098
    52555927020553995188323691320225317096340687798498057634440618188905647503384292
    79493920419695886724506109053220167190536026635080266763647744881063220423654648
    36855624855494077960732944499038847158715263413026604773216510801253044020991845
    89652657529729792772055725210165026891724511953666038764273616212464901231675592
    46950937136633665320781952510620087284589083139308516989522633786063418913473703
    96532777760440118656525488729217328376766171004246127636983612583177565603918697
    15557602015171235214344399010185766876727226408494760175957535995025356361689144
    85181975631986409708533731043231896096597038345028523539733981468056497208027899
    6245509252811753667386001506195
    

    对于这样大的组合列表,我们可以对空间进行二进制搜索,而我们添加的开销意味着对于小的组合列表,搜索速度只会稍微慢一点:

    def iterCombination(index, n, k):
        '''Yields the items of the single combination that would be at the provided
        (0-based) index in a lexicographically sorted list of combinations of choices
        of k items from n items [0,n), given the combinations were sorted in 
        descending order. Yields in descending order.
        '''
        if index < 0 or n < k or n < 1 or k < 1 or choose(n, k) <= index:
            return
        for i in range(k, 0, -1):
            d = (n - i) // 2 or 1
            n -= d
            while 1:
                nCi = choose(n, i)
                while nCi > index:
                    d = d // 2 or 1
                    n -= d
                    nCi = choose(n, i)
                if d == 1:
                    break
                n += d
                d //= 2
                n -= d
            yield n
            index -= nCi
    

    从这里你可能会注意到 choose 有取消的条款,如果我们取消所有的东西,我们最终会得到更快的实现,我认为。。。

    该问题的最优函数

    def iterCombination(index, n, k):
        '''Yields the items of the single combination that would be at the provided
        (0-based) index in a lexicographically sorted list of combinations of choices
        of k items from n items [0,n), given the combinations were sorted in 
        descending order. Yields in descending order.
        '''
        nCk = 1
        for nMinusI, iPlus1 in zip(range(n, n - k, -1), range(1, k + 1)):
            nCk *= nMinusI
            nCk //= iPlus1
        curIndex = nCk
        for k in range(k, 0, -1):
            nCk *= k
            nCk //= n
            while curIndex - nCk > index:
                curIndex -= nCk
                nCk *= (n - k)
                nCk -= nCk % k
                n -= 1
                nCk //= n
            n -= 1
            yield n
    

    最后提醒您,对于问题的用例,您将执行以下操作:

    def combinationAt(index, itemsDescending, k):
        return [itemsDescending[i] for i in
                list(iterCombination(index, len(itemsDescending), k))[-1::-1]]
    
    >>> itemsDescending = [6,4,2,1]
    >>> numberOfItemsBeingChosen = 3
    >>> zeroBasedIndexWanted = 1
    >>> combinationAt(zeroBasedIndexWanted, itemsDescending, numberOfItemsBeingChosen)
    [6, 4, 1]
    
        3
  •  8
  •   poundifdef    16 年前

    一种方法是使用位的属性。这仍然需要一些枚举,但您不必枚举每个集合。

    {6, 4, 2, 1}
    
    0000 - {(no numbers in set)}
    0001 - {1}
    0010 - {2}
    0011 - {2, 1}
    ...
    1111 - {6, 4, 2, 1}
    

    查看每个“位”如何对应于“该数字是否在您的集合中”?我们在这里看到有16种可能性(2^4)。

    所以现在我们可以遍历并找到所有只有3位开启的可能性。这将告诉我们存在的“3”的所有组合:

    0111 - {4, 2, 1}
    1011 - {6, 2, 1}
    1101 - {6, 4, 1}
    1110 - {6, 4, 2}
    

    让我们将每个二进制值重写为十进制值:

    0111 = 7
    1011 = 11
    1101 = 13
    1110 = 14
    

    现在我们已经这样做了-好吧,你说你想要“第三次”枚举。让我们看看第三大数字:11。它有位模式1011。它对应于{6, 2, 1}

    酷!

        4
  •  3
  •   pylang    8 年前

    来自Python3.6 itertools recipes :

    def nth_combination(iterable, r, index):
        'Equivalent to list(combinations(iterable, r))[index]'
        pool = tuple(iterable)
        n = len(pool)
        if r < 0 or r > n:
            raise ValueError
        c = 1
        k = min(r, n-r)
        for i in range(1, k+1):
            c = c * (n - k + i) // i
        if index < 0:
            index += c
        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(pool[-1-n])
        return tuple(result)
    

    实际上:

    iterable, r, index = [6, 4, 2, 1], 3, 2
    
    nth_combination(iterable, r, index)
    # (6, 2, 1)
    

    或者,如文档字符串中所述:

    import itertools as it
    
    
    list(it.combinations(iterable, r))[index]
    # (6, 2, 1)
    

    另见 more_itertools -实现的第三方库 this recipe 为你。通过以下方式安装:

    > pip install more_itertools
    
        5
  •  2
  •   Anycorn    16 年前

    将数字排列到元组的上三角矩阵中:

    A(n-1,n-1)   
    Aij = [i+1, j-1]
    

    至少我会这样处理这个问题

    让我做一个维度的例子,原则上你们可以扩展到20个维度(簿记可能很糟糕)。

    ij = (i*i + i)/2 + j // ij is also the combination number
    (i,j) = decompose(ij) // from ij one can recover i,j components
    I = i // actual first index
    J = j + 1 // actual second index
    

    这个二维的例子适用于任何数字n,你不必把排列列成表格。

        6
  •  0
  •   Deepesh Patel    5 年前

    是的,有没有一种直接的方法可以得到nCr所有组合的有序集合的第n个组合?假设您需要生成第0、第3、第6个。。给定集合的组合。您可以直接生成它,而无需使用JNuberTools在两者之间生成组合。您甚至可以生成下一个第十亿个组合(如果设置的大小较大) 下面是代码示例:

    JNumberTools.combinationsOf(list)
            .uniqueNth(8,1000_000_000) //skip to billionth combination of size 8
            .forEach(System.out::println);
    

    <dependency>
        <groupId>io.github.deepeshpatel</groupId>
        <artifactId>jnumbertools</artifactId>
        <version>1.0.0</version>
    </dependency>