อัลกอริทึมเพื่อส่งคืนการรวมกันทั้งหมดขององค์ประกอบ k จาก n


571

ฉันต้องการเขียนฟังก์ชั่นที่ใช้อาร์เรย์ของตัวอักษรเป็นอาร์กิวเมนต์และเลือกตัวอักษรเหล่านั้นจำนวนหนึ่ง

สมมติว่าคุณมีอาร์เรย์ 8 ตัวอักษรและต้องการเลือก 3 ตัวอักษรจากนั้น จากนั้นคุณควรจะได้รับ:

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

อาร์เรย์ (หรือคำ) ในทางกลับกันประกอบด้วย 3 ตัวอักษรแต่ละตัว


2
การตั้งค่าภาษาโปรแกรมใด ๆ ?
Jonathan Tran

7
คุณต้องการจัดการกับตัวอักษรที่ซ้ำกันอย่างไร
wcm

ไม่มีการตั้งค่าภาษาฉันจะรหัสในทับทิม แต่ความคิดทั่วไปของอัลกอริทึมที่จะใช้จะดี ตัวอักษรสองตัวที่มีค่าเท่ากันอาจมีอยู่ แต่ไม่ใช่ตัวอักษรเดียวกันที่แน่นอนสองครั้ง
Fredrik


ใน php สิ่งต่อไปนี้ควรทำเคล็ดลับ: stackoverflow.com/questions/4279722/…
Kemal Dağ

คำตอบ:


413

Art of Computer Programming Volume 4: Fascicle 3มีสิ่งเหล่านี้มากมายที่อาจเหมาะสมกับสถานการณ์ของคุณได้ดีกว่าที่ฉันอธิบาย

รหัสสีเทา

ปัญหาที่คุณจะเจอคือหน่วยความจำแน่นอนและรวดเร็วคุณจะมีปัญหา 20 องค์ประกอบในชุดของคุณ - 20 C 3 = 1140 และถ้าคุณต้องการวนซ้ำชุดที่ดีที่สุดคือการใช้สีเทาแก้ไข อัลกอริธึมรหัสดังนั้นคุณไม่ได้เก็บมันไว้ในหน่วยความจำ สิ่งเหล่านี้สร้างชุดค่าผสมถัดไปจากก่อนหน้านี้และหลีกเลี่ยงการทำซ้ำ มีหลายสิ่งสำหรับการใช้งานที่แตกต่างกัน เราต้องการเพิ่มความแตกต่างระหว่างชุดค่าผสมที่ต่อเนื่องหรือไม่ ลด? และอื่น ๆ

เอกสารต้นฉบับบางส่วนที่อธิบายถึงรหัสสีเทา:

  1. บางเส้นทางแฮมิลตันและอัลกอริทึมการเปลี่ยนแปลงที่น้อยที่สุด
  2. อัลกอริทึมการสร้างชุดค่าผสม Interchange

นี่คือเอกสารอื่น ๆ ที่ครอบคลุมหัวข้อ:

  1. การใช้ Eades, Hickey, Read Algorithm การสร้างชุดการแลกเปลี่ยน Interchange ที่อยู่ติดกันอย่างมีประสิทธิภาพ (PDF พร้อมรหัสใน Pascal)
  2. เครื่องกำเนิดไฟฟ้าแบบผสม
  3. การสำรวจรหัส Combinatorial Grey (PostScript)
  4. อัลกอริทึมสำหรับรหัสสีเทา

Chase's Twiddle (อัลกอริทึม)

Phillip J Chase, ` อัลกอริทึม 382: การรวม M จากวัตถุ N '(1970)

อัลกอริทึมใน C ...

ดัชนีชุดค่าผสมในคำสั่งพจนานุกรม (อัลกอริธึม Buckles 515)

นอกจากนี้คุณยังสามารถอ้างอิงชุดค่าผสมตามดัชนีของมัน (ตามลำดับพจนานุกรม) ตระหนักว่าดัชนีควรมีจำนวนการเปลี่ยนแปลงจากขวาไปซ้ายตามดัชนีเราสามารถสร้างสิ่งที่ควรกู้คืนชุดค่าผสม

ดังนั้นเราจึงมีชุด {1,2,3,4,5,6} ... และเราต้องการองค์ประกอบสามอย่าง สมมุติว่า {1,2,3} เราสามารถพูดได้ว่าความแตกต่างระหว่างองค์ประกอบคือหนึ่งและเป็นลำดับและน้อยที่สุด {1,2,4} มีการเปลี่ยนแปลงเพียงครั้งเดียวและเป็นจำนวนตามพจนานุกรม 2 จำนวนดังนั้นจำนวนของ 'การเปลี่ยนแปลง' ในสถานที่สุดท้ายที่บัญชีสำหรับการเปลี่ยนแปลงหนึ่งในการสั่งซื้อพจนานุกรม สถานที่ที่สองโดยมีการเปลี่ยนแปลงหนึ่งครั้ง {1,3,4} มีการเปลี่ยนแปลงเพียงครั้งเดียว แต่บัญชีสำหรับการเปลี่ยนแปลงเพิ่มเติมเนื่องจากอยู่ในสถานที่ที่สอง (ตามสัดส่วนของจำนวนองค์ประกอบในชุดต้นฉบับ)

วิธีที่ฉันอธิบายไว้เป็นโครงสร้างที่ดูเหมือนว่าจากการตั้งค่าเป็นดัชนีเราจำเป็นต้องทำย้อนกลับ - ซึ่งเป็นเรื่องที่ยุ่งยากมาก นี่คือวิธีที่Bucklesแก้ไขปัญหา ฉันเขียนC เพื่อคำนวณพวกมันโดยมีการเปลี่ยนแปลงเล็กน้อย - ฉันใช้ดัชนีของเซตแทนที่จะเป็นช่วงตัวเลขเพื่อแสดงชุดดังนั้นเราจึงทำงานตั้งแต่ 0 ... n บันทึก:

  1. เนื่องจากชุดค่าผสมไม่ได้เรียงลำดับ {1,3,2} = {1,2,3} - เราจึงสั่งให้ชุดคำเหล่านั้นเป็นพจนานุกรม
  2. วิธีนี้มีนัย 0 เพื่อเริ่มต้นการตั้งค่าสำหรับความแตกต่างแรก

ดัชนีรวมในคำสั่งพจนานุกรม (McCaffrey)

มีวิธีอื่น : แนวคิดของมันง่ายต่อการเข้าใจและโปรแกรม แต่มันไม่มีการเพิ่มประสิทธิภาพของ Buckles โชคดีที่มันไม่ได้สร้างชุดค่าผสมที่ซ้ำกัน:

ชุดx_k ... x_1 ใน Nที่เพิ่มที่i = C (x_1, k) + C (x_2, k-1) + ... + C (x_k, 1)C (n, r) = {n เลือก 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-loop แต่ใช้การเรียกซ้ำ

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
สิ่งนี้จะสร้างชุดค่าผสมที่ซ้ำกันในกรณีที่ชุดประกอบด้วยองค์ประกอบที่เท่ากันหรือไม่
โทมัส Ahle

2
ใช่มันจะโทมัส มันไม่เชื่อเรื่องพระเจ้ากับข้อมูลในอาร์เรย์ คุณสามารถกรองรายการที่ซ้ำก่อนเสมอหากเป็นเอฟเฟกต์ที่ต้องการหรือเลือกอัลกอริทึมอื่น
nlucaroni

19
คำตอบที่ยอดเยี่ยม คุณช่วยอธิบายสรุปเวลาทำงานและการวิเคราะห์หน่วยความจำสำหรับแต่ละอัลกอริทึมได้หรือไม่?
uncaught_exceptions

2
คำตอบที่ดีพอสมควร 20C3 คือ 1140 เครื่องหมายอัศเจรีย์ทำให้เกิดความสับสนที่นี่เนื่องจากดูเหมือนว่าแฟคทอเรียลและแฟคทอเรียลจะใส่สูตรสำหรับค้นหาชุดค่าผสม ฉันจะแก้ไขเครื่องหมายอัศเจรีย์
CashCow

3
มันน่าสนใจที่การอ้างอิงจำนวนมากอยู่เบื้องหลังการจ่ายเงิน มีความเป็นไปได้ไหมที่จะรวมลิงค์ที่ไม่ใช่ paywall หรือรวมตัวอย่างโค้ดที่สามารถหาได้จากแหล่งที่มา
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

สง่างามแน่นอน แต่ IEnumerable จะแจกแจงมากครั้ง ถ้าเรื่องนี้ได้รับการสนับสนุนโดยการดำเนินการบางอย่างที่สำคัญ ...
Drew Noakes

2
เนื่องจากเป็นวิธีการขยายสายการใช้งานของคุณสามารถอ่านได้:var result = new[] { 1, 2, 3, 4, 5 }.Combinations(3);
Dave Cousineau

1
คุณสามารถระบุรุ่นที่ไม่ใช่ linq ที่แน่นอนของแบบสอบถามนี้โดยใช้ลูปแบบเรียกซ้ำได้
ไหม

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

ฉันทำงานกับ 20 เลือก 10 และดูเหมือนว่าจะเร็วพอสำหรับฉัน (น้อยกว่า 1 วินาที)
demongolem

4
@NanoHead คุณผิด นี่คือการรวมกันโดยไม่มีการซ้ำซ้อน และกรณีของคุณอยู่กับการทำซ้ำ
แจ็คเดอะริปเปอร์

โค้ดชิ้นนี้ควรจะง่ายต่อการค้นหาบนเว็บ ... นี่คือสิ่งที่ฉันกำลังมองหา!
Manuel S.

ฉันเพิ่งทดสอบสิ่งนี้และการใช้งานจาวาอื่น ๆ อีก 7 รายการ - อันนี้เร็วที่สุด ที่เร็วที่สุดที่สองคือมากกว่าลำดับความช้า
สจ๊วต

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

1
ไม่ควรพูดช่วงลูปแรกอย่างเคร่งครัดfor i in xrange(len(elements) - length + 1):หรือ ไม่สำคัญว่าจะเป็นงูหลามเนื่องจากการจัดการดัชนีไม่สมบูรณ์ แต่เป็นอัลกอริทึมที่ถูกต้อง
Stephan Dollberg

62

ให้บอกว่าตัวอักษรของคุณมีลักษณะดังนี้: "ABCDEFGH" คุณมีสามดัชนี (i, j, k) เพื่อระบุตัวอักษรที่คุณจะใช้สำหรับคำปัจจุบันคุณเริ่มต้นด้วย:

ABCDEFGH
^ ^ ^
IJK

ขั้นแรกคุณต้องเปลี่ยนแปลง k ดังนั้นขั้นตอนต่อไปจะเป็นดังนี้:

ABCDEFGH
^ ^ ^
IJK

หากคุณถึงจุดสิ้นสุดคุณจะดำเนินการต่อและเปลี่ยนค่า j แล้วเปลี่ยน k อีกครั้ง

ABCDEFGH
^ ^ ^
IJK

ABCDEFGH
^ ^ ^
IJK

เมื่อคุณถึง G คุณจะเริ่มแตกต่างกันฉัน

ABCDEFGH
  ^ ^ ^
  IJK

ABCDEFGH
  ^ ^ ^
  IJK
...

เขียนในรหัสนี้มีลักษณะเช่นนั้น

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

7
คุณสามารถเปลี่ยนโซลูชันนี้เป็นแบบเรียกซ้ำโดยใช้พารามิเตอร์ที่กำหนดเอง
Rok Kralj

5
@ ROKKralj เราจะ "เปลี่ยนวิธีนี้เป็น recursive หนึ่งด้วยพารามิเตอร์โดยพลการ" ได้อย่างไร ดูเหมือนเป็นไปไม่ได้สำหรับฉัน
Aaron McDaid

3
คำอธิบายที่เข้าใจง่ายเกี่ยวกับวิธีการใช้งาน
Yonatan Simson

53

อัลกอริทึมแบบเรียกซ้ำต่อไปนี้เลือกชุดค่าผสม k-element ทั้งหมดจากชุดคำสั่ง:

  • เลือกองค์ประกอบแรกiของชุดค่าผสมของคุณ
  • รวมiกันของการรวมกันขององค์ประกอบที่ได้รับการแต่งตั้งซ้ำจากชุดขององค์ประกอบที่มีขนาดใหญ่กว่าk-1i

วนซ้ำด้านบนสำหรับแต่ละiชุด

มันเป็นสิ่งสำคัญที่คุณจะต้องเลือกองค์ประกอบที่เหลือให้ใหญ่กว่าiเพื่อหลีกเลี่ยงการทำซ้ำ วิธีนี้ [3,5] จะถูกเลือกเพียงครั้งเดียวเนื่องจาก [3] รวมกับ [5] แทนที่จะเป็นสองครั้ง (เงื่อนไขจะกำจัด [5] + [3]) หากไม่มีเงื่อนไขนี้คุณจะได้รับชุดรูปแบบแทนชุดค่าผสม


12
คำอธิบายที่ดีมากในภาษาอังกฤษของอัลกอริทึมที่ใช้โดยคำตอบหลายคำตอบ
MestreLion

ที่สองข้างต้น; โดยเฉพาะอย่างยิ่งสิ่งนี้ช่วยให้ฉันเข้าใจโซลูชันที่โพสต์โดย user935714 ทั้งสองอย่างยอดเยี่ยม
jacoblambert

25

ใน C ++ ชุดคำสั่งต่อไปนี้จะสร้างชุดค่าผสมของระยะทางยาวทั้งหมด (แรก, 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: แทนที่beingและbeginมีs.begin()และมีend s.end()รหัสอย่างใกล้ชิดต่อไปนี้ STL ของnext_permutationขั้นตอนวิธีการอธิบายที่นี่ในรายละเอียดเพิ่มเติม
Anthony Labarre

5
เกิดอะไรขึ้น? i1 = สุดท้าย; --i1; i1 = k;
Manoj R

24

ฉันพบหัวข้อนี้มีประโยชน์และคิดว่าฉันจะเพิ่มโซลูชัน Javascript ที่คุณสามารถปรากฏใน 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"
Jakob Jenkov

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

ปัญหาเดียวของฟังก์ชั่นนี้คือการเรียกซ้ำ ในขณะที่มันเป็นเรื่องปกติสำหรับซอฟต์แวร์ที่ทำงานบนพีซีหากคุณกำลังทำงานกับแพลตฟอร์มที่มีข้อ จำกัด ด้านทรัพยากรเพิ่มเติม (ฝังตัวอย่าง) คุณไม่มีโชค
Padu Merloti

นอกจากนี้ยังจะจัดสรรรายการจำนวนมากและทำงานอย่างมากเพื่อทำซ้ำรายการของอาร์เรย์ในแต่ละรายการใหม่ ดูเหมือนว่าฉันจะไม่สามารถรวบรวมรายการเหล่านี้ได้จนกว่าการแจงนับทั้งหมดจะเสร็จสมบูรณ์
Niall Connaughton

นั่นคือเนียน คุณพบอัลกอริธึมหรือว่าเป็นรอยขีดข่วน?
paparazzo

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นั้น

restหยิบn - 1องค์ประกอบจากการใช้โทรxs recursive combinationsผลสุดท้ายของการทำงานคือรายการที่แต่ละองค์ประกอบคือx : rest(เช่นรายการซึ่งมีxฐานะเป็นหัวหน้าและrestเป็นหาง) สำหรับค่าที่แตกต่างกันทุกและxrest

> 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

และที่นี่ภาษาโคบอลคุณปู่ซึ่งเป็นภาษาที่ร้ายกาจมาก

สมมติว่าอาร์เรย์มี 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.

ตัวอย่างภาษาโคบอล:

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 ปัญหาสกาล่า

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 ตัวอักษรแม้ว่าจะมีกี่ตัวอักษรที่คุณมีในตาราง "ตัวอักษร" (อาจเป็น 3, 8, 10, 27, ฯลฯ )

หากสิ่งที่คุณต้องการคือพีชคณิตทั้งหมดแทนที่จะเป็นชุดค่าผสม (เช่นคุณต้องการให้ "ACB" และ "ABC" นับเป็นจำนวนที่แตกต่างกันแทนที่จะปรากฏเพียงครั้งเดียว) ให้ลบบรรทัดสุดท้าย (และหนึ่ง) และทำเสร็จแล้ว

โพสต์ - แก้ไข: หลังจากอ่านคำถามอีกครั้งฉันรู้ว่าสิ่งที่จำเป็นคืออัลกอริธึมทั่วไปไม่ใช่เฉพาะสำหรับกรณีที่เลือก 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

ภารกิจคือการสร้างชุดค่าผสมทั้งหมดที่ตรงกับ n ส่วนเกิน k สัมประสิทธิ์ทวินามตอบคำถามว่ามีกี่วิธีในการเลือกชุดย่อยขององค์ประกอบ k ที่ไม่มีการเรียงลำดับจากชุดองค์ประกอบ n ที่แน่นอน ดังนั้นอัลกอริทึมที่เสนอจึงทำสิ่งที่ควร
Christoph

6

https://gist.github.com/3118596

มีการนำไปใช้สำหรับ JavaScript มันมีฟังก์ชั่นเพื่อรับชุดค่าผสม k และชุดค่าผสมของวัตถุใด ๆ ตัวอย่าง:

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

ที่นี่คุณมีรุ่นที่ประเมินขี้เกียจของอัลกอริทึมที่เข้ารหัสใน 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

ฉันมีอัลกอริทึมการเปลี่ยนแปลงที่ฉันใช้สำหรับโครงการออยเลอร์ในหลาม:

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
^ ^ ^
IJK

ขั้นแรกคุณต้องเปลี่ยนแปลง k ดังนั้นขั้นตอนต่อไปจะเป็นดังนี้:

ABCDEFGH
^ ^ ^
IJK

หากคุณถึงจุดสิ้นสุดคุณจะดำเนินการต่อและเปลี่ยนค่า j แล้วเปลี่ยน k อีกครั้ง

ABCDEFGH
^ ^ ^
IJK

ABCDEFGH
^ ^ ^
IJK

เมื่อคุณถึง G คุณจะเริ่มแตกต่างกันฉัน

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

อ้างอิงจากhttps://stackoverflow.com/a/127898/2628125แต่เป็นนามธรรมมากขึ้นสำหรับพอยน์เตอร์ขนาดใดก็ได้


ภาษาอันยิ่งใหญ่นี้คืออะไร? ทุบตี?
shinzou

1
php แต่ภาษาไม่สำคัญที่นี่อัลกอริทึมทำ
Oleksandr Knyga

ฉันดีใจมากที่ฉันปฏิเสธที่จะเรียนภาษานี้ ภาษาที่ล่าม / คอมไพเลอร์ต้องการความช่วยเหลือในการรับรู้ถึงตัวแปรที่ไม่ควรมีอยู่ในปี 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

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 อับดุล abe acd ace ade bcd bce bde cde


3

ฉันสร้างโซลูชันใน SQL Server 2005 สำหรับสิ่งนี้และโพสต์ไว้บนเว็บไซต์ของฉัน: http://www.jessemclain.com/downloads/code/sql/fn_GetMChooseNCombos.sql.htm

นี่คือตัวอย่างที่แสดงการใช้งาน:

SELECT * FROM dbo.fn_GetMChooseNCombos('ABCD', 2, '')

ผล:

Word
----
AB
AC
AD
BC
BD
CD

(6 row(s) affected)

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 ซึ่งจะคำนวณและส่งคืนการรวมกันทั้งหมดขององค์ประกอบ "NUM" จากองค์ประกอบ "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

โซลูชัน 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();
        }
    }
}

ทำไมมันทำงาน

มี bijection ระหว่างชุดย่อยของชุด n-element และลำดับ n-bit

นั่นหมายความว่าเราสามารถคิดได้ว่ามีชุดย่อยกี่ชุดด้วยการนับลำดับ

เช่นชุดสี่องค์ประกอบด้านล่างสามารถแสดงด้วย {0,1} X {0, 1} X {0, 1} X {0, 1} (หรือ 2 ^ 4) ลำดับที่แตกต่างกัน

ดังนั้น - สิ่งที่เราต้องทำคือนับจาก 1 ถึง 2 ^ n เพื่อค้นหาชุดค่าผสมทั้งหมด (เราไม่สนใจชุดว่าง) ถัดไปแปลตัวเลขเป็นตัวแทนไบนารีของพวกเขา จากนั้นแทนที่องค์ประกอบของชุดของคุณเป็นบิต 'เปิด'

หากคุณต้องการผลลัพธ์องค์ประกอบ k เท่านั้นพิมพ์เฉพาะเมื่อ k บิตเปิดใช้งาน

(ถ้าคุณต้องการเซตย่อยทั้งหมดแทนชุดย่อยความยาว k ให้ลบส่วน cnt / kElement)

(สำหรับการพิสูจน์ให้ดูบทเรียนคณิตศาสตร์ฟรีของ MIT สำหรับวิทยาการคอมพิวเตอร์ Lehman et al ส่วนที่ 11.2.2 https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics- สำหรับคอมพิวเตอร์วิทยาศาสตร์ฤดูใบไม้ร่วง 2010 / การอ่าน / )


3

รหัสหลามสั้น, ตำแหน่งดัชนีให้ผล

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. เอาต์พุต K-index ทั้งหมดในรูปแบบที่ดีสำหรับ N ที่เลือก K ไปยังไฟล์ ดัชนี K สามารถแทนที่ด้วยสตริงหรือตัวอักษรที่มีความหมายมากขึ้น วิธีนี้ทำให้การแก้ปัญหาประเภทนี้ค่อนข้างเล็กน้อย

  2. แปลง K-indexes เป็นดัชนีที่เหมาะสมของรายการในตารางสัมประสิทธิ์ทวินามที่เรียงลำดับ เทคนิคนี้เร็วกว่าเทคนิคที่เผยแพร่เก่ากว่าที่ต้องใช้การวนซ้ำ มันทำได้โดยใช้คุณสมบัติทางคณิตศาสตร์ที่มีอยู่ในสามเหลี่ยมปาสคาล กระดาษของฉันพูดเกี่ยวกับเรื่องนี้ ฉันเชื่อว่าฉันเป็นคนแรกที่ค้นพบและเผยแพร่เทคนิคนี้ แต่ฉันอาจผิด

  3. แปลงดัชนีในตารางสัมประสิทธิ์ทวินามที่เรียงลำดับเป็นดัชนี K ที่สอดคล้องกัน

  4. ใช้วิธีMark Dominusเพื่อคำนวณค่าสัมประสิทธิ์ทวินามซึ่งมีโอกาสน้อยมากที่จะล้นและทำงานกับจำนวนที่มากขึ้น

  5. คลาสนั้นถูกเขียนใน. NET C # และให้วิธีการจัดการวัตถุที่เกี่ยวข้องกับปัญหา (ถ้ามี) โดยใช้รายการทั่วไป ตัวสร้างของคลาสนี้ใช้ค่าบูลที่เรียกว่า InitTable ซึ่งเมื่อจริงจะสร้างรายการทั่วไปเพื่อเก็บวัตถุที่จะจัดการ หากค่านี้เป็นเท็จจะไม่มีการสร้างตาราง ไม่จำเป็นต้องสร้างตารางเพื่อดำเนินการตามวิธีทั้ง 4 ด้านบน มีการจัดเตรียมวิธีการเข้าถึงเพื่อเข้าถึงตาราง

  6. มีคลาสการทดสอบที่เกี่ยวข้องซึ่งแสดงวิธีใช้คลาสและวิธีการ มันได้รับการทดสอบอย่างกว้างขวางกับ 2 กรณีและไม่มีข้อบกพร่องที่รู้จักกัน

หากต้องการอ่านเกี่ยวกับคลาสนี้และดาวน์โหลดรหัสดูTablizing มีสองจำนวน Coeffieicent

ไม่ควรยากที่จะแปลงคลาสนี้เป็น C ++


มันไม่ถูกต้องที่จะเรียกมันว่า "วิธีการทำมาร์คโดมินัส" เพราะอย่างที่ฉันกล่าวถึงมันเป็นอย่างน้อย 850 ปีและไม่ยากอย่างที่คิด ทำไมไม่เรียกมันว่าLilavatiวิธี?
MJD
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.