Hasilkan Gabungan di Java

1. Pengenalan

Dalam tutorial ini, kita akan membincangkan penyelesaian masalah k-kombinasi di Java .

Pertama, kita akan membincangkan dan melaksanakan algoritma rekursif dan berulang untuk menghasilkan semua kombinasi ukuran tertentu. Kemudian kami akan mengkaji penyelesaian menggunakan perpustakaan Java biasa.

2. Tinjauan Gabungan

Ringkasnya, gabungan adalah subkumpulan elemen dari satu set tertentu .

Tidak seperti permutasi, urutan di mana kita memilih elemen individu tidak penting. Sebaliknya, kita hanya peduli sama ada elemen tertentu dalam pemilihan.

Sebagai contoh, dalam permainan kad, kita harus mengurus 5 kad dari bungkusan yang terdiri daripada 52 kad. Kami tidak berminat dengan urutan 5 kad yang dipilih. Sebaliknya, kami hanya memperhatikan kad yang ada di tangan.

Beberapa masalah memerlukan kita menilai semua kemungkinan kombinasi. Untuk melakukan ini, kami menghitung pelbagai kombinasi.

Bilangan cara yang berbeza untuk memilih elemen "r" dari kumpulan elemen "n" dapat dinyatakan secara matematik dengan formula berikut:

Oleh itu, bilangan cara untuk memilih elemen dapat berkembang secara pesat dalam keadaan terburuk. Oleh itu, untuk populasi yang besar, tidak mungkin menghitung pilihan yang berbeza.

Dalam kes seperti itu, kita boleh memilih beberapa pilihan wakil secara rawak. Proses itu disebut persampelan .

Seterusnya, kami akan mengkaji pelbagai algoritma untuk menyenaraikan kombinasi.

3. Algoritma Rekursif untuk Menghasilkan Gabungan

Algoritma rekursif biasanya berfungsi dengan membahagi masalah menjadi masalah kecil yang serupa. Proses ini berterusan sehingga kita mencapai keadaan penamatan, yang juga merupakan asas kes. Kemudian kami menyelesaikan kes asas secara langsung.

Kami akan membincangkan dua cara untuk membahagikan tugas memilih elemen dari satu set. Pendekatan pertama membahagikan masalah dari segi elemen dalam himpunan. Pendekatan kedua membahagikan masalah dengan mengesan elemen terpilih sahaja.

3.1. Pemisahan oleh Elemen dalam Keseluruhan Set

Mari bahagikan tugas memilih elemen "r" dari item "n" dengan memeriksa item satu persatu. Untuk setiap item dalam set, kita boleh memasukkannya ke dalam pilihan atau mengecualikannya.

Sekiranya kita memasukkan item pertama, maka kita perlu memilih elemen "r - 1" dari item " n - 1" yang tersisa . Sebaliknya, jika kita membuang item pertama, maka kita perlu memilih elemen "r" dari item " n - 1" yang tersisa .

Ini dapat dinyatakan secara matematik sebagai:

Sekarang, mari kita lihat pelaksanaan pendekatan ini secara berulang:

private void helper(List combinations, int data[], int start, int end, int index) { if (index == data.length) { int[] combination = data.clone(); combinations.add(combination); } else if (start <= end) { data[index] = start; helper(combinations, data, start + 1, end, index + 1); helper(combinations, data, start + 1, end, index); } }

The penolong Cara membuat dua panggilan rekursi kepada dirinya. Panggilan pertama merangkumi elemen semasa. Panggilan kedua membuang elemen semasa.

Seterusnya, mari tulis penjana kombinasi menggunakan kaedah pembantu ini :

public List generate(int n, int r) { List combinations = new ArrayList(); helper(combinations, new int[r], 0, n-1, 0); return combinations; }

Dalam kod di atas, kaedah menghasilkan menetapkan panggilan pertama ke kaedah pembantu dan melewati parameter yang sesuai.

Seterusnya, mari kita panggil kaedah ini untuk menghasilkan kombinasi:

List combinations = generate(N, R); for (int[] combination : combinations) { System.out.println(Arrays.toString(combination)); } System.out.printf("generated %d combinations of %d items from %d ", combinations.size(), R, N);

Semasa melaksanakan program, kami mendapat output berikut:

[0, 1] [0, 2] [0, 3] [0, 4] [1, 2] [1, 3] [1, 4] [2, 3] [2, 4] [3, 4] generated 10 combinations of 2 items from 5

Akhirnya, mari kita tulis kes ujian:

@Test public void givenSetAndSelectionSize_whenCalculatedUsingSetRecursiveAlgorithm_thenExpectedCount() { SetRecursiveCombinationGenerator generator = new SetRecursiveCombinationGenerator(); List selection = generator.generate(N, R); assertEquals(nCr, selection.size()); }

Sangat mudah untuk diperhatikan bahawa ukuran timbunan yang diperlukan adalah jumlah elemen dalam set. Apabila jumlah elemen dalam kumpulan besar, katakanlah, lebih besar daripada kedalaman timbunan panggilan maksimum, kita akan melimpah tumpukan dan mendapat StackOverflowError .

Oleh itu, pendekatan ini tidak berfungsi jika set inputnya besar.

3.2. Pembahagian oleh Elemen dalam Gabungan

Daripada melacak elemen dalam set input, kami akan membahagikan tugas dengan mengesan item dalam pilihan .

Pertama, mari memesan item dalam set input menggunakan indeks "1" hingga " n" . Sekarang, kita boleh memilih item pertama dari item " n-r + 1" pertama .

Mari kita anggap bahawa kita memilih item kth . Kemudian, kita perlu memilih item " r - 1" dari baki item " n - k" yang diindeks " k + 1" ke " n" .

Kami menyatakan proses ini secara matematik sebagai:

Seterusnya, mari tulis kaedah rekursif untuk melaksanakan pendekatan ini:

private void helper(List combinations, int data[], int start, int end, int index) { if (index == data.length) { int[] combination = data.clone(); combinations.add(combination); } else { int max = Math.min(end, end + 1 - data.length + index); for (int i = start; i <= max; i++) { data[index] = i; helper(combinations, data, i + 1, end, index + 1); } } }

Dalam kod di atas, for for loop memilih item seterusnya, Kemudian, ia memanggil kaedah helper () secara berulang untuk memilih item yang tinggal . Kami berhenti apabila jumlah item yang diperlukan telah dipilih.

Seterusnya, mari kita gunakan kaedah penolong untuk menghasilkan pilihan:

public List generate(int n, int r) { List combinations = new ArrayList(); helper(combinations, new int[r], 0, n - 1, 0); return combinations; }

Akhirnya, mari kita tulis kes ujian:

@Test public void givenSetAndSelectionSize_whenCalculatedUsingSelectionRecursiveAlgorithm_thenExpectedCount() { SelectionRecursiveCombinationGenerator generator = new SelectionRecursiveCombinationGenerator(); List selection = generator.generate(N, R); assertEquals(nCr, selection.size()); }

Ukuran timbunan panggilan yang digunakan oleh pendekatan ini sama dengan jumlah elemen dalam pemilihan. Oleh itu, pendekatan ini dapat berfungsi untuk input yang besar selagi jumlah elemen yang akan dipilih kurang dari kedalaman timbunan panggilan maksimum.

Sekiranya bilangan elemen yang akan dipilih juga besar, kaedah ini tidak akan berfungsi.

4. Algoritma Iteratif

Dalam pendekatan berulang, kita mulakan dengan kombinasi awal. Kemudian, kami terus menghasilkan kombinasi seterusnya dari yang sekarang sehingga kami telah menghasilkan semua kombinasi .

Mari buat kombinasi dalam susunan leksikografi. Kita mulakan dengan kombinasi leksikografi terendah.

Untuk mendapatkan kombinasi seterusnya dari yang sekarang, kita dapati lokasi paling tepat dalam kombinasi semasa yang dapat ditingkatkan. Kemudian, kami menambah lokasi dan menghasilkan gabungan leksikografi serendah mungkin di sebelah kanan lokasi tersebut.

Mari tulis kod yang mengikuti pendekatan ini:

public List generate(int n, int r) { List combinations = new ArrayList(); int[] combination = new int[r]; // initialize with lowest lexicographic combination for (int i = 0; i < r; i++) { combination[i] = i; } while (combination[r - 1] < n) { combinations.add(combination.clone()); // generate next combination in lexicographic order int t = r - 1; while (t != 0 && combination[t] == n - r + t) { t--; } combination[t]++; for (int i = t + 1; i < r; i++) { combination[i] = combination[i - 1] + 1; } } return combinations; }

Seterusnya, mari kita tulis kes ujian:

@Test public void givenSetAndSelectionSize_whenCalculatedUsingIterativeAlgorithm_thenExpectedCount() { IterativeCombinationGenerator generator = new IterativeCombinationGenerator(); List selection = generator.generate(N, R); assertEquals(nCr, selection.size()); }

Sekarang, marilah kita menggunakan beberapa perpustakaan Java untuk menyelesaikan masalahnya.

5. Perpustakaan Java Melaksanakan Gabungan

Seboleh-bolehnya, kita harus menggunakan semula pelaksanaan perpustakaan yang ada dan bukannya melancarkan sendiri. Di bahagian ini, kita akan meneroka perpustakaan Java berikut yang menerapkan kombinasi:

  • Apache Commons
  • Jambu batu
  • CombinatoricsLib

5.1. Apache Commons

The CombinatoricsUtils class from Apache Commons provides many combination utility functions. In particular, the combinationsIterator method returns an iterator that will generate combinations in lexicographic order.

First, let's add the Maven dependency commons-math3 to the project:

 org.apache.commons commons-math3 3.6.1 

Next, let's use the combinationsIterator method to print the combinations:

public static void generate(int n, int r) { Iterator iterator = CombinatoricsUtils.combinationsIterator(n, r); while (iterator.hasNext()) { final int[] combination = iterator.next(); System.out.println(Arrays.toString(combination)); } }

5.2. Google Guava

The Sets class from Guava library provides utility methods for set-related operations. The combinations method returns all subsets of a given size.

First, let's add the maven dependency for the Guava library to the project:

 com.google.guava guava 27.0.1-jre 

Next, let's use the combinations method to generate combinations:

Set
    
      combinations = Sets.combinations(ImmutableSet.of(0, 1, 2, 3, 4, 5), 3);
    

Here, we are using the ImmutableSet.of method to create a set from the given numbers.

5.3. CombinatoricsLib

CombinatoricsLib is a small and simple Java library for permutations, combinations, subsets, integer partitions, and cartesian product.

To use it in the project, let's add the combinatoricslib3 Maven dependency:

 com.github.dpaukov combinatoricslib3 3.3.0 

Next, let's use the library to print the combinations:

Generator.combination(0, 1, 2, 3, 4, 5) .simple(3) .stream() .forEach(System.out::println);

This produces the following output on execution:

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

More examples are available at combinatoricslib3-example.

6. Conclusion

In this article, we implemented a few algorithms to generate combinations.

Kami juga mengkaji beberapa pelaksanaan perpustakaan. Lazimnya, kami akan menggunakan ini daripada melancarkan sendiri.

Seperti biasa, kod sumber lengkap boleh didapati di GitHub.