Thuật toán trả về tất cả các kết hợp của các phần tử k từ n


571

Tôi muốn viết một hàm lấy một mảng các chữ cái làm đối số và một số các chữ cái đó để chọn.

Giả sử bạn cung cấp một mảng gồm 8 chữ cái và muốn chọn 3 chữ cái từ đó. Sau đó, bạn sẽ nhận được:

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

Mảng (hoặc từ) đổi lại bao gồm 3 chữ cái mỗi.


2
Bất kỳ sở thích của ngôn ngữ lập trình?
Jonathan Trần

7
Làm thế nào để bạn muốn đối phó với các chữ cái trùng lặp?
wcm

Không có sở thích về ngôn ngữ, tôi sẽ viết mã bằng ruby ​​nhưng một ý tưởng chung về việc sử dụng thuật toán nào sẽ ổn. Hai chữ cái có cùng giá trị có thể tồn tại nhưng không phải là cùng một chữ cái hai lần.
Fredrik


Trong php, sau đây nên thực hiện thủ thuật: stackoverflow.com/questions/4279722/
triệt

Câu trả lời:


413

Nghệ thuật lập trình máy tính Tập 4: Fascicle 3 có rất nhiều thứ có thể phù hợp với tình huống cụ thể của bạn hơn là cách tôi mô tả.

Mã màu xám

Tất nhiên, một vấn đề mà bạn sẽ gặp phải là bộ nhớ và khá nhanh, bạn sẽ gặp vấn đề với 20 yếu tố trong bộ của mình - 20 C 3 = 1140. Và nếu bạn muốn lặp lại bộ đó, tốt nhất nên sử dụng màu xám đã sửa đổi thuật toán mã để bạn không giữ tất cả chúng trong bộ nhớ. Chúng tạo ra sự kết hợp tiếp theo từ trước đó và tránh lặp lại. Có rất nhiều trong số này cho các mục đích sử dụng khác nhau. Chúng ta có muốn tối đa hóa sự khác biệt giữa các kết hợp liên tiếp không? giảm thiểu? vân vân.

Một số giấy tờ gốc mô tả mã màu xám:

  1. Một số đường dẫn Hamilton và thuật toán thay đổi tối thiểu
  2. Thuật toán tạo kết hợp trao đổi liền kề

Dưới đây là một số giấy tờ khác bao gồm chủ đề:

  1. Một triển khai hiệu quả của Eades, Hickey, Đọc thuật toán tạo kết hợp trao đổi liền kề (PDF, với mã trong Pascal)
  2. Máy phát điện kết hợp
  3. Khảo sát mã màu xám kết hợp (PostScript)
  4. Một thuật toán cho mã màu xám

Chase's Twiddle (thuật toán)

Phillip J Chase, ` Thuật toán 382: Sự kết hợp của M trong số N Đối tượng '(1970)

Thuật toán trong C ...

Chỉ số kết hợp theo thứ tự từ điển (Thuật toán Buckles 515)

Bạn cũng có thể tham chiếu một sự kết hợp theo chỉ số của nó (theo thứ tự từ điển). Nhận ra rằng chỉ mục nên có một số lượng thay đổi từ phải sang trái dựa trên chỉ mục, chúng ta có thể xây dựng một cái gì đó sẽ phục hồi kết hợp.

Vì vậy, chúng tôi có một bộ {1,2,3,4,5,6} ... và chúng tôi muốn có ba yếu tố. Giả sử {1,2,3} chúng ta có thể nói rằng sự khác biệt giữa các yếu tố là một và theo thứ tự và tối thiểu. {1,2,4} có một sự thay đổi và là số thứ tự từ điển 2. Vì vậy, số lượng 'thay đổi' ở nơi cuối cùng chiếm một sự thay đổi trong thứ tự null. Vị trí thứ hai, với một thay đổi {1,3,4} có một thay đổi nhưng chiếm nhiều thay đổi hơn vì nó ở vị trí thứ hai (tỷ lệ thuận với số lượng phần tử trong bộ gốc).

Phương pháp tôi đã mô tả là giải cấu trúc, vì dường như, từ thiết lập đến chỉ mục, chúng ta cần thực hiện ngược lại - khó hơn nhiều. Đây là cách Buckles giải quyết vấn đề. Tôi đã viết một số C để tính toán chúng , với những thay đổi nhỏ - Tôi đã sử dụng chỉ mục của các tập hợp thay vì một phạm vi số để biểu diễn tập hợp, vì vậy chúng tôi luôn làm việc từ 0 ... n. Ghi chú:

  1. Vì các kết hợp không có thứ tự, {1,3,2} = {1,2,3} - chúng tôi yêu cầu chúng là từ điển.
  2. Phương pháp này có 0 ẩn để bắt đầu thiết lập cho sự khác biệt đầu tiên.

Chỉ số kết hợp theo thứ tự từ điển (McCaffrey)

một cách khác :, khái niệm của nó dễ nắm bắt và lập trình hơn nhưng không có sự tối ưu hóa của Buckles. May mắn thay, nó cũng không tạo ra các kết hợp trùng lặp:

Tập hợp x_k ... x_1 bằng Ntối đa hóa i = C (x_1, k) + C (x_2, k-1) + ... + C (x_k, 1), ở đâu C (n, r) = {n chọn r}.

Ví dụ : 27 = C(6,4) + C(5,3) + C(2,2) + C(1,1). Vì vậy, sự kết hợp từ vựng thứ 27 của bốn điều là: {1,2,5,6}, đó là các chỉ mục của bất kỳ tập hợp nào bạn muốn xem xét. Ví dụ dưới đây (OCaml), yêu cầu choosechức năng, để lại cho người đọc:

(* 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)

Một vòng lặp kết hợp nhỏ và đơn giản

Hai thuật toán sau được cung cấp cho mục đích giáo khoa. Họ thực hiện một kết hợp tổng thể lặp và thư mục (tổng quát hơn). Chúng càng nhanh càng tốt, có độ phức tạp O ( n C k ). Tiêu thụ bộ nhớ bị ràng buộc bởi k.

Chúng ta sẽ bắt đầu với iterator, sẽ gọi hàm do người dùng cung cấp cho mỗi kết hợp

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

Một phiên bản tổng quát hơn sẽ gọi hàm do người dùng cung cấp cùng với biến trạng thái, bắt đầu từ trạng thái ban đầu. Vì chúng ta cần chuyển trạng thái giữa các trạng thái khác nhau, chúng tôi sẽ không sử dụng vòng lặp for, nhưng thay vào đó, hãy sử dụng đệ quy,

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
Điều này sẽ tạo ra các kết hợp trùng lặp trong trường hợp tập hợp chứa các phần tử bằng nhau?
Thomas Ahle

2
Vâng, nó sẽ Thomas. Nó là bất khả tri đối với dữ liệu trong mảng. Bạn luôn có thể lọc ra các bản sao đầu tiên, nếu đó là hiệu quả mong muốn, hoặc lựa chọn thuật toán khác.
nlucaroni

19
Câu trả lời tuyệt vời. Bạn có thể vui lòng cung cấp tóm tắt về thời gian chạy và phân tích bộ nhớ cho từng thuật toán không?
unsaught_exceptions

2
Câu trả lời khá tốt. 20C3 là 1140, các dấu chấm than là khó hiểu ở đây vì nó trông giống như một thừa, và thừa làm nhập công thức cho việc tìm kiếm kết hợp. Do đó tôi sẽ chỉnh sửa dấu chấm than.
CashCow

3
Nó hút rằng nhiều trích dẫn đằng sau một tường thành. Có khả năng bao gồm các liên kết không phải trả tiền hoặc bao gồm các đoạn trích dẫn từ các nguồn không?
Terrance

195

Trong 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)));
}

Sử dụng:

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

Kết quả:

123
124
125
134
135
145
234
235
245
345

2
Giải pháp này hoạt động tốt cho các bộ "nhỏ" nhưng đối với các bộ lớn hơn, nó sử dụng một chút bộ nhớ.
Artur Carvalho

1
không liên quan trực tiếp nhưng, mã rất thú vị / dễ đọc và tôi tự hỏi phiên bản nào của c # có cấu trúc / phương thức này? (Tôi chỉ sử dụng c # v1.0 và không nhiều lắm).
LBarret

Chắc chắn là thanh lịch, nhưng IEnumerable sẽ được liệt kê rất nhiều lần. Nếu điều này được hỗ trợ bởi một số hoạt động quan trọng ...
Drew Noakes

2
vì đó là một phương thức mở rộng mà dòng sử dụng của bạn có thể đọc:var result = new[] { 1, 2, 3, 4, 5 }.Combinations(3);
Dave Cousineau

1
Bạn có thể cung cấp phiên bản không chính xác của truy vấn này bằng cách sử dụng các vòng lặp đệ quy
irfandar

81

Giải pháp java ngắn:

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);
        }
    }       
}

Kết quả sẽ là

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

điều này có vẻ là O (n ^ 3) phải không? Tôi tự hỏi là có một thuật toán nhanh hơn để làm điều này.
LZH

Tôi đang làm việc với 20 chọn 10 và điều này dường như đủ nhanh đối với tôi (chưa đến 1 giây)
demongolem

4
@NanoHead bạn sai rồi. Đây là sự kết hợp mà không lặp lại. Và trường hợp của bạn là với sự lặp lại.
Jack The Ripper

Đoạn mã này sẽ dễ tìm thấy hơn trên web ... đây chính xác là thứ tôi đang tìm kiếm!
Manuel S.

Tôi mới thử nghiệm cái này và 7 cách triển khai java khác - cái này nhanh nhất. Nhanh thứ 2 là trên một thứ tự cường độ chậm hơn.
stuart

77

Tôi có thể trình bày giải pháp Python đệ quy của mình cho vấn đề này không?

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))

Ví dụ sử dụng:

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

Tôi thích nó vì sự đơn giản của nó.


16
len(tuple(itertools.combinations('abcdefgh',3)))sẽ đạt được điều tương tự trong Python với ít mã hơn.
hgus1294

59
@ hgus1294 Đúng, nhưng đó sẽ là gian lận. Op yêu cầu một thuật toán, không phải là một phương pháp "ma thuật" gắn liền với một ngôn ngữ lập trình cụ thể.
MestreLion

1
Không nên nói đúng phạm vi vòng lặp đầu tiên for i in xrange(len(elements) - length + 1):? Không quan trọng trong python vì việc ra khỏi chỉ mục lát được xử lý một cách duyên dáng nhưng đó là thuật toán chính xác.
Stephan Dollberg

62

Hãy nói rằng mảng các chữ cái của bạn trông như thế này: "ABCDEFGH". Bạn có ba chỉ số (i, j, k) cho biết bạn sẽ sử dụng chữ cái nào cho từ hiện tại, Bạn bắt đầu bằng:

ABCDEFGH
^ ^ ^
ijk

Đầu tiên bạn thay đổi k, vì vậy bước tiếp theo trông như thế:

ABCDEFGH
^ ^ ^
ijk

Nếu bạn đạt đến cuối, bạn tiếp tục và thay đổi j và sau đó k một lần nữa.

ABCDEFGH
^ ^ ^
ijk

ABCDEFGH
^ ^ ^
ijk

Khi bạn đạt G, bạn cũng bắt đầu thay đổi i.

ABCDEFGH
  ^ ^ ^
  ijk

ABCDEFGH
  ^ ^ ^
  ijk
...

Viết bằng mã này trông giống như thế

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
Vấn đề với cách tiếp cận này là nó nối cứng tham số 3 vào mã. (Điều gì sẽ xảy ra nếu muốn có 4 ký tự?) Khi tôi hiểu câu hỏi, cả mảng ký tự VÀ số lượng ký tự sẽ chọn sẽ được cung cấp. Tất nhiên, một cách xung quanh vấn đề đó là thay thế các vòng lặp lồng nhau rõ ràng bằng đệ quy.
joel.neely

10
@ Dr.PersonPersonII Và tại sao tam giác của bất kỳ liên quan đến OP?
MestreLion

7
Bạn luôn có thể chuyển đổi giải pháp này thành một đệ quy với tham số tùy ý.
Rok Kralj

5
@RokKralj, làm thế nào để chúng ta "chuyển đổi giải pháp này thành đệ quy với tham số tùy ý"? Có vẻ như không thể với tôi.
Aaron McDaid

3
Một lời giải thích trực quan thú vị về cách thực hiện
Yonatan Simson

53

Thuật toán đệ quy sau đây chọn tất cả các kết hợp phần tử k từ một tập hợp có thứ tự:

  • chọn yếu tố đầu tiên icủa sự kết hợp của bạn
  • kết hợp ivới mỗi kết hợp của k-1các phần tử được chọn đệ quy từ tập hợp các phần tử lớn hơn i.

Lặp lại ở trên cho mỗi itrong bộ.

Điều cần thiết là bạn chọn phần còn lại của các phần tử lớn hơn i, để tránh lặp lại. Cách này [3,5] sẽ chỉ được chọn một lần, vì [3] kết hợp với [5], thay vì hai lần (điều kiện loại bỏ [5] + [3]). Không có điều kiện này, bạn có được các biến thể thay vì kết hợp.


12
Mô tả rất tốt bằng tiếng Anh về thuật toán được sử dụng bởi nhiều câu trả lời
MestreLion

thứ hai ở trên; đặc biệt, điều này giúp tôi hiểu được giải pháp do user935714 đặt ra. cả hai đều xuất sắc
jacoblambert

25

Trong C ++, thói quen sau sẽ tạo ra tất cả các kết hợp khoảng cách độ dài (đầu tiên, k) giữa phạm vi [đầu tiên, cuối cùng):

#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;
}

Nó có thể được sử dụng như thế này:

#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;
}

Điều này sẽ in như sau:

123
124
125
134
135
145
234
235
245
345

1
Cái gì là bắt đầu, cái gì là kết thúc trong trường hợp này? Làm thế nào nó thực sự có thể trả về một cái gì đó nếu tất cả các biến được truyền cho hàm này được truyền theo giá trị?
Sergej Andrejev

6
@Sergej Andrejev: thay thế beingbeginbằng s.begin(), và endbằng s.end(). Mã theo sát next_permutationthuật toán của STL , được mô tả chi tiết hơn ở đây .
Anthony Labarre

5
chuyện gì đang xảy ra vậy i1 = cuối cùng; --i1; i1 = k;
Manoj R

24

Tôi thấy chủ đề này hữu ích và nghĩ rằng tôi sẽ thêm một giải pháp Javascript mà bạn có thể bật vào Fireorms. Tùy thuộc vào công cụ JS của bạn, có thể mất một chút thời gian nếu chuỗi bắt đầu lớn.

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");

Đầu ra phải như sau:

abc
ab
ac
a
bc
b
c

4
@NanoHead Điều này không sai. Đầu ra đã hiển thị "ac" - và "ca" là sự kết hợp giống như "ac". Bạn đang nói về hoán vị (trong toán học) trong đó "ac" sẽ không giống như "ca".
Jakob Jenkov

1
Đây không phải là n chọ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;
    }
}

Giải pháp tốt đẹp. Tôi đã tham khảo nó khi trả lời câu hỏi gần đây này: stackoverflow.com/questions/4472036/
mức lương

Vấn đề duy nhất với chức năng này là đệ quy. Mặc dù phần mềm chạy trên PC thường tốt, nhưng nếu bạn làm việc với nền tảng bị hạn chế tài nguyên hơn (ví dụ như được nhúng), bạn sẽ không gặp may
Padu Merloti

Nó cũng sẽ phân bổ rất nhiều Danh sách và thực hiện nhiều công việc để sao chép các mục của mảng vào mỗi danh sách mới. Theo tôi thì những danh sách này sẽ không thể được thu thập cho đến khi toàn bộ bảng liệt kê hoàn tất.
Niall Connaughton

Đó là khéo léo. Bạn đã tìm thấy một thuật toán hoặc đó là từ đầu?
paparazzo

20

Ví dụ ngắn trong 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

Để giải thích, phương pháp đệ quy được mô tả với ví dụ sau:

Ví dụ: ABCDE
Tất cả các kết hợp của 3 sẽ là:

  • A với tất cả các kết hợp 2 từ phần còn lại (BCDE)
  • B với tất cả các kết hợp 2 từ phần còn lại (CDE)
  • C với tất cả các kết hợp 2 từ phần còn lại (DE)

17

Thuật toán đệ quy đơn giản trong Haskell

import Data.List

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

Trước tiên chúng ta xác định trường hợp đặc biệt, tức là chọn các phần tử bằng không. Nó tạo ra một kết quả duy nhất, đó là một danh sách trống (tức là một danh sách chứa danh sách trống).

Với n> 0, xđi qua mọi phần tử của danh sách và xslà mọi phần tử sau x.

restchọn n - 1các yếu tố từ xsviệc sử dụng một cuộc gọi đệ quy đến combinations. Kết quả cuối cùng của hàm là một danh sách trong đó mỗi phần tử là x : rest(tức là một danh sách có phần xđầu và phần restđuôi) cho mỗi giá trị khác nhau của xrest.

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

Và tất nhiên, vì Haskell lười biếng, danh sách dần dần được tạo ra khi cần thiết, do đó bạn có thể đánh giá một phần các kết hợp lớn theo cấp số nhân.

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

13

Và đây là COBOL, một ngôn ngữ không phù hợp.

Giả sử một mảng gồm 34 phần tử 8 byte mỗi phần (lựa chọn hoàn toàn tùy ý.) Ý tưởng là liệt kê tất cả các kết hợp 4 phần tử có thể và tải chúng vào một mảng.

Chúng tôi sử dụng 4 chỉ số, mỗi chỉ số cho mỗi vị trí trong nhóm 4

Mảng được xử lý như thế này:

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

Chúng tôi thay đổi idx4 từ 4 đến cuối. Đối với mỗi idx4, chúng tôi nhận được một sự kết hợp độc đáo của các nhóm bốn. Khi idx4 đến cuối mảng, chúng ta tăng idx3 lên 1 và đặt idx4 thành idx3 + 1. Sau đó, chúng tôi chạy idx4 đến cuối một lần nữa. Chúng tôi tiến hành theo cách này, tăng lần lượt idx3, idx2 và idx1 cho đến khi vị trí của idx1 nhỏ hơn 4 từ cuối mảng. Điều đó kết thúc thuật toán.

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

Lặp lại đầu tiên:

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

Một ví dụ về 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.

nhưng tại sao {} {} {} {}
shinzou

9

Đây là một triển khai chung, thanh lịch trong Scala, như được mô tả trên 99 Vấn đề Scala .

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

Nếu bạn có thể sử dụng cú pháp SQL - giả sử, nếu bạn đang sử dụng LINQ để truy cập các trường của cấu trúc hoặc mảng hoặc truy cập trực tiếp vào cơ sở dữ liệu có bảng có tên là "Bảng chữ cái" chỉ với một chữ cái "Thư", bạn có thể điều chỉnh theo mã:

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

Điều này sẽ trả về tất cả các kết hợp của 3 chữ cái, mặc dù bạn có bao nhiêu chữ cái trong bảng "Bảng chữ cái" (nó có thể là 3, 8, 10, 27, v.v.).

Nếu những gì bạn muốn là tất cả các hoán vị, thay vì kết hợp (tức là bạn muốn "ACB" và "ABC" được tính là khác nhau, thay vì chỉ xuất hiện một lần), chỉ cần xóa dòng cuối cùng (một AND) và thế là xong.

Chỉnh sửa sau: Sau khi đọc lại câu hỏi, tôi nhận ra những gì cần thiết là thuật toán chung , không chỉ là một thuật toán cụ thể cho trường hợp chọn 3 mục. Câu trả lời của Adam Hughes là câu trả lời đầy đủ, tiếc là tôi không thể bỏ phiếu (chưa). Câu trả lời này đơn giản nhưng chỉ hoạt động khi bạn muốn chính xác 3 mục.


7

Một phiên bản C # khác với thế hệ lười biếng của các chỉ số kết hợp. Phiên bản này duy trì một mảng các chỉ mục duy nhất để xác định ánh xạ giữa danh sách tất cả các giá trị và các giá trị cho kết hợp hiện tại, tức là liên tục sử dụng không gian bổ sung O (k) trong toàn bộ thời gian chạy. Mã này tạo ra các kết hợp riêng lẻ, bao gồm cả kết hợp đầu tiên, trong thời gian 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;
        }
    }
}

Mã kiểm tra:

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

Đầu ra:

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

Điều này bảo tồn trật tự. Tôi đang mong đợi kết quả được đặt cũng chứa c b acái mà nó không có.
Dmitri Nesteruk

Nhiệm vụ là tạo ra tất cả các kết hợp thỏa mãn n trên k. Các hệ số nhị thức trả lời câu hỏi về việc có bao nhiêu cách chọn một tập hợp con không có thứ tự của các phần tử k từ một tập hợp n phần tử cố định. Do đó, thuật toán đề xuất làm những gì nó cần.
Christoph

6

https://gist.github.com/3118596

Có một triển khai cho JavaScript. Nó có các chức năng để có được kết hợp k và tất cả các kết hợp của một mảng của bất kỳ đối tượng nào. Ví dụ:

k_combinations([1,2,3], 2)
-> [[1,2], [1,3], [2,3]]

combinations([1,2,3])
-> [[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]

6

Ở đây bạn có một phiên bản đánh giá lười biếng của thuật toán đó được mã hóa bằng 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));
        }
    }

Và phần kiểm tra:

    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));
        }
    }

Hy vọng điều này sẽ giúp bạn!


6

Tôi đã có một thuật toán hoán vị tôi đã sử dụng cho dự án euler, trong python:

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ếu

n<len(l) 

bạn nên có tất cả sự kết hợp bạn cần mà không cần lặp lại, bạn có cần nó không?

Nó là một trình tạo, vì vậy bạn sử dụng nó trong một cái gì đó như thế này:

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

Phiên bản 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

Hãy nói rằng mảng các chữ cái của bạn trông như thế này: "ABCDEFGH". Bạn có ba chỉ số (i, j, k) cho biết bạn sẽ sử dụng chữ cái nào cho từ hiện tại, Bạn bắt đầu bằng:

ABCDEFGH
^ ^ ^
ijk

Đầu tiên bạn thay đổi k, vì vậy bước tiếp theo trông như thế:

ABCDEFGH
^ ^ ^
ijk

Nếu bạn đạt đến cuối, bạn tiếp tục và thay đổi j và sau đó k một lần nữa.

ABCDEFGH
^ ^ ^
ijk

ABCDEFGH
^ ^ ^
ijk

Khi bạn đạt G, bạn cũng bắt đầu thay đổi i.

ABCDEFGH
  ^ ^ ^
  ijk

ABCDEFGH
  ^ ^ ^
  ijk
...
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);

Dựa trên https://stackoverflow.com/a/127898/2628125 , nhưng trừu tượng hơn, cho bất kỳ kích thước con trỏ.


Ngôn ngữ khủng khiếp này là gì? Bash?
shinzou

1
php, nhưng ngôn ngữ không thành vấn đề ở đây, thuật toán thực hiện
Oleksandr Knyga

Tôi rất vui vì tôi từ chối học ngôn ngữ này. Một ngôn ngữ mà trình thông dịch / trình biên dịch của nó cần trợ giúp để nhận ra các biến không nên tồn tại vào năm 2018.
shinzou

4

Tất cả đã nói và thực hiện ở đây có mã O'caml cho điều đó. Thuật toán là hiển nhiên từ mã ..

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

Đây là một phương pháp cung cấp cho bạn tất cả các kết hợp kích thước được chỉ định từ một chuỗi độ dài ngẫu nhiên. Tương tự như giải pháp của quinmars, nhưng hoạt động cho đầu vào và k khác nhau.

Mã có thể được thay đổi để bọc xung quanh, tức là 'dab' từ đầu vào 'abcd' wk = 3.

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);
    }
}

Đầu ra cho "abcde":

abc abd abe acd ace ade bcd bce bde cde



3

Đây là đề xuất của tôi trong C ++

Tôi đã cố gắng áp đặt một chút hạn chế đối với loại trình vòng lặp vì tôi có thể giải pháp này giả sử chỉ là trình chuyển tiếp chuyển tiếp và nó có thể là một const_iterator. Điều này sẽ làm việc với bất kỳ container tiêu chuẩn. Trong trường hợp các đối số không có ý nghĩa, nó sẽ ném std :: default_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

Đây là một mã tôi đã viết gần đây bằng Java, tính toán và trả về tất cả sự kết hợp của các phần tử "num" từ các phần tử "outOf".

// 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

Một giải pháp Javascript ngắn gọn:

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

Thuật toán:

  • Đếm từ 1 đến 2 ^ n.
  • Chuyển đổi từng chữ số để biểu diễn nhị phân của nó.
  • Dịch từng bit 'trên' thành các phần tử của tập hợp của bạn, dựa trên vị trí.

Trong 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();
        }
    }
}

Tại sao nó hoạt động?

Có một sự lựa chọn giữa các tập hợp con của tập hợp phần tử n và chuỗi n-bit.

Điều đó có nghĩa là chúng ta có thể tìm ra có bao nhiêu tập con có bằng cách đếm các chuỗi.

ví dụ: bốn phần tử được đặt bên dưới có thể được biểu diễn bằng {0,1} X {0, 1} X {0, 1} X {0, 1} (hoặc 2 ^ 4) các chuỗi khác nhau.

Vì vậy - tất cả những gì chúng ta phải làm là đếm từ 1 đến 2 ^ n để tìm tất cả các kết hợp. (Chúng tôi bỏ qua tập hợp trống.) Tiếp theo, dịch các chữ số sang biểu diễn nhị phân của chúng. Sau đó, thay thế các phần tử của tập hợp của bạn cho các bit 'on'.

Nếu bạn chỉ muốn kết quả phần tử k, chỉ in khi k bit được 'bật'.

(Nếu bạn muốn tất cả các tập hợp con thay vì tập con k chiều dài, hãy xóa phần cnt / kEuity.)

(Đối với giấy tờ chứng minh, xem MIT miễn phí chương trình học Toán Khoa học máy tính, Lehman et al, phần 11.2.2. Https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics- cho máy tính-khoa học-mùa thu-2010 / bài đọc / )


3

mã python ngắn, mang lại vị trí chỉ số

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

Tôi đã viết một lớp để xử lý các hàm phổ biến để làm việc với hệ số nhị thức, đây là loại vấn đề mà vấn đề của bạn rơi vào. Nó thực hiện các nhiệm vụ sau:

  1. Xuất ra tất cả các chỉ mục K ở định dạng đẹp cho mọi N chọn K vào một tệp. Các chỉ số K có thể được thay thế bằng các chuỗi hoặc chữ cái mô tả nhiều hơn. Phương pháp này làm cho việc giải quyết loại vấn đề này khá tầm thường.

  2. Chuyển đổi các chỉ mục K thành chỉ mục thích hợp của một mục trong bảng hệ số nhị thức được sắp xếp. Kỹ thuật này nhanh hơn nhiều so với các kỹ thuật được công bố cũ hơn dựa trên phép lặp. Nó thực hiện điều này bằng cách sử dụng một thuộc tính toán học vốn có trong Tam giác của Pascal. Bài viết của tôi nói về điều này. Tôi tin rằng tôi là người đầu tiên khám phá và xuất bản kỹ thuật này, nhưng tôi có thể sai.

  3. Chuyển đổi chỉ mục trong bảng hệ số nhị thức được sắp xếp thành các chỉ số K tương ứng.

  4. Sử dụng phương pháp Mark Dominus để tính hệ số nhị thức, ít có khả năng tràn hơn và hoạt động với số lượng lớn hơn.

  5. Lớp này được viết bằng .NET C # và cung cấp cách quản lý các đối tượng liên quan đến vấn đề (nếu có) bằng cách sử dụng danh sách chung. Hàm tạo của lớp này nhận một giá trị bool gọi là initTable mà khi true sẽ tạo một danh sách chung để giữ các đối tượng cần quản lý. Nếu giá trị này là sai, thì nó sẽ không tạo bảng. Bảng không cần phải được tạo để thực hiện 4 phương thức trên. Phương thức Accessor được cung cấp để truy cập vào bảng.

  6. Có một lớp kiểm tra liên quan chỉ ra cách sử dụng lớp và các phương thức của nó. Nó đã được thử nghiệm rộng rãi với 2 trường hợp và không có lỗi nào được biết đến.

Để đọc về lớp này và tải xuống mã, hãy xem Tablizing The Binomial Coeffieicent .

Không khó để chuyển đổi lớp này sang C ++.


Thật sự không đúng khi gọi nó là "phương pháp Mark Dominus", vì như tôi đã đề cập, nó ít nhất là 850 tuổi và không khó để nghĩ ra. Tại sao không gọi nó là phương pháp Lilavati ?
MJD
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.