从 n 返回 k 个元素的所有组合的算法

Posted

技术标签:

【中文标题】从 n 返回 k 个元素的所有组合的算法【英文标题】:Algorithm to return all combinations of k elements from n 【发布时间】:2010-09-12 18:21:38 【问题描述】:

我想编写一个函数,它接受一个字母数组作为参数,并选择其中的一些字母。

假设您提供了一个包含 8 个字母的数组,并希望从中选择 3 个字母。那么你应该得到:

8! / ((8 - 3)! * 3!) = 56

数组(或单词)返回,每个包含 3 个字母。

【问题讨论】:

编程语言有什么偏好吗? 你想如何处理重复的字母? 没有语言偏好,我会用 ruby​​ 编写代码,但大致了解使用什么算法就可以了。两个相同值的字母可以存在,但不能出现两次完全相同的字母。 flash as3 解决方案***.com/questions/4576313/… php 中,以下应该可以解决问题:***.com/questions/4279722/… 【参考方案1】:

您可以使用 Asif 算法生成所有可能的组合。这可能是最简单和最有效的一种。可以查看中篇文章here。

让我们看一下 javascript 中的实现。

function Combinations( arr, r ) 
    // To avoid object referencing, cloning the array.
    arr = arr && arr.slice() || [];

    var len = arr.length;

    if( !len || r > len || !r )
        return [ [] ];
    else if( r === len ) 
        return [ arr ];

    if( r === len ) return arr.reduce( ( x, v ) => 
        x.push( [ v ] );

        return x;
    , [] );

    var head = arr.shift();

    return Combinations( arr, r - 1 ).map( x => 
        x.unshift( head );

        return x;
     ).concat( Combinations( arr, r ) );


// Now do your stuff.

console.log( Combinations( [ 'a', 'b', 'c', 'd', 'e' ], 3 ) );

【讨论】:

【参考方案2】:

Art of Computer Programming Volume 4: Fascicle 3 有很多可能比我描述的更适合您的特定情况。

格雷码

您会遇到的一个问题当然是内存,而且很快,您的集合中的 20 个元素就会出现问题 -- 20C3 = 1140 . 如果你想迭代集合,最好使用修改后的格雷码算法,这样你就不会把它们都保存在内存中。这些从前一个组合生成下一个组合并避免重复。其中有许多用于不同的用途。我们想最大化连续组合之间的差异吗?最小化?等等。

一些描述格雷码的原始论文:

    Some Hamilton Paths and a Minimal Change Algorithm Adjacent Interchange Combination Generation Algorithm

以下是其他一些涵盖该主题的论文:

    An Efficient Implementation of the Eades, Hickey, Read Adjacent Interchange Combination Generation Algorithm(PDF,Pascal 代码) Combination Generators Survey of Combinatorial Gray Codes (PostScript) An Algorithm for Gray Codes

Chase's Twiddle(算法)

菲利普·J·蔡斯,`Algorithm 382: Combinations of M out of N Objects' (1970)

The algorithm in C...

按字典顺序排列的组合索引(Buckles 算法 515)

您还可以通过索引(按字典顺序)引用组合。意识到索引应该是基于索引从右到左的一些变化,我们可以构造一些应该恢复组合的东西。

所以,我们有一个集合 1,2,3,4,5,6... 我们想要三个元素。假设 1,2,3 我们可以说元素之间的差异是一个且有序且最小。 1,2,4 有一个变化,按字典顺序排列为 2。因此,最后一个位置的“变化”数说明了字典顺序的一个变化。第二位,有一个变化 1,3,4 有一个变化,但由于它位于第二位(与原始集合中的元素数量成正比),所以变化更多。

我描述的方法是一种解构,看起来,从集合到索引,我们需要做相反的事情——这要复杂得多。这就是Buckles 解决问题的方式。我写了一些C to compute them,做了一些小的改动——我使用集合的索引而不是数字范围来表示集合,所以我们总是从 0...n 开始工作。 注意:

    由于组合是无序的,1,3,2 = 1,2,3——我们按字典顺序排列它们。 此方法有一个隐含的 0 来启动第一个差异的集合。

按字典顺序排列的组合索引 (McCaffrey)

有another way:,它的概念更容易掌握和编程,但没有Buckles的优化。幸运的是,它也不会产生重复的组合:

最大化 的集合,其中。

例如:27 = C(6,4) + C(5,3) + C(2,2) + C(1,1)。所以,第 27 个字典组合的四件事是:1,2,5,6,这些是您想要查看的任何集合的索引。下面的示例(OCaml),需要choose 函数,留给读者:

(* this will find the [x] combination of a [set] list when taking [k] elements *)
let combination_maccaffery set k x =
    (* maximize function -- maximize a that is aCb              *)
    (* return largest c where c < i and choose(c,i) <= z        *)
    let rec maximize a b x =
        if (choose a b ) <= x then a else maximize (a-1) b x
    in
    let rec iterate n x i = match i with
        | 0 -> []
        | i ->
            let max = maximize n i x in
            max :: iterate n (x - (choose max i)) (i-1)
    in
    if x < 0 then failwith "errors" else
    let idxs =  iterate (List.length set) x k in
    List.map (List.nth set) (List.sort (-) idxs)

一个小而简单的组合迭代器

提供以下两种算法用于教学目的。他们实现了迭代器和(更通用的)文件夹整体组合。 它们尽可能快,复杂度为 O(nCk)。内存消耗受k约束。

我们将从迭代器开始,它将为每个组合调用用户提供的函数

let iter_combs n k f =
  let rec iter v s j =
    if j = k then f v
    else for i = s to n - 1 do iter (i::v) (i+1) (j+1) done in
  iter [] 0 0

更通用的版本将调用用户提供的函数以及状态变量,从初始状态开始。由于我们需要在不同状态之间传递状态,因此我们不会使用 for 循环,而是使用递归,

let fold_combs n k f x =
  let rec loop i s c x =
    if i < n then
      loop (i+1) s c @@
      let c = i::c and s = s + 1 and i = i + 1 in
      if s < k then loop i s c x else f c x
    else x in
  loop 0 0 [] x

【讨论】:

是的,托马斯会的。它与数组中的数据无关。如果这是您想要的效果,或者选择其他算法,您始终可以先过滤掉重复项。 很棒的答案。您能否提供每种算法的运行时间和内存分析的摘要? 相当好的答案。 20C3 是 1140,感叹号在这里令人困惑,因为它看起来像一个阶乘,并且阶乘确实输入了查找组合的公式。因此,我将删除感叹号。 doc_180:Chase 的算法比其他算法更复杂,但具有额外的好处,即每个连续组合与前一个组合仅相差一个元素。如果使用前一个组合的部分工作可以更快地完成需要对每个组合执行的工作,这将很有用。 很糟糕,许多引文都在付费墙后面。是否有可能包含非付费链接或包含来自来源的可引用 sn-ps?【参考方案3】:

PowerShell 解决方案:

function Get-NChooseK

    <#
    .SYNOPSIS
    Returns all the possible combinations by choosing K items at a time from N possible items.

    .DESCRIPTION
    Returns all the possible combinations by choosing K items at a time from N possible items.
    The combinations returned do not consider the order of items as important i.e. 123 is considered to be the same combination as 231, etc.

    .PARAMETER ArrayN
    The array of items to choose from.

    .PARAMETER ChooseK
    The number of items to choose.

    .PARAMETER AllK
    Includes combinations for all lesser values of K above zero i.e. 1 to K.

    .PARAMETER Prefix
    String that will prefix each line of the output.

    .EXAMPLE
    PS C:\> Get-NChooseK -ArrayN '1','2','3' -ChooseK 3
    123

    .EXAMPLE
    PS C:\> Get-NChooseK -ArrayN '1','2','3' -ChooseK 3 -AllK
    1
    2
    3
    12
    13
    23
    123

    .EXAMPLE
    PS C:\> Get-NChooseK -ArrayN '1','2','3' -ChooseK 2 -Prefix 'Combo: '
    Combo: 12
    Combo: 13
    Combo: 23

    .NOTES
    Author : nmbell
    #>

    # Use cmdlet binding
    [CmdletBinding()]

    # Declare parameters
    Param
    (

        [String[]]
        $ArrayN

    ,   [Int]
        $ChooseK

    ,   [Switch]
        $AllK

    ,   [String]
        $Prefix = ''

    )

    BEGIN
    
    

    PROCESS
    
        # Validate the inputs
        $ArrayN = $ArrayN | Sort-Object -Unique

        If ($ChooseK -gt $ArrayN.Length)
        
            Write-Error "Can't choose $ChooseK items when only $($ArrayN.Length) are available." -ErrorAction Stop
        

        # Control the output
        $firstK = If ($AllK)  1  Else  $ChooseK 

        # Get combinations
        $firstK..$ChooseK | ForEach-Object 

            $thisK = $_

            $ArrayN[0..($ArrayN.Length-($thisK--))] | ForEach-Object 
                If ($thisK -eq 0)
                
                    Write-Output ($Prefix+$_)
                
                Else
                
                    Get-NChooseK -Array ($ArrayN[($ArrayN.IndexOf($_)+1)..($ArrayN.Length-1)]) -Choose $thisK -AllK:$false -Prefix ($Prefix+$_)
                
            

        
    

    END
    
    



例如:

PS C:\>Get-NChooseK -ArrayN 'A','B','C','D','E' -ChooseK 3
ABC
ABD
ABE
ACD
ACE
ADE
BCD
BCE
BDE
CDE

最近在IronScripter 网站上发布了一个与此问题类似的挑战,您可以在其中找到我的链接和其他一些解决方案。

【讨论】:

【参考方案4】:

最近IronScripter 网站上出现了一个需要 n-choose-k 解决方案的 PowerShell 挑战。我在那里发布了一个解决方案,但这里有一个更通用的版本。

AllK 开关用于控制输出是仅长度为 ChooseK 的组合,还是长度为 1 到 ChooseK 的组合。 Prefix 参数实际上是输出字符串的累加器,但其效果是为初始调用传入的值实际上会为每一行输出添加前缀。
function Get-NChooseK


    [CmdletBinding()]

    Param
    (

        [String[]]
        $ArrayN

    ,   [Int]
        $ChooseK

    ,   [Switch]
        $AllK

    ,   [String]
        $Prefix = ''

    )

    PROCESS
    
        # Validate the inputs
        $ArrayN = $ArrayN | Sort-Object -Unique

        If ($ChooseK -gt $ArrayN.Length)
        
            Write-Error "Can't choose $ChooseK items when only $($ArrayN.Length) are available." -ErrorAction Stop
        

        # Control the output
        $firstK = If ($AllK)  1  Else  $ChooseK 

        # Get combinations
        $firstK..$ChooseK | ForEach-Object 

            $thisK = $_

            $ArrayN[0..($ArrayN.Length-($thisK--))] | ForEach-Object 
                If ($thisK -eq 0)
                
                    Write-Output ($Prefix+$_)
                
                Else
                
                    Get-NChooseK -Array ($ArrayN[($ArrayN.IndexOf($_)+1)..($ArrayN.Length-1)]) -Choose $thisK -AllK:$false -Prefix ($Prefix+$_)
                
            

        
    


例如:

PS C:\>$ArrayN  = 'E','B','C','A','D'
PS C:\>$ChooseK = 3
PS C:\>Get-NChooseK -ArrayN $ArrayN -ChooseK $ChooseK
ABC
ABD
ABE
ACD
ACE
ADE
BCD
BCE
BDE
CDE

【讨论】:

【参考方案5】:

另一个 python 递归解决方案。

def combination_indicies(n, k, j = 0, stack = []):   
    if len(stack) == k:            
        yield list(stack)
        return
        
    for i in range(j, n):
        stack.append(i)
        for x in combination_indicies(n, k, i + 1, stack):            
            yield x
        stack.pop()  
        
list(combination_indicies(5, 3))

输出:

[[0, 1, 2],
 [0, 1, 3],
 [0, 1, 4],
 [0, 2, 3],
 [0, 2, 4],
 [0, 3, 4],
 [1, 2, 3],
 [1, 2, 4],
 [1, 3, 4],
 [2, 3, 4]]

【讨论】:

【参考方案6】:

以下是 C++ 中的一种迭代算法,它不使用 STL、递归或条件嵌套循环。这种方式速度更快,它不执行任何元素交换,也不会给堆栈带来递归负担,还可以通过将mallloc()free()printf() 替换为new 来轻松移植到ANSI C, deletestd::cout

如果要显示具有不同或更长字母的元素,请将*alphabet 参数更改为指向与"abcdefg" 不同的字符串。

void OutputArrayChar(unsigned int* ka, size_t n, const char *alphabet) 
    for (int i = 0; i < n; i++)
        std::cout << alphabet[ka[i]] << ",";
    std::cout << endl;

    

void GenCombinations(const unsigned int N, const unsigned int K, const char *alphabet) 
    unsigned int *ka = new unsigned int [K];  //dynamically allocate an array of UINTs
    unsigned int ki = K-1;                    //Point ki to the last elemet of the array
    ka[ki] = N-1;                             //Prime the last elemet of the array.
    
    while (true) 
        unsigned int tmp = ka[ki];  //Optimization to prevent reading ka[ki] repeatedly

        while (ki)                  //Fill to the left with consecutive descending values (blue squares)
            ka[--ki] = --tmp;
        OutputArrayChar(ka, K, alphabet);
    
        while (--ka[ki] == ki)     //Decrement and check if the resulting value equals the index (bright green squares)
            OutputArrayChar(ka, K, alphabet);
            if (++ki == K)       //Exit condition (all of the values in the array are flush to the left)
                delete[] ka;
                return;
                               
        
    

    

int main(int argc, char *argv[])

    GenCombinations(7, 4, "abcdefg");
    return 0;

重要提示:*alphabet 参数必须指向至少包含 N 字符的字符串。您还可以传递在其他地方定义的字符串的地址。

组合:“7选4”。

【讨论】:

【参考方案7】:

这里是一个简单且难以理解的递归 C++ 解决方案:

#include<vector>
using namespace std;

template<typename T>
void ksubsets(const vector<T>& arr, unsigned left, unsigned idx,
    vector<T>& lst, vector<vector<T>>& res)

    if (left < 1) 
        res.push_back(lst);
        return;
    
    for (unsigned i = idx; i < arr.size(); i++) 
        lst.push_back(arr[i]);
        ksubsets(arr, left - 1, i + 1, lst, res);
        lst.pop_back();
    


int main()

    vector<int> arr =  1, 2, 3, 4, 5 ;
    unsigned left = 3;
    vector<int> lst;
    vector<vector<int>> res;
    ksubsets<int>(arr, left, 0, lst, res);
    // now res has all the combinations

【讨论】:

【参考方案8】:

短 javascript 版本 (ES 5)

let combine = (list, n) =>
  n == 0 ?
    [[]] :
    list.flatMap((e, i) =>
      combine(
        list.slice(i + 1),
        n - 1
      ).map(c => [e].concat(c))
    );

let res = combine([1,2,3,4], 3);
res.forEach(e => console.log(e.join()));

【讨论】:

【参考方案9】:

我知道已经有很多答案了,但我想我会在 JavaScript 中添加我自己的个人贡献,它由两个函数组成 - 一个生成所有可能的不同 k 子集原始 n 元素集,并使用第一个函数生成原始 n 元素集的幂集。

这是两个函数的代码:

//Generate combination subsets from a base set of elements (passed as an array). This function should generate an
//array containing nCr elements, where nCr = n!/[r! (n-r)!].

//Arguments:

//[1] baseSet :     The base set to create the subsets from (e.g., ["a", "b", "c", "d", "e", "f"])
//[2] cnt :         The number of elements each subset is to contain (e.g., 3)

function MakeCombinationSubsets(baseSet, cnt)

    var bLen = baseSet.length;
    var indices = [];
    var subSet = [];
    var done = false;
    var result = [];        //Contains all the combination subsets generated
    var done = false;
    var i = 0;
    var idx = 0;
    var tmpIdx = 0;
    var incr = 0;
    var test = 0;
    var newIndex = 0;
    var inBounds = false;
    var tmpIndices = [];
    var checkBounds = false;

    //First, generate an array whose elements are indices into the base set ...

    for (i=0; i<cnt; i++)

        indices.push(i);

    //Now create a clone of this array, to be used in the loop itself ...

        tmpIndices = [];

        tmpIndices = tmpIndices.concat(indices);

    //Now initialise the loop ...

    idx = cnt - 1;      //point to the last element of the indices array
    incr = 0;
    done = false;
    while (!done)
    
    //Create the current subset ...

        subSet = [];    //Make sure we begin with a completely empty subset before continuing ...

        for (i=0; i<cnt; i++)

            subSet.push(baseSet[tmpIndices[i]]);    //Create the current subset, using items selected from the
                                                    //base set, using the indices array (which will change as we
                                                    //continue scanning) ...

    //Add the subset thus created to the result set ...

        result.push(subSet);

    //Now update the indices used to select the elements of the subset. At the start, idx will point to the
    //rightmost index in the indices array, but the moment that index moves out of bounds with respect to the
    //base set, attention will be shifted to the next left index.

        test = tmpIndices[idx] + 1;

        if (test >= bLen)
        
        //Here, we're about to move out of bounds with respect to the base set. We therefore need to scan back,
        //and update indices to the left of the current one. Find the leftmost index in the indices array that
        //isn't going to  move out of bounds with respect to the base set ...

            tmpIdx = idx - 1;
            incr = 1;

            inBounds = false;       //Assume at start that the index we're checking in the loop below is out of bounds
            checkBounds = true;

            while (checkBounds)
            
                if (tmpIdx < 0)
                
                    checkBounds = false;    //Exit immediately at this point
                
                else
                
                    newIndex = tmpIndices[tmpIdx] + 1;
                    test = newIndex + incr;

                    if (test >= bLen)
                    
                    //Here, incrementing the current selected index will take that index out of bounds, so
                    //we move on to the next index to the left ...

                        tmpIdx--;
                        incr++;
                    
                    else
                    
                    //Here, the index will remain in bounds if we increment it, so we
                    //exit the loop and signal that we're in bounds ...

                        inBounds = true;
                        checkBounds = false;

                    //End if/else
                    

                //End if 
                               
            //End while
            
    //At this point, if we'er still in bounds, then we continue generating subsets, but if not, we abort immediately.

            if (!inBounds)
                done = true;
            else
            
            //Here, we're still in bounds. We need to update the indices accordingly. NOTE: at this point, although a
            //left positioned index in the indices array may still be in bounds, incrementing it to generate indices to
            //the right may take those indices out of bounds. We therefore need to check this as we perform the index
            //updating of the indices array.

                tmpIndices[tmpIdx] = newIndex;

                inBounds = true;
                checking = true;
                i = tmpIdx + 1;

                while (checking)
                
                    test = tmpIndices[i - 1] + 1;   //Find out if incrementing the left adjacent index takes it out of bounds

                    if (test >= bLen)
                    
                        inBounds = false;           //If we move out of bounds, exit NOW ...
                        checking = false;
                    
                    else
                    
                        tmpIndices[i] = test;       //Otherwise, update the indices array ...

                        i++;                        //Now move on to the next index to the right in the indices array ...

                        checking = (i < cnt);       //And continue until we've exhausted all the indices array elements ...
                    //End if/else
                    
                //End while
                
                //At this point, if the above updating of the indices array has moved any of its elements out of bounds,
                //we abort subset construction from this point ...
                if (!inBounds)
                    done = true;
            //End if/else
            
        
        else
        
        //Here, the rightmost index under consideration isn't moving out of bounds with respect to the base set when
        //we increment it, so we simply increment and continue the loop ...
            tmpIndices[idx] = test;
        //End if
        
    //End while
    
    return(result);
//End function



function MakePowerSet(baseSet)

    var bLen = baseSet.length;
    var result = [];
    var i = 0;
    var partialSet = [];

    result.push([]);    //add the empty set to the power set

    for (i=1; i<bLen; i++)
    
        partialSet = MakeCombinationSubsets(baseSet, i);
        result = result.concat(partialSet);
    //End i loop
    
    //Now, finally, add the base set itself to the power set to make it complete ...

    partialSet = [];
    partialSet.push(baseSet);
    result = result.concat(partialSet);

    return(result);
    //End function

我以集合 ["a", "b", "c", "d", "e", "f"] 作为基集对此进行了测试,并运行代码以生成以下幂集:

[]
["a"]
["b"]
["c"]
["d"]
["e"]
["f"]
["a","b"]
["a","c"]
["a","d"]
["a","e"]
["a","f"]
["b","c"]
["b","d"]
["b","e"]
["b","f"]
["c","d"]
["c","e"]
["c","f"]
["d","e"]
["d","f"]
["e","f"]
["a","b","c"]
["a","b","d"]
["a","b","e"]
["a","b","f"]
["a","c","d"]
["a","c","e"]
["a","c","f"]
["a","d","e"]
["a","d","f"]
["a","e","f"]
["b","c","d"]
["b","c","e"]
["b","c","f"]
["b","d","e"]
["b","d","f"]
["b","e","f"]
["c","d","e"]
["c","d","f"]
["c","e","f"]
["d","e","f"]
["a","b","c","d"]
["a","b","c","e"]
["a","b","c","f"]
["a","b","d","e"]
["a","b","d","f"]
["a","b","e","f"]
["a","c","d","e"]
["a","c","d","f"]
["a","c","e","f"]
["a","d","e","f"]
["b","c","d","e"]
["b","c","d","f"]
["b","c","e","f"]
["b","d","e","f"]
["c","d","e","f"]
["a","b","c","d","e"]
["a","b","c","d","f"]
["a","b","c","e","f"]
["a","b","d","e","f"]
["a","c","d","e","f"]
["b","c","d","e","f"]
["a","b","c","d","e","f"]

只需“按原样”复制和粘贴这两个函数,您将具备提取 n 元素集的不同 k 子集所需的基础知识,生成如果您愿意,可以设置该 n 元素集。

我并不认为这很优雅,只是在经过大量测试后它可以工作(并在调试阶段变成蓝色:))。

【讨论】:

【参考方案10】:

简短的python代码,产生索引位置

def yield_combos(n,k):
    # n is set size, k is combo size

    i = 0
    a = [0]*k

    while i > -1:
        for j in range(i+1, k):
            a[j] = a[j-1]+1
        i=j
        yield a
        while a[i] == i + n - k:
            i -= 1
        a[i] += 1

【讨论】:

这非常优雅/高效并且效果很好。我刚刚将它翻译成 C++。【参考方案11】:

JavaScript,基于生成器的递归方法:

function *nCk(n,k)
  for(var i=n-1;i>=k-1;--i)
    if(k===1)
      yield [i];
    else
      for(var temp of nCk(i,k-1))
        temp.unshift(i);
        yield temp;
      


function test()
  try
    var n=parseInt(ninp.value);
    var k=parseInt(kinp.value);
    log.innerText="";
    var stop=Date.now()+1000;
    if(k>=1)
      for(var res of nCk(n,k))
        if(Date.now()<stop)
          log.innerText+=JSON.stringify(res)+" ";
        else
          log.innerText+="1 second passed, stopping here.";
          break;
        
  catch(ex)
n:<input id="ninp" oninput="test()">
&gt;= k:<input id="kinp" oninput="test()"> &gt;= 1
<div id="log"></div>

这种方式(递减iunshift())会以递减的顺序生成组合和组合内的元素,有点令人赏心悦目。 测试在 1 秒后停止,因此输入奇怪的数字相对安全。

【讨论】:

【参考方案12】:

按照Haskell代码同时计算组合数和组合,由于Haskell的惰性,你可以得到其中的一部分而不计算另一部分。

import Data.Semigroup
import Data.Monoid

data Comb = MkComb count :: Int, combinations :: [[Int]] deriving (Show, Eq, Ord)

instance Semigroup Comb where
    (MkComb c1 cs1) <> (MkComb c2 cs2) = MkComb (c1 + c2) (cs1 ++ cs2)

instance Monoid Comb where
    mempty = MkComb 0 []

addElem :: Comb -> Int -> Comb
addElem (MkComb c cs) x = MkComb c (map (x :) cs)

comb :: Int -> Int -> Comb
comb n k | n < 0 || k < 0 = error "error in `comb n k`, n and k should be natural number"
comb n k | k == 0 || k == n = MkComb 1 [(take k [k-1,k-2..0])]
comb n k | n < k = mempty
comb n k = comb (n-1) k <> (comb (n-1) (k-1) `addElem` (n-1))

它的工作原理是:

*Main> comb 0 1
MkComb count = 0, combinations = []

*Main> comb 0 0
MkComb count = 1, combinations = [[]]

*Main> comb 1 1
MkComb count = 1, combinations = [[0]]

*Main> comb 4 2
MkComb count = 6, combinations = [[1,0],[2,0],[2,1],[3,0],[3,1],[3,2]]

*Main> count (comb 10 5)
252

【讨论】:

【参考方案13】:

这是一种使用宏的 Lisp 方法。这适用于 Common Lisp,应该适用于其他 Lisp 方言。

下面的代码创建“n”个嵌套循环,并为列表lst 中的“n”个元素的每个组合执行任意代码块(存储在body 变量中)。变量var 指向一个包含用于循环的变量的列表。

(defmacro do-combinations ((var lst num) &body body)
  (loop with syms = (loop repeat num collect (gensym))
        for i on syms
        for k = `(loop for ,(car i) on (cdr ,(cadr i))
                         do (let ((,var (list ,@(reverse syms)))) (progn ,@body)))
                then `(loop for ,(car i) on ,(if (cadr i) `(cdr ,(cadr i)) lst) do ,k)
        finally (return k)))

让我们看看...

(macroexpand-1 '(do-combinations (p '(1 2 3 4 5 6 7) 4) (pprint (mapcar #'car p))))

(LOOP FOR #:G3217 ON '(1 2 3 4 5 6 7) DO
 (LOOP FOR #:G3216 ON (CDR #:G3217) DO
  (LOOP FOR #:G3215 ON (CDR #:G3216) DO
   (LOOP FOR #:G3214 ON (CDR #:G3215) DO
    (LET ((P (LIST #:G3217 #:G3216 #:G3215 #:G3214)))
     (PROGN (PPRINT (MAPCAR #'CAR P))))))))

(do-combinations (p '(1 2 3 4 5 6 7) 4) (pprint (mapcar #'car p)))

(1 2 3 4)
(1 2 3 5)
(1 2 3 6)
...

由于默认情况下不存储组合,因此将存储量保持在最低限度。选择body 代码而不是存储所有结果的可能性也提供了更大的灵活性。

【讨论】:

【参考方案14】:

又来了祖父 COBOL,这是一种备受诟病的语言。

让我们假设一个由 34 个元素组成的数组,每个元素 8 个字节(纯粹是任意选择)。这个想法是枚举所有可能的 4 元素组合并将它们加载到一个数组中。

我们使用 4 个索引,每个索引对应 4 个组中的每个位置

数组是这样处理的:

    idx1 = 1
    idx2 = 2
    idx3 = 3
    idx4 = 4

我们将 idx4 从 4 变化到末尾。对于每个 idx4,我们得到一个唯一的组合 四人一组。当 idx4 到达数组末尾时,我们将 idx3 加 1 并将 idx4 设置为 idx3+1。然后我们再次运行 idx4 到最后。我们以这种方式继续,分别增加 idx3、idx2 和 idx1,直到 idx1 的位置距离数组末尾小于 4。算法到此结束。

1          --- pos.1
2          --- pos 2
3          --- pos 3
4          --- pos 4
5
6
7
etc.

第一次迭代:

1234
1235
1236
1237
1245
1246
1247
1256
1257
1267
etc.

COBOL 示例:

01  DATA_ARAY.
    05  FILLER     PIC X(8)    VALUE  "VALUE_01".
    05  FILLER     PIC X(8)    VALUE  "VALUE_02".
  etc.
01  ARAY_DATA    OCCURS 34.
    05  ARAY_ITEM       PIC X(8).

01  OUTPUT_ARAY   OCCURS  50000   PIC X(32).

01   MAX_NUM   PIC 99 COMP VALUE 34.

01  INDEXXES  COMP.
    05  IDX1            PIC 99.
    05  IDX2            PIC 99.
    05  IDX3            PIC 99.
    05  IDX4            PIC 99.
    05  OUT_IDX   PIC 9(9).

01  WHERE_TO_STOP_SEARCH          PIC 99  COMP.

* Stop the search when IDX1 is on the third last array element:

COMPUTE WHERE_TO_STOP_SEARCH = MAX_VALUE - 3     

MOVE 1 TO IDX1

PERFORM UNTIL IDX1 > WHERE_TO_STOP_SEARCH
   COMPUTE IDX2 = IDX1 + 1
   PERFORM UNTIL IDX2 > MAX_NUM
      COMPUTE IDX3 = IDX2 + 1
      PERFORM UNTIL IDX3 > MAX_NUM
         COMPUTE IDX4 = IDX3 + 1
         PERFORM UNTIL IDX4 > MAX_NUM
            ADD 1 TO OUT_IDX
            STRING  ARAY_ITEM(IDX1)
                    ARAY_ITEM(IDX2)
                    ARAY_ITEM(IDX3)
                    ARAY_ITEM(IDX4)
                    INTO OUTPUT_ARAY(OUT_IDX)
            ADD 1 TO IDX4
         END-PERFORM
         ADD 1 TO IDX3
      END-PERFORM
      ADD 1 TO IDX2
   END_PERFORM
   ADD 1 TO IDX1
END-PERFORM.

【讨论】:

但是为什么【参考方案15】:

这是一个简单的JS解决方案:

function getAllCombinations(n, k, f1) 
	indexes = Array(k);
  for (let i =0; i< k; i++) 
  	indexes[i] = i;
  
  var total = 1;
  f1(indexes);
  while (indexes[0] !== n-k) 
  	total++;
		getNext(n, indexes);
    f1(indexes);
  
  return total;


function getNext(n, vec) 
	const k = vec.length;
  vec[k-1]++;
	for (var i=0; i<k; i++) 
  	var currentIndex = k-i-1;
    if (vec[currentIndex] === n - i) 
	  	var nextIndex = k-i-2;
      vec[nextIndex]++;
      vec[currentIndex] = vec[nextIndex] + 1;
    
  

	for (var i=1; i<k; i++) 
    if (vec[i] === n - (k-i - 1)) 
      vec[i] = vec[i-1] + 1;
    
  
	return vec;
 



let start = new Date();
let result = getAllCombinations(10, 3, indexes => console.log(indexes)); 
let runTime = new Date() - start; 

console.log(
result, runTime
);

【讨论】:

【参考方案16】:

MetaTrader MQL4 的快速组合实现为迭代器对象。

代码很容易理解。

我对很多算法进行了基准测试,这个真的非常快 - 比大多数 next_combination() 函数快大约 3 倍。

class CombinationsIterator

private:
	int input_array[];  // 1 2 3 4 5
	int index_array[];  // i j k
	int m_elements;     // N
	int m_indices;      // K

public:
	CombinationsIterator(int &src_data[], int k)
	
		m_indices = k;
		m_elements = ArraySize(src_data);
		ArrayCopy(input_array, src_data);
		ArrayResize(index_array, m_indices);

		// create initial combination (0..k-1)
		for (int i = 0; i < m_indices; i++)
		
			index_array[i] = i;
		
	

	// https://***.com/questions/5076695
	// bool next_combination(int &item[], int k, int N)
	bool advance()
	
		int N = m_elements;
		for (int i = m_indices - 1; i >= 0; --i)
		
			if (index_array[i] < --N)
			
				++index_array[i];
				for (int j = i + 1; j < m_indices; ++j)
				
					index_array[j] = index_array[j - 1] + 1;
				
				return true;
			
		
		return false;
	

	void getItems(int &items[])
	
		// fill items[] from input array
		for (int i = 0; i < m_indices; i++)
		
			items[i] = input_array[index_array[i]];
		
	
;

一个测试上述迭代器类的驱动程序:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
// driver program to test above class

#define N 5
#define K 3

void OnStart()

	int myset[N] = 1, 2, 3, 4, 5;
	int items[K];

	CombinationsIterator comboIt(myset, K);

	do
	
		comboIt.getItems(items);

		printf("%s", ArrayToString(items));

	 while (comboIt.advance());

Output:
1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5

【讨论】:

【参考方案17】:

我为 C++ 中的组合创建了一个通用类。 它是这样使用的。

char ar[] = "0ABCDEFGH";
nCr ncr(8, 3);
while(ncr.next()) 
    for(int i=0; i<ncr.size(); i++) cout << ar[ncr[i]];
    cout << ' ';

我的库 ncr[i] 从 1 返回,而不是从 0 返回。 这就是数组中有 0 的原因。 如果要考虑顺序,只需将 nCr 类更改为 nPr。 用法相同。

结果

ABC ABD 安倍 ABF ABG ABH ACD 高手 ACF ACG ACH ADE ADF 助理总干事 ADH AEF AEG AEH AFG AFH AGH BCD 公元前 BCF 卡介苗 生物安全信息交易所所 溴化二苯醚 BDF BDG BDH BEF 求 BEH 高炉 高炉 生长激素 CDE CDF *** CDH CEF CEG CEH CFG CFH CGH 国防军 度 DEH 东风 东风 生长激素 EFG EFH 生长激素 生长激素

这里是头文件。

#pragma once
#include <exception>

class NRexception : public std::exception

public:
    virtual const char* what() const throw() 
        return "Combination : N, R should be positive integer!!";
    
;

class Combination

public:
    Combination(int n, int r);
    virtual ~Combination()  delete [] ar;
    int& operator[](unsigned i) return ar[i];
    bool next();
    int size() return r;
    static int factorial(int n);

protected:
    int* ar;
    int n, r;
;

class nCr : public Combination

public: 
    nCr(int n, int r);
    bool next();
    int count() const;
;

class nTr : public Combination

public:
    nTr(int n, int r);
    bool next();
    int count() const;
;

class nHr : public nTr

public:
    nHr(int n, int r) : nTr(n,r) 
    bool next();
    int count() const;
;

class nPr : public Combination

public:
    nPr(int n, int r);
    virtual ~nPr() delete [] on;
    bool next();
    void rewind();
    int count() const;

private:
    bool* on;
    void inc_ar(int i);
;

以及实现。

#include "combi.h"
#include <set>
#include<cmath>

Combination::Combination(int n, int r)

    //if(n < 1 || r < 1) throw NRexception();
    ar = new int[r];
    this->n = n;
    this->r = r;


int Combination::factorial(int n) 

    return n == 1 ? n : n * factorial(n-1);


int nPr::count() const

    return factorial(n)/factorial(n-r);


int nCr::count() const

    return factorial(n)/factorial(n-r)/factorial(r);


int nTr::count() const

    return pow(n, r);


int nHr::count() const

    return factorial(n+r-1)/factorial(n-1)/factorial(r);


nCr::nCr(int n, int r) : Combination(n, r)

    if(r == 0) return;
    for(int i=0; i<r-1; i++) ar[i] = i + 1;
    ar[r-1] = r-1;


nTr::nTr(int n, int r) : Combination(n, r)

    for(int i=0; i<r-1; i++) ar[i] = 1;
    ar[r-1] = 0;


bool nCr::next()

    if(r == 0) return false;
    ar[r-1]++;
    int i = r-1;
    while(ar[i] == n-r+2+i) 
        if(--i == -1) return false;
        ar[i]++;
    
    while(i < r-1) ar[i+1] = ar[i++] + 1;
    return true;


bool nTr::next()

    ar[r-1]++;
    int i = r-1;
    while(ar[i] == n+1) 
        ar[i] = 1;
        if(--i == -1) return false;
        ar[i]++;
    
    return true;


bool nHr::next()

    ar[r-1]++;
    int i = r-1;
    while(ar[i] == n+1) 
        if(--i == -1) return false;
        ar[i]++;
    
    while(i < r-1) ar[i+1] = ar[i++];
    return true;


nPr::nPr(int n, int r) : Combination(n, r)

    on = new bool[n+2];
    for(int i=0; i<n+2; i++) on[i] = false;
    for(int i=0; i<r; i++) 
        ar[i] = i + 1;
        on[i] = true;
    
    ar[r-1] = 0;


void nPr::rewind()

    for(int i=0; i<r; i++) 
        ar[i] = i + 1;
        on[i] = true;
    
    ar[r-1] = 0;


bool nPr::next()
   
    inc_ar(r-1);

    int i = r-1;
    while(ar[i] == n+1) 
        if(--i == -1) return false;
        inc_ar(i);
    
    while(i < r-1) 
        ar[++i] = 0;
        inc_ar(i);
    
    return true;


void nPr::inc_ar(int i)

    on[ar[i]] = false;
    while(on[++ar[i]]);
    if(ar[i] != n+1) on[ar[i]] = true;

【讨论】:

【参考方案18】:

我们可以使用位的概念来做到这一点。让我们有一个字符串“abc”,我们想要所有长度为 2 的元素组合(即“ab”、“ac”、“bc”。)

我们可以找到从 1 到 2^n(不包括)的数字中的设置位。这里是 1 到 7,只要我们设置了 bits = 2,我们就可以从字符串中打印出对应的值。

例如:

1 - 001 2-010 3 - 011 -> print ab (str[0] , str[1]) 4 - 100 5 - 101 -> print ac (str[0] , str[2]) 6 - 110 -> print ab (str[1] , str[2]) 7 - 111.

代码示例:

public class StringCombinationK    
    static void combk(String s , int k)
        int n = s.length();
        int num = 1<<n;
        int j=0;
        int count=0;

        for(int i=0;i<num;i++)
            if (countSet(i)==k)
                setBits(i,j,s);
                count++;
                System.out.println();
            
        

        System.out.println(count);
    

    static void setBits(int i,int j,String s) // print the corresponding string value,j represent the index of set bit
        if(i==0)
            return;
        

        if(i%2==1)
            System.out.print(s.charAt(j));                  
        

        setBits(i/2,j+1,s);
    

    static int countSet(int i) //count number of set bits
        if( i==0)
            return 0;
        

        return (i%2==0? 0:1) + countSet(i/2);
    

    public static void main(String[] arhs)
        String s = "abcdefgh";
        int k=3;
        combk(s,k);
    

【讨论】:

【参考方案19】:

假设您的字母数组如下所示:“ABCDEFGH”。您有三个索引 (i, j, k) 指示您要为当前单词使用哪些字母,您可以从以下内容开始:

A B C D E F G H ^ ^ ^ 我 j k

首先你改变 k,所以下一步看起来像这样:

A B C D E F G H ^ ^ ^ 我 j k

如果你到达终点,你继续改变 j,然后再改变 k。

A B C D E F G H ^ ^ ^ 我 j k A B C D E F G H ^ ^ ^ 我 j k

一旦你 j 达到 G,你也开始改变 i。

A B C D E F G H ^ ^ ^ 我 j k A B C D E F G H ^ ^ ^ 我 j k ...
function initializePointers($cnt) 
    $pointers = [];

    for($i=0; $i<$cnt; $i++) 
        $pointers[] = $i;
    

    return $pointers;     


function incrementPointers(&$pointers, &$arrLength) 
    for($i=0; $i<count($pointers); $i++) 
        $currentPointerIndex = count($pointers) - $i - 1;
        $currentPointer = $pointers[$currentPointerIndex];

        if($currentPointer < $arrLength - $i - 1) 
           ++$pointers[$currentPointerIndex];

           for($j=1; ($currentPointerIndex+$j)<count($pointers); $j++) 
                $pointers[$currentPointerIndex+$j] = $pointers[$currentPointerIndex]+$j;
           

           return true;
        
    

    return false;


function getDataByPointers(&$arr, &$pointers) 
    $data = [];

    for($i=0; $i<count($pointers); $i++) 
        $data[] = $arr[$pointers[$i]];
    

    return $data;


function getCombinations($arr, $cnt)

    $len = count($arr);
    $result = [];
    $pointers = initializePointers($cnt);

    do 
        $result[] = getDataByPointers($arr, $pointers);
     while(incrementPointers($pointers, count($arr)));

    return $result;


$result = getCombinations([0, 1, 2, 3, 4, 5], 3);
print_r($result);

基于https://***.com/a/127898/2628125,但更抽象,适用于任何大小的指针。

【讨论】:

这是什么可怕的语言?重击? php,但是这里的语言无关紧要,算法很重要 我很高兴我拒绝学习这门语言。 2018 年不应该存在解释器/编译器需要帮助识别变量的语言。【参考方案20】:

基于java解决方案从n(二项式系数)返回k个元素的所有组合的简短php算法:

$array = array(1,2,3,4,5);

$array_result = NULL;

$array_general = NULL;

function combinations($array, $len, $start_position, $result_array, $result_len, &$general_array)

    if($len == 0)
    
        $general_array[] = $result_array;
        return;
    

    for ($i = $start_position; $i <= count($array) - $len; $i++)
    
        $result_array[$result_len - $len] = $array[$i];
        combinations($array, $len-1, $i+1, $result_array, $result_len, $general_array);
    
 

combinations($array, 3, 0, $array_result, 3, $array_general);

echo "<pre>";
print_r($array_general);
echo "</pre>";

相同的解决方案,但在 javascript 中:

var newArray = [1, 2, 3, 4, 5];
var arrayResult = [];
var arrayGeneral = [];

function combinations(newArray, len, startPosition, resultArray, resultLen, arrayGeneral) 
    if(len === 0) 
        var tempArray = [];
        resultArray.forEach(value => tempArray.push(value));
        arrayGeneral.push(tempArray);
        return;
    
    for (var i = startPosition; i <= newArray.length - len; i++) 
        resultArray[resultLen - len] = newArray[i];
        combinations(newArray, len-1, i+1, resultArray, resultLen, arrayGeneral);
    
 

combinations(newArray, 3, 0, arrayResult, 3, arrayGeneral);

console.log(arrayGeneral);

【讨论】:

对不起$array, $len, $start_position, $result_array, $result_len, &amp;$general_array 的变量是什么? 组合函数是递归函数。它对自身进行迭代,直到 $len 等于 0。它使用 $array、$start_position、$result_array、$result_len 进行本地计算,并使用 &$general_array,这是一个接受通过引用传递的变量的参数,因此它可以修改变量 $array_general 并保持不同调用之间的值。希望这会有所帮助 如果您想知道如何使用它,那么 $array 包含来自 wich 的值以进行组合。 $len 是组合的长度,$start_position 表示开始获取元素的位置。如果您设置 $start_position = 1,如果 $start_position = 0,它将仅计算 2,3,4,5 而不是 1,2,3,4,5。$result_array 用于每次调用的临时计算。 $result_len 必须与 $len 具有相同的值。&general_array 保存结果或组合。【参考方案21】:

算法:

从 1 数到 2^n。 将每个数字转换为其二进制表示。 根据位置将每个“on”位转换为集合中的元素。

在 C# 中:

void Main()

    var set = new [] "A", "B", "C", "D" ; //, "E", "F", "G", "H", "I", "J" ;

    var kElement = 2;

    for(var i = 1; i < Math.Pow(2, set.Length); i++) 
        var result = Convert.ToString(i, 2).PadLeft(set.Length, '0');
        var cnt = Regex.Matches(Regex.Escape(result),  "1").Count; 
        if (cnt == kElement) 
            for(int j = 0; j < set.Length; j++)
                if ( Char.GetNumericValue(result[j]) == 1)
                    Console.Write(set[j]);
            Console.WriteLine();
        
    

为什么会起作用?

n 元素集合的子集和 n 位序列的子集之间存在双射。

这意味着我们可以通过计算序列来计算出有多少子集。

例如,下面的四个元素集合可以用 0,1 X 0, 1 X 0, 1 X 0, 1(或 2^4)个不同的序列来表示。

所以 - 我们要做的就是从 1 数到 2^n 以找到所有组合。(我们忽略空集。)接下来,将数字转换为它们的二进制表示。然后用你的集合中的元素替换“on”位。

如果您只想要 k 个元素的结果,则仅在 k 位为“on”时打印。

(如果您想要所有子集而不是 k 个长度的子集,请删除 cnt/kElement 部分。)

(有关证明,请参阅 MIT 免费课件数学计算机科学,Lehman 等人,第 11.2.2 节。https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics-for-computer-science-fall-2010/readings/)

【讨论】:

【参考方案22】:

另一种 C# 解决方案:

 static List<List<T>> GetCombinations<T>(List<T> originalItems, int combinationLength)
    
        if (combinationLength < 1)
        
            return null;
        

        return CreateCombinations<T>(new List<T>(), 0, combinationLength, originalItems);
    

 static List<List<T>> CreateCombinations<T>(List<T> initialCombination, int startIndex, int length, List<T> originalItems)
    
        List<List<T>> combinations = new List<List<T>>();
        for (int i = startIndex; i < originalItems.Count - length + 1; i++)
        
            List<T> newCombination = new List<T>(initialCombination);
            newCombination.Add(originalItems[i]);
            if (length > 1)
            
                List<List<T>> newCombinations = CreateCombinations(newCombination, i + 1, length - 1, originalItems);
                combinations.AddRange(newCombinations);
            
            else
            
                combinations.Add(newCombination);
            
        

        return combinations;
    

使用示例:

   List<char> initialArray = new List<char>()  'a','b','c','d';
   int combinationLength = 3;
   List<List<char>> combinations = GetCombinations(initialArray, combinationLength);

【讨论】:

【参考方案23】:

简单但缓慢的 C++ 回溯算法。

#include <iostream>

void backtrack(int* numbers, int n, int k, int i, int s)

    if (i == k)
    
        for (int j = 0; j < k; ++j)
        
            std::cout << numbers[j];
        
        std::cout << std::endl;

        return;
    

    if (s > n)
    
        return;
    

    numbers[i] = s;
    backtrack(numbers, n, k, i + 1, s + 1);
    backtrack(numbers, n, k, i, s + 1);


int main(int argc, char* argv[])

    int n = 5;
    int k = 3;

    int* numbers = new int[k];

    backtrack(numbers, n, k, 0, 1);

    delete[] numbers;

    return 0;

【讨论】:

【参考方案24】:

在 C++ 中,以下例程将生成范围 [first,last) 之间的长度距离 (first,k) 的所有组合:

#include <algorithm>

template <typename Iterator>
bool next_combination(const Iterator first, Iterator k, const Iterator last)

   /* Credits: Mark Nelson http://marknelson.us */
   if ((first == last) || (first == k) || (last == k))
      return false;
   Iterator i1 = first;
   Iterator i2 = last;
   ++i1;
   if (last == i1)
      return false;
   i1 = last;
   --i1;
   i1 = k;
   --i2;
   while (first != i1)
   
      if (*--i1 < *i2)
      
         Iterator j = k;
         while (!(*i1 < *j)) ++j;
         std::iter_swap(i1,j);
         ++i1;
         ++j;
         i2 = k;
         std::rotate(i1,j,last);
         while (last != j)
         
            ++j;
            ++i2;
         
         std::rotate(k,i2,last);
         return true;
      
   
   std::rotate(first,k,last);
   return false;

可以这样使用:

#include <string>
#include <iostream>

int main()

    std::string s = "12345";
    std::size_t comb_size = 3;
    do
    
        std::cout << std::string(s.begin(), s.begin() + comb_size) << std::endl;
     while (next_combination(s.begin(), s.begin() + comb_size, s.end()));

    return 0;

这将打印以下内容:

123
124
125
134
135
145
234
235
245
345

【讨论】:

在这种情况下什么是开始,什么是结束?如果传递给这个函数的所有变量都是按值传递的,它怎么能真正返回一些东西呢? @Sergej Andrejev:将beingbegin 替换为s.begin(),将end 替换为s.end()。该代码紧跟 STL 的 next_permutation 算法,更详细地描述了 here。 发生了什么? i1 = 最后一个; --i1; i1 = k;【参考方案25】:

我想介绍我的解决方案。 next 中没有递归调用,也没有嵌套循环。 代码的核心是next()方法。

public class Combinations 
    final int pos[];
    final List<Object> set;

    public Combinations(List<?> l, int k) 
        pos = new int[k];
        set=new ArrayList<Object>(l);
        reset();
    
    public void reset() 
        for (int i=0; i < pos.length; ++i) pos[i]=i;
    
    public boolean next() 
        int i = pos.length-1;
        for (int maxpos = set.size()-1; pos[i] >= maxpos; --maxpos) 
            if (i==0) return false;
            --i;
        
        ++pos[i];
        while (++i < pos.length)
            pos[i]=pos[i-1]+1;
        return true;
    

    public void getSelection(List<?> l) 
        @SuppressWarnings("unchecked")
        List<Object> ll = (List<Object>)l;
        if (ll.size()!=pos.length) 
            ll.clear();
            for (int i=0; i < pos.length; ++i)
                ll.add(set.get(pos[i]));
        
        else 
            for (int i=0; i < pos.length; ++i)
                ll.set(i, set.get(pos[i]));
        
    

及用法示例:

static void main(String[] args) 
    List<Character> l = new ArrayList<Character>();
    for (int i=0; i < 32; ++i) l.add((char)('a'+i));
    Combinations comb = new Combinations(l,5);
    int n=0;
    do 
        ++n;
        comb.getSelection(l);
        //Log.debug("%d: %s", n, l.toString());
     while (comb.next());
    Log.debug("num = %d", n);

【讨论】:

【参考方案26】:

不需要集合操作。这个问题几乎与循环 K 嵌套循环相同,但您必须小心索引和边界(忽略 Java 和 OOP 的东西):

 public class CombinationsGen 
    private final int n;
    private final int k;
    private int[] buf;

    public CombinationsGen(int n, int k) 
        this.n = n;
        this.k = k;
    

    public void combine(Consumer<int[]> consumer) 
        buf = new int[k];
        rec(0, 0, consumer);
    

    private void rec(int index, int next, Consumer<int[]> consumer) 
        int max = n - index;

        if (index == k - 1) 
            for (int i = 0; i < max && next < n; i++) 
                buf[index] = next;
                next++;
                consumer.accept(buf);
            
         else 
            for (int i = 0; i < max && next + index < n; i++) 
                buf[index] = next;
                next++;
                rec(index + 1, next, consumer);
            
        
    

这样使用:

 CombinationsGen gen = new CombinationsGen(5, 2);

 AtomicInteger total = new AtomicInteger();
 gen.combine(arr -> 
     System.out.println(Arrays.toString(arr));
     total.incrementAndGet();
 );
 System.out.println(total);

获得预期结果:

[0, 1]
[0, 2]
[0, 3]
[0, 4]
[1, 2]
[1, 3]
[1, 4]
[2, 3]
[2, 4]
[3, 4]
10

最后,将索引映射到您可能拥有的任何数据集。

【讨论】:

【参考方案27】:

我正在为 PHP 寻找类似的解决方案并遇到以下问题

class Combinations implements Iterator

    protected $c = null;
    protected $s = null;
    protected $n = 0;
    protected $k = 0;
    protected $pos = 0;

    function __construct($s, $k) 
        if(is_array($s)) 
            $this->s = array_values($s);
            $this->n = count($this->s);
         else 
            $this->s = (string) $s;
            $this->n = strlen($this->s);
        
        $this->k = $k;
        $this->rewind();
    
    function key() 
        return $this->pos;
    
    function current() 
        $r = array();
        for($i = 0; $i < $this->k; $i++)
            $r[] = $this->s[$this->c[$i]];
        return is_array($this->s) ? $r : implode('', $r);
    
    function next() 
        if($this->_next())
            $this->pos++;
        else
            $this->pos = -1;
    
    function rewind() 
        $this->c = range(0, $this->k);
        $this->pos = 0;
    
    function valid() 
        return $this->pos >= 0;
    

    protected function _next() 
        $i = $this->k - 1;
        while ($i >= 0 && $this->c[$i] == $this->n - $this->k + $i)
            $i--;
        if($i < 0)
            return false;
        $this->c[$i]++;
        while($i++ < $this->k - 1)
            $this->c[$i] = $this->c[$i - 1] + 1;
        return true;
    



foreach(new Combinations("1234567", 5) as $substring)
    echo $substring, ' ';

source

我不确定这个课程的效率如何,但我只是将它用于播种机。

【讨论】:

【参考方案28】:

递归,一个非常简单的答案,combo,在 Free Pascal 中。

    procedure combinata (n, k :integer; producer :oneintproc);

        procedure combo (ndx, nbr, len, lnd :integer);
        begin
            for nbr := nbr to len do begin
                productarray[ndx] := nbr;
                if len < lnd then
                    combo(ndx+1,nbr+1,len+1,lnd)
                else
                    producer(k);
            end;
        end;

    begin
        combo (0, 0, n-k, n-1);
    end;

“生产者”处理为每个组合制作的产品数组。

【讨论】:

【参考方案29】:

My implementation in c/c++

#include <unistd.h>
#include <stdio.h>
#include <iconv.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char **argv)

    int opt = -1, min_len = 0, max_len = 0;
    char ofile[256], fchar[2], tchar[2];
    ofile[0] = 0;
    fchar[0] = 0;
    tchar[0] = 0;
    while((opt = getopt(argc, argv, "o:f:t:l:L:")) != -1)
    
            switch(opt)
            
                    case 'o':
                    strncpy(ofile, optarg, 255);
                    break;
                    case 'f':
                    strncpy(fchar, optarg, 1);
                    break;
                    case 't':
                    strncpy(tchar, optarg, 1);
                    break;
                    case 'l':
                    min_len = atoi(optarg);
                    break;
                    case 'L':
                    max_len = atoi(optarg);
                    break;
                    default:
                    printf("usage: %s -oftlL\n\t-o output file\n\t-f from char\n\t-t to char\n\t-l min seq len\n\t-L max seq len", argv[0]);
            
    
if(max_len < 1)

    printf("error, length must be more than 0\n");
    return 1;

if(min_len > max_len)

    printf("error, max length must be greater or equal min_length\n");
    return 1;

if((int)fchar[0] > (int)tchar[0])

    printf("error, invalid range specified\n");
    return 1;

FILE *out = fopen(ofile, "w");
if(!out)

    printf("failed to open input file with error: %s\n", strerror(errno));
    return 1;

int cur_len = min_len;
while(cur_len <= max_len)

    char buf[cur_len];
    for(int i = 0; i < cur_len; i++)
        buf[i] = fchar[0];
    fwrite(buf, cur_len, 1, out);
    fwrite("\n", 1, 1, out);
    while(buf[0] != (tchar[0]+1))
    
        while(buf[cur_len-1] < tchar[0])
        
            (int)buf[cur_len-1]++;
            fwrite(buf, cur_len, 1, out);
            fwrite("\n", 1, 1, out);
        
        if(cur_len < 2)
            break;
        if(buf[0] == tchar[0])
        
            bool stop = true;
            for(int i = 1; i < cur_len; i++)
            
                if(buf[i] != tchar[0])
                
                    stop = false;
                    break;
                
            
            if(stop)
                break;
        
        int u = cur_len-2;
        for(; u>=0 && buf[u] >= tchar[0]; u--)
            ;
        (int)buf[u]++;
        for(int i = u+1; i < cur_len; i++)
            buf[i] = fchar[0];
        fwrite(buf, cur_len, 1, out);
        fwrite("\n", 1, 1, out);
    
    cur_len++;

fclose(out);
return 0;

这是我在 C++ 中的实现,它将所有组合写入指定的文件,但可以更改行为,我制作了各种字典,它接受最小和最大长度和字符范围,目前仅支持 ansi,这对我来说已经足够了需要

【讨论】:

【参考方案30】:

这是一种从随机长度字符串中为您提供指定大小的所有组合的方法。类似于 quinmars 的解决方案,但适用于不同的输入和 k。

可以将代码更改为环绕,即输入 'abcd' w k=3 中的 'dab'。

public void run(String data, int howMany)
    choose(data, howMany, new StringBuffer(), 0);



//n choose k
private void choose(String data, int k, StringBuffer result, int startIndex)
    if (result.length()==k)
        System.out.println(result.toString());
        return;
    

    for (int i=startIndex; i<data.length(); i++)
        result.append(data.charAt(i));
        choose(data,k,result, i+1);
        result.setLength(result.length()-1);
    

“abcde”的输出:

abc abd abe acd ace ade bcd bce bde cde

【讨论】:

以上是关于从 n 返回 k 个元素的所有组合的算法的主要内容,如果未能解决你的问题,请参考以下文章

java题目好难啊?求大侠解答,谢谢、、 设计算法求解从集合1...n中选取k(k<=n)个元素的所有组合。

给出两个整数n和k,返回从1到n中取k个数字的所有可能的组合

Leetcode 77.组合

从 n 个排序数组中找到第 k 个最小的数

该算法查找所有组合的时间复杂度是多少?

从 C 中的递归函数中枚举并返回从二维数组中的 n 项中选择 k 的所有组合