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


571

我想编写一个函数,该函数采用字母数组作为参数,并选择多个字母。

假设您提供8个字母的数组,并希望从中选择3个字母。然后您将获得:

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

返回由3个字母组成的数组(或单词)。


2
对编程语言有偏好吗?
乔纳森·陈

7
您想如何处理重复的字母?
wcm

没有语言偏爱,我要用ruby编写代码,但是对使用哪种算法有一个总体的了解。可能存在两个相同值的字母,但两次不完全相同。
Fredrik


:在PHP中,下面应该做的伎俩 stackoverflow.com/questions/4279722/...
凯末尔·达格

Answers:


412

《计算机编程艺术》第4卷:第3册中的大量内容可能比我的描述更适合您的特定情况。

格雷码

您会遇到的一个问题当然是记忆力,而且很快,您的集合中会有20个元素出现问题-20 C 3 =1140。而且,如果要遍历集合,最好使用修改后的灰色代码算法,因此您不必将所有代码都保存在内存中。这些将根据前一个生成下一个组合,并避免重复。其中有许多用于不同用途。我们是否要最大化连续组合之间的差异?最小化?等等。

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

  1. 某些汉密尔顿路径和最小变化算法
  2. 相邻立交组合生成算法

以下是涉及该主题的其他一些论文:

  1. Eades,Hickey,Read相邻互换组合生成算法(PDF,带有Pascal中的代码)的有效实现
  2. 组合发电机
  3. 组合格雷码调查(PostScript)
  4. 格雷码算法

蔡斯的小提琴(算法)

菲利普·蔡斯(Phillip J Chase),“ 算法382:N个对象中M个的组合 ”(1970年)

在C算法 ...

词典顺序的组合索引(Buckles算法515)

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

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

我所描述的方法似乎是一种解构,从设置到索引,我们需要做相反的事情-这要复杂得多。这是扣具解决问题的方式。我写了一些C来计算它们,并做了些微改动–我使用集合的索引而不是数字范围来表示集合,因此我们始终从0 ... n开始。注意:

  1. 由于组合是无序的,因此{1,3,2} = {1,2,3}-我们将它们排序为词典顺序。
  2. 此方法具有隐式0,以开始第一个差异的集合。

字典顺序组合索引(McCaffrey)

还有另一种方法:它的概念更易于掌握和编程,但是没有优化Buckles。幸运的是,它也不会产生重复的组合:

x_k...x_1 in N最大化的集合i = C(x_1,k) + C(x_2,k-1) + ... + C(x_k,1),在哪里C(n,r) = {n choose r}

例如: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(n C k)。内存消耗受约束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

1
如果集合包含相等的元素,这会产生重复的组合吗?
Thomas Ahle 2010年

2
是的,托马斯。它与数组中的数据无关。如果这是理想的效果,您总是可以先过滤掉重复项,或者选择其他算法。
nlucaroni

19
很棒的答案。您能否提供每种算法的运行时摘要和内存分析?
uncaught_exceptions 2012年

2
很好的答案。20C3是1140,感叹号在这里令人困惑,因为它看起来像阶乘,并且阶乘确实输入用于查找组合的公式。因此,我将删除感叹号。
CashCow

3
令人惊讶的是,许多引用都在付费专栏后面。是否可以包含非付费专线链接或包含来自源的报价片段?
Terrance

195

在C#中:

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
{
  return k == 0 ? new[] { new T[0] } :
    elements.SelectMany((e, i) =>
      elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] {e}).Concat(c)));
}

用法:

var result = Combinations(new[] { 1, 2, 3, 4, 5 }, 3);

结果:

123
124
125
134
135
145
234
235
245
345

2
该解决方案适用于“小型”设备,但对于大型设备,它会占用一些内存。
Artur Carvalho

1
没有直接关系,但是代码非常有趣/可读,我想知道哪个C#版本具有此构造/方法?(我只使用了c#v1.0,而没有那么多)。
LBarret 2012年

绝对优雅,但IEnumerable将会被列举很多次。如果这得到了一些重要行动的支持……
Drew Noakes 2012年

2
由于它是一种扩展方法,因此您的用法行可以读取:var result = new[] { 1, 2, 3, 4, 5 }.Combinations(3);
Dave Cousineau,2013年

1
你能否提供使用递归循环此查询的确切非LINQ版本
irfandar

81

简短的Java解决方案:

import java.util.Arrays;

public class Combination {
    public static void main(String[] args){
        String[] arr = {"A","B","C","D","E","F"};
        combinations2(arr, 3, 0, new String[3]);
    }

    static void combinations2(String[] arr, int len, int startPosition, String[] result){
        if (len == 0){
            System.out.println(Arrays.toString(result));
            return;
        }       
        for (int i = startPosition; i <= arr.length-len; i++){
            result[result.length - len] = arr[i];
            combinations2(arr, len-1, i+1, result);
        }
    }       
}

结果将是

[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]

这似乎是O(n ^ 3)对吗?我想知道是否有更快的算法可以做到这一点。
LZH 2014年

我正在与20个选择10个一起工作,这对我来说似乎足够快(不到1秒)
demongolem 2015年

4
@NanoHead你错了。这是无重复的组合。而你的情况是重复的。
开膛手杰克

这段代码应该更容易在网络上找到……这正是我想要的!
Manuel S.

我刚刚测试了这个以及其他7个Java实现-到目前为止,这是最快的。第二快是慢了一个数量级。
斯图尔特

77

我可以提出我的递归Python解决方案吗?

def choose_iter(elements, length):
    for i in xrange(len(elements)):
        if length == 1:
            yield (elements[i],)
        else:
            for next in choose_iter(elements[i+1:len(elements)], length-1):
                yield (elements[i],) + next
def choose(l, k):
    return list(choose_iter(l, k))

用法示例:

>>> len(list(choose_iter("abcdefgh",3)))
56

我喜欢它的简单性。


16
len(tuple(itertools.combinations('abcdefgh',3)))用更少的代码就能在Python中实现相同的目的。
hgus1294

59
@ hgus1294是的,但这将是作弊。Op要求一种算法,而不是与特定编程语言绑定的“魔术”方法。
MestreLion,2012年

1
严格来讲,第一个循环范围不应该是for i in xrange(len(elements) - length + 1):吗?在python中没关系,因为切片索引的移出可以很好地处理,但这是正确的算法。
斯蒂芬·多尔伯格

62

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

ABCDEFGH
^ ^ ^
约克

首先,您改变k,因此下一步看起来像这样:

ABCDEFGH
^ ^ ^
约克

如果到达末尾,则继续并依次改变j和k。

ABCDEFGH
^ ^ ^
约克

ABCDEFGH
^ ^ ^
约克

j达到G后,您也开始改变i。

ABCDEFGH
  ^ ^ ^
  约克

ABCDEFGH
  ^ ^ ^
  约克
...

用代码写的看起来像这样

void print_combinations(const char *string)
{
    int i, j, k;
    int len = strlen(string);

    for (i = 0; i < len - 2; i++)
    {
        for (j = i + 1; j < len - 1; j++)
        {
            for (k = j + 1; k < len; k++)
                printf("%c%c%c\n", string[i], string[j], string[k]);
        }
    }
}

115
这种方法的问题在于它将参数3硬连接到代码中。(如果需要4个字符怎么办?)据我所知,将同时提供字符数组和要选择的字符数。当然,解决该问题的一种方法是用递归替换显式嵌套的循环。
joel.neely

10
@ Dr.PersonPersonII为什么三角形与OP相关?
MestreLion,2012年

7
您始终可以将此解决方案转换为具有任意参数的递归解决方案。
Rok Kralj 2014年

5
@RokKralj,我们如何“将该解决方案转换为具有任意参数的递归解决方案”?在我看来似乎是不可能的。
亚伦·麦克戴德

3
一个很好的直观的解释
Yonatan Simson

53

以下递归算法从有序集合中选择所有k元素组合:

  • 选择i组合的第一个元素
  • 结合i与每个组合的k-1从所述一组大于元件的递归选择元素i

i对集合中的每个元素重复上述操作。

请务必选择大于的其余元素i,以避免重复。这样,[3,5]将仅被选择一次,因为[3]与[5]组合,而不是两次(条件消除了[5] + [3])。没有这种条件,您将获得变化而不是组合。


12
用英语对很多答案使用的算法进行了很好的描述
MestreLion 2012年

以上第二点;特别是,这有助于我理解user935714提出的解决方案。两者都很棒。
jacoblambert

25

在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

1
什么是开始,在这种情况下是什么结束?如果传递给该函数的所有变量均按值传递,它实际上如何返回某物?
Sergej Andrejev

6
@Sergej Andrejev:更换beingbegin使用s.begin(),并ends.end()。该代码紧密遵循STL的next_permutation算法,在此进行更详细的描述。
Anthony Labarre

5
怎么了?i1 =最后;--i1; i1 = k;
Manoj R 2012年3

24

我发现此线程很有用,并认为我会添加一个Java脚本解决方案,然后将其弹出Firebug。如果起始字符串较大,则可能需要一些时间,具体取决于您的JS引擎。

function string_recurse(active, rest) {
    if (rest.length == 0) {
        console.log(active);
    } else {
        string_recurse(active + rest.charAt(0), rest.substring(1, rest.length));
        string_recurse(active, rest.substring(1, rest.length));
    }
}
string_recurse("", "abc");

输出应如下所示:

abc
ab
ac
a
bc
b
c

4
@NanoHead这是不是错了。输出已经显示“ ac”-“ ca” 与“ ac” 是相同的组合。您所谈论的是排列(在数学上),其中“ ac”与“ ca”不同。
雅各布·詹科夫

1
这不是n选择k。
Shinzou

20
static IEnumerable<string> Combinations(List<string> characters, int length)
{
    for (int i = 0; i < characters.Count; i++)
    {
        // only want 1 character, just return this one
        if (length == 1)
            yield return characters[i];

        // want more than one character, return this one plus all combinations one shorter
        // only use characters after the current one for the rest of the combinations
        else
            foreach (string next in Combinations(characters.GetRange(i + 1, characters.Count - (i + 1)), length - 1))
                yield return characters[i] + next;
    }
}

不错的解决方案。我引用它在回答这个问题最近:stackoverflow.com/questions/4472036/...
wageoghe

此功能的唯一问题是递归性。虽然通常适合PC上运行的软件,但是如果您使用的是资源受限的平台(例如,嵌入式),那么您
就不

它还将分配大量列表,并进行大量工作来将数组的项目复制到每个新数组中。在我看来,直到整个枚举完成,这些列表才能被收集。
尼尔·康诺顿

真滑 您找到了算法还是从头开始?
狗仔队

20

Python中的简短示例:

def comb(sofar, rest, n):
    if n == 0:
        print sofar
    else:
        for i in range(len(rest)):
            comb(sofar + rest[i], rest[i+1:], n-1)

>>> comb("", "abcde", 3)
abc
abd
abe
acd
ace
ade
bcd
bce
bde
cde

为了说明起见,下面的示例描述了递归方法:

示例:ABCDE
3的所有组合将是:

  • A,其余2个的所有组合(BCDE)
  • B,其余为2的所有组合(CDE)
  • C与其余2的所有组合(DE)

17

Haskell中的简单递归算法

import Data.List

combinations 0 lst = [[]]
combinations n lst = do
    (x:xs) <- tails lst
    rest   <- combinations (n-1) xs
    return $ x : rest

我们首先定义特殊情况,即选择零元素。它产生一个结果,它是一个空列表(即一个包含空列表的列表)。

对于n> 0,x遍历列表xs中的每个元素,并且之后的每个元素x

restn - 1xs对的递归调用中选择元素combinations。函数的最终结果是一个列表,其中的每个元素x : rest(即,具有x作为头和rest尾的列表)都对应于x和的每个不同值rest

> combinations 3 "abcde"
["abc","abd","abe","acd","ace","ade","bcd","bce","bde","cde"]

当然,由于Haskell是惰性的,因此列表会根据需要逐渐生成,因此您可以部分评估指数级的大组合。

> let c = combinations 8 "abcdefghijklmnopqrstuvwxyz"
> take 10 c
["abcdefgh","abcdefgi","abcdefgj","abcdefgk","abcdefgl","abcdefgm","abcdefgn",
 "abcdefgo","abcdefgp","abcdefgq"]

13

到这里来的是爷爷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.

但是为什么{} {} {} {}
shinzou

9

这是Scala中一个优雅的通用实现,如99 Scala Problems中所述

object P26 {
  def flatMapSublists[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] = 
    ls match {
      case Nil => Nil
      case sublist@(_ :: tail) => f(sublist) ::: flatMapSublists(tail)(f)
    }

  def combinations[A](n: Int, ls: List[A]): List[List[A]] =
    if (n == 0) List(Nil)
    else flatMapSublists(ls) { sl =>
      combinations(n - 1, sl.tail) map {sl.head :: _}
    }
}

9

如果您可以使用SQL语法-举例来说,如果您使用LINQ来访问结构或数组的字段,或者直接访问具有仅包含一个字符字段“字母”的名为“字母”的表的数据库,则可以进行以下调整码:

SELECT A.Letter, B.Letter, C.Letter
FROM Alphabet AS A, Alphabet AS B, Alphabet AS C
WHERE A.Letter<>B.Letter AND A.Letter<>C.Letter AND B.Letter<>C.Letter
AND A.Letter<B.Letter AND B.Letter<C.Letter

即使您在“字母”表中有多少个字母(也可以是3、8、10、27等),这将返回3个字母的所有组合。

如果您想要的是所有排列,而不是组合(即,您希望“ ACB”和“ ABC”算作不同,而不是只出现一次),只需删除最后一行(AND),就可以了。

编辑后:重新阅读问题后,我意识到需要的是通用算法,而不是针对选择3个项目的特定算法。亚当·休斯(Adam Hughes)的回答是完整的,很遗憾,我还不能投票赞成。这个答案很简单,但仅适用于您只需要3个项目的情况。


7

带有懒惰组合索引生成的另一个C#版本。该版本维护单个索引数组,以定义所有值的列表与当前组合的值之间的映射,即在整个运行期间不断使用O(k)额外空间。该代码以O(k)时间生成单个组合,包括第一个组合。

public static IEnumerable<T[]> Combinations<T>(this T[] values, int k)
{
    if (k < 0 || values.Length < k)
        yield break; // invalid parameters, no combinations possible

    // generate the initial combination indices
    var combIndices = new int[k];
    for (var i = 0; i < k; i++)
    {
        combIndices[i] = i;
    }

    while (true)
    {
        // return next combination
        var combination = new T[k];
        for (var i = 0; i < k; i++)
        {
            combination[i] = values[combIndices[i]];
        }
        yield return combination;

        // find first index to update
        var indexToUpdate = k - 1;
        while (indexToUpdate >= 0 && combIndices[indexToUpdate] >= values.Length - k + indexToUpdate)
        {
            indexToUpdate--;
        }

        if (indexToUpdate < 0)
            yield break; // done

        // update combination indices
        for (var combIndex = combIndices[indexToUpdate] + 1; indexToUpdate < k; indexToUpdate++, combIndex++)
        {
            combIndices[indexToUpdate] = combIndex;
        }
    }
}

测试代码:

foreach (var combination in new[] {'a', 'b', 'c', 'd', 'e'}.Combinations(3))
{
    System.Console.WriteLine(String.Join(" ", combination));
}

输出:

a b c
a b d
a b e
a c d
a c e
a d e
b c d
b c e
b d e
c d e

这样可以保留顺序。我期望结果集也包含c b a不包含的结果集。
Dmitri Nesteruk '16

任务是生成在k上满足n的所有组合。二项式系数回答了有关从固定的n个元素集中选择k个元素的无序子集的方式的问题。因此,所提出的算法做了应做的事情。
克里斯托夫·


6

在这里,您有一个用C#编码的算法的惰性评估版:

    static bool nextCombination(int[] num, int n, int k)
    {
        bool finished, changed;

        changed = finished = false;

        if (k > 0)
        {
            for (int i = k - 1; !finished && !changed; i--)
            {
                if (num[i] < (n - 1) - (k - 1) + i)
                {
                    num[i]++;
                    if (i < k - 1)
                    {
                        for (int j = i + 1; j < k; j++)
                        {
                            num[j] = num[j - 1] + 1;
                        }
                    }
                    changed = true;
                }
                finished = (i == 0);
            }
        }

        return changed;
    }

    static IEnumerable Combinations<T>(IEnumerable<T> elements, int k)
    {
        T[] elem = elements.ToArray();
        int size = elem.Length;

        if (k <= size)
        {
            int[] numbers = new int[k];
            for (int i = 0; i < k; i++)
            {
                numbers[i] = i;
            }

            do
            {
                yield return numbers.Select(n => elem[n]);
            }
            while (nextCombination(numbers, size, k));
        }
    }

并测试部分:

    static void Main(string[] args)
    {
        int k = 3;
        var t = new[] { "dog", "cat", "mouse", "zebra"};

        foreach (IEnumerable<string> i in Combinations(t, k))
        {
            Console.WriteLine(string.Join(",", i));
        }
    }

希望这对您有所帮助!


6

我在python中有一个用于项目euler的置换算法:

def missing(miss,src):
    "Returns the list of items in src not present in miss"
    return [i for i in src if i not in miss]


def permutation_gen(n,l):
    "Generates all the permutations of n items of the l list"
    for i in l:
        if n<=1: yield [i]
        r = [i]
        for j in permutation_gen(n-1,missing([i],l)):  yield r+j

如果

n<len(l) 

您应该没有重复地拥有所有需要的组合,您需要吗?

它是一个生成器,因此您可以在以下方式中使用它:

for comb in permutation_gen(3,list("ABCDEFGH")):
    print comb 

5
Array.prototype.combs = function(num) {

    var str = this,
        length = str.length,
        of = Math.pow(2, length) - 1,
        out, combinations = [];

    while(of) {

        out = [];

        for(var i = 0, y; i < length; i++) {

            y = (1 << i);

            if(y & of && (y !== of))
                out.push(str[i]);

        }

        if (out.length >= num) {
           combinations.push(out);
        }

        of--;
    }

    return combinations;
}

5

Clojure版本:

(defn comb [k l]
  (if (= 1 k) (map vector l)
      (apply concat
             (map-indexed
              #(map (fn [x] (conj x %2))
                    (comb (dec k) (drop (inc %1) l)))
              l))))

5

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

ABCDEFGH
^ ^ ^
约克

首先,您改变k,因此下一步看起来像这样:

ABCDEFGH
^ ^ ^
约克

如果到达末尾,则继续并依次改变j和k。

ABCDEFGH
^ ^ ^
约克

ABCDEFGH
^ ^ ^
约克

j达到G后,您也开始改变i。

ABCDEFGH
  ^ ^ ^
  约克

ABCDEFGH
  ^ ^ ^
  约克
...
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://stackoverflow.com/a/127898/2628125,但更抽象,适用于任何大小的指针。


这是什么可怕的语言?重击?
Shinzou

1
php,但是这里的语言无关紧要,算法很重要
Oleksandr Knyga '18

我很高兴我拒绝学习这种语言。在2018
。– shinzou

4

所有人都说过并在这里完成的是O'caml代码。从代码中可以明显看出算法。

let combi n lst =
    let rec comb l c =
        if( List.length c = n) then [c] else
        match l with
        [] -> []
        | (h::t) -> (combi t (h::c))@(combi t c)
    in
        combi lst []
;;

4

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

可以更改代码以换行,即从输入“ abcd” wk = 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



3

这是我在C ++中的主张

我试图对迭代器类型施加尽可能小的限制,因此此解决方案仅假定转发迭代器,并且它可以是const_iterator。这适用于任何标准容器。如果参数没有意义,则抛出std :: invalid_argumnent

#include <vector>
#include <stdexcept>

template <typename Fci> // Fci - forward const iterator
std::vector<std::vector<Fci> >
enumerate_combinations(Fci begin, Fci end, unsigned int combination_size)
{
    if(begin == end && combination_size > 0u)
        throw std::invalid_argument("empty set and positive combination size!");
    std::vector<std::vector<Fci> > result; // empty set of combinations
    if(combination_size == 0u) return result; // there is exactly one combination of
                                              // size 0 - emty set
    std::vector<Fci> current_combination;
    current_combination.reserve(combination_size + 1u); // I reserve one aditional slot
                                                        // in my vector to store
                                                        // the end sentinel there.
                                                        // The code is cleaner thanks to that
    for(unsigned int i = 0u; i < combination_size && begin != end; ++i, ++begin)
    {
        current_combination.push_back(begin); // Construction of the first combination
    }
    // Since I assume the itarators support only incrementing, I have to iterate over
    // the set to get its size, which is expensive. Here I had to itrate anyway to  
    // produce the first cobination, so I use the loop to also check the size.
    if(current_combination.size() < combination_size)
        throw std::invalid_argument("combination size > set size!");
    result.push_back(current_combination); // Store the first combination in the results set
    current_combination.push_back(end); // Here I add mentioned earlier sentinel to
                                        // simplyfy rest of the code. If I did it 
                                        // earlier, previous statement would get ugly.
    while(true)
    {
        unsigned int i = combination_size;
        Fci tmp;                            // Thanks to the sentinel I can find first
        do                                  // iterator to change, simply by scaning
        {                                   // from right to left and looking for the
            tmp = current_combination[--i]; // first "bubble". The fact, that it's 
            ++tmp;                          // a forward iterator makes it ugly but I
        }                                   // can't help it.
        while(i > 0u && tmp == current_combination[i + 1u]);

        // Here is probably my most obfuscated expression.
        // Loop above looks for a "bubble". If there is no "bubble", that means, that
        // current_combination is the last combination, Expression in the if statement
        // below evaluates to true and the function exits returning result.
        // If the "bubble" is found however, the ststement below has a sideeffect of 
        // incrementing the first iterator to the left of the "bubble".
        if(++current_combination[i] == current_combination[i + 1u])
            return result;
        // Rest of the code sets posiotons of the rest of the iterstors
        // (if there are any), that are to the right of the incremented one,
        // to form next combination

        while(++i < combination_size)
        {
            current_combination[i] = current_combination[i - 1u];
            ++current_combination[i];
        }
        // Below is the ugly side of using the sentinel. Well it had to haave some 
        // disadvantage. Try without it.
        result.push_back(std::vector<Fci>(current_combination.begin(),
                                          current_combination.end() - 1));
    }
}

3

这是我最近用Java写的代码,该代码计算并返回“ outOf”元素中“ num”元素的所有组合。

// author: Sourabh Bhat (heySourabh@gmail.com)

public class Testing
{
    public static void main(String[] args)
    {

// Test case num = 5, outOf = 8.

        int num = 5;
        int outOf = 8;
        int[][] combinations = getCombinations(num, outOf);
        for (int i = 0; i < combinations.length; i++)
        {
            for (int j = 0; j < combinations[i].length; j++)
            {
                System.out.print(combinations[i][j] + " ");
            }
            System.out.println();
        }
    }

    private static int[][] getCombinations(int num, int outOf)
    {
        int possibilities = get_nCr(outOf, num);
        int[][] combinations = new int[possibilities][num];
        int arrayPointer = 0;

        int[] counter = new int[num];

        for (int i = 0; i < num; i++)
        {
            counter[i] = i;
        }
        breakLoop: while (true)
        {
            // Initializing part
            for (int i = 1; i < num; i++)
            {
                if (counter[i] >= outOf - (num - 1 - i))
                    counter[i] = counter[i - 1] + 1;
            }

            // Testing part
            for (int i = 0; i < num; i++)
            {
                if (counter[i] < outOf)
                {
                    continue;
                } else
                {
                    break breakLoop;
                }
            }

            // Innermost part
            combinations[arrayPointer] = counter.clone();
            arrayPointer++;

            // Incrementing part
            counter[num - 1]++;
            for (int i = num - 1; i >= 1; i--)
            {
                if (counter[i] >= outOf - (num - 1 - i))
                    counter[i - 1]++;
            }
        }

        return combinations;
    }

    private static int get_nCr(int n, int r)
    {
        if(r > n)
        {
            throw new ArithmeticException("r is greater then n");
        }
        long numerator = 1;
        long denominator = 1;
        for (int i = n; i >= r + 1; i--)
        {
            numerator *= i;
        }
        for (int i = 2; i <= n - r; i++)
        {
            denominator *= i;
        }

        return (int) (numerator / denominator);
    }
}

3

简洁的Javascript解决方案:

Array.prototype.combine=function combine(k){    
    var toCombine=this;
    var last;
    function combi(n,comb){             
        var combs=[];
        for ( var x=0,y=comb.length;x<y;x++){
            for ( var l=0,m=toCombine.length;l<m;l++){      
                combs.push(comb[x]+toCombine[l]);           
            }
        }
        if (n<k-1){
            n++;
            combi(n,combs);
        } else{last=combs;}
    }
    combi(1,toCombine);
    return last;
}
// Example:
// var toCombine=['a','b','c'];
// var results=toCombine.combine(4);

3

算法:

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

在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免费课程软件“计算机科学数学”,雷曼等人,第11.2.2节。https: //ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics- for-computer-science-fall-2010 / readings /


3

简短的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

2

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

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

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

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

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

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

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

要了解有关该类的信息并下载代码,请参见将二项式系数制表

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


称其为“ Mark Dominus方法”确实是不正确的,因为正如我所提到的,它已经有850年的历史了,并不难想象。为什么不称之为Lilavati方法呢?
MJD
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.