Algoritma Boruvka untuk Pokok Rentang Minimum di Jawa

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan melihat pelaksanaan Java algoritma Boruvka untuk mencari Minimum Spanning Tree (MST) graf berwajaran tepi .

Ini mendahului algoritma Prim dan Kruskal, tetapi masih boleh dianggap sebagai persilangan antara keduanya.

2. Algoritma Boruvka

Kami akan melompat ke algoritma yang ada. Mari lihat sedikit sejarah dan kemudian algoritma itu sendiri.

2.1. Sejarah

Satu kaedah untuk mencari MST dari grafik yang diberikan pertama kali dirumuskan oleh Otakar Boruvka pada tahun 1926. Ini adalah cara sebelum komputer wujud, dan sebenarnya dimodelkan untuk merancang sistem pengagihan elektrik yang cekap.

Georges Sollin menjumpainya semula pada tahun 1965 dan menggunakannya dalam pengkomputeran selari.

2.2. Algoritma

Idea utama algoritma adalah bermula dengan sekumpulan pokok dengan setiap bucu mewakili pokok terpencil. Kemudian, kita perlu terus menambahkan tepi untuk mengurangkan bilangan pokok terpencil sehingga kita mempunyai satu pokok yang bersambung.

Mari lihat ini secara berperingkat dengan graf contoh:

  • Langkah 0: buat grafik
  • Langkah 1: mulakan dengan sekumpulan pokok yang tidak bersambung (bilangan pokok = bilangan bucu)
  • Langkah 2: sementara ada pokok yang tidak bersambung, untuk setiap pokok yang tidak bersambung:
    • cari kelebihannya dengan berat badan yang lebih rendah
    • tambahkan tepi ini untuk menghubungkan pokok lain

3. Pelaksanaan Java

Sekarang mari kita lihat bagaimana kita dapat melaksanakannya di Java.

3.1. The UnionFind Struktur Data

Sebagai permulaan, kita memerlukan struktur data untuk menyimpan ibu bapa dan barisan bucu kita .

Mari tentukan kelas UnionFind untuk tujuan ini, dengan dua kaedah: penyatuan , dan cari :

public class UnionFind { private int[] parents; private int[] ranks; public UnionFind(int n) { parents = new int[n]; ranks = new int[n]; for (int i = 0; i < n; i++) { parents[i] = i; ranks[i] = 0; } } public int find(int u) { while (u != parents[u]) { u = parents[u]; } return u; } public void union(int u, int v) { int uParent = find(u); int vParent = find(v); if (uParent == vParent) { return; } if (ranks[uParent]  ranks[vParent]) { parents[vParent] = uParent; } else { parents[vParent] = uParent; ranks[uParent]++; } } } 

Kami mungkin menganggap kelas ini sebagai struktur penolong untuk mengekalkan hubungan antara bucu kami dan secara beransur-ansur membina MST kami.

Untuk mengetahui sama ada dua bucu u dan v tergolong dalam pokok yang sama, kita melihat sama ada find (u) mengembalikan induk yang sama dengan find (v) . The kesatuan kaedah yang digunakan untuk menggabungkan pokok. Kami akan melihat penggunaan ini sebentar lagi.

3.2. Masukkan Graf Dari Pengguna

Sekarang kita memerlukan cara untuk mendapatkan titik dan sudut grafik dari pengguna dan memetakannya ke objek yang dapat kita gunakan dalam algoritma kita pada waktu berjalan.

Oleh kerana kami akan menggunakan JUnit untuk menguji algoritma kami, bahagian ini menggunakan kaedah @Sebelum ini:

@Before public void setup() { graph = ValueGraphBuilder.undirected().build(); graph.putEdgeValue(0, 1, 8); graph.putEdgeValue(0, 2, 5); graph.putEdgeValue(1, 2, 9); graph.putEdgeValue(1, 3, 11); graph.putEdgeValue(2, 3, 15); graph.putEdgeValue(2, 4, 10); graph.putEdgeValue(3, 4, 7); } 

Di sini, kami telah menggunakan Guava's MutableValueGraph untuk menyimpan grafik kami. Kemudian kami menggunakan ValueGraphBuilder untuk membina graf berwajaran tidak terarah.

Kaedah putEdgeValue mengambil tiga argumen, dua Integer untuk bucu, dan Integer ketiga untuk beratnya, seperti yang ditentukan oleh deklarasi jenis generik MutableValueGraph .

Seperti yang kita lihat, ini adalah input yang sama seperti yang ditunjukkan dalam rajah kita dari sebelumnya.

3.3. Terbitkan Pokok Rentang Minimum

Akhirnya, kita sampai pada inti masalah, pelaksanaan algoritma.

Kami akan melakukan ini di kelas yang akan kami panggil BoruvkaMST . Pertama, mari kita nyatakan beberapa pemboleh ubah contoh:

public class BoruvkaMST { private static MutableValueGraph mst = ValueGraphBuilder.undirected().build(); private static int totalWeight; } 

Seperti yang dapat kita lihat, kita menggunakan MutableValueGraph di sini untuk mewakili MST.

Kedua, kita akan menentukan konstruktor, di mana semua sihir berlaku. Ia memerlukan satu argumen - grafik yang kami bina sebelumnya.

Perkara pertama yang dilakukannya adalah untuk memulakan UnionFind dari simpul grafik input. Pada mulanya, semua simpul adalah ibu bapa mereka sendiri, masing-masing dengan kedudukan 0:

public BoruvkaMST(MutableValueGraph graph) { int size = graph.nodes().size(); UnionFind uf = new UnionFind(size); 

Seterusnya, kami akan membuat gelung yang menentukan bilangan lelaran yang diperlukan untuk membuat MST - paling banyak masa log V atau sehingga kami mempunyai tepi V-1, di mana V adalah bilangan bucu:

for (int t = 1; t < size && mst.edges().size() < size - 1; t = t + t) { EndpointPair[] closestEdgeArray = new EndpointPair[size]; 

Di sini kita juga menginisialisasi susunan tepi, terdekatEdgeArray - untuk menyimpan tepi yang paling dekat dan kurang berat.

Selepas itu, kami akan menentukan bahagian dalam untuk gelung untuk mengulangi semua tepi grafik untuk mengisiEdgeArray terdekat kami .

Sekiranya ibu bapa dari dua bucu itu sama, ia adalah pokok yang sama dan kami tidak menambahkannya ke susunan. Jika tidak, kami membandingkan berat tepi semasa dengan berat tepi bucu induknya. Sekiranya lebih rendah, maka kita tambahkan ke terdekatEdgeArray:

for (EndpointPair edge : graph.edges()) { int u = edge.nodeU(); int v = edge.nodeV(); int uParent = uf.find(u); int vParent = uf.find(v); if (uParent == vParent) { continue; } int weight = graph.edgeValueOrDefault(u, v, 0); if (closestEdgeArray[uParent] == null) { closestEdgeArray[uParent] = edge; } if (closestEdgeArray[vParent] == null) { closestEdgeArray[vParent] = edge; } int uParentWeight = graph.edgeValueOrDefault(closestEdgeArray[uParent].nodeU(), closestEdgeArray[uParent].nodeV(), 0); int vParentWeight = graph.edgeValueOrDefault(closestEdgeArray[vParent].nodeU(), closestEdgeArray[vParent].nodeV(), 0); if (weight < uParentWeight) { closestEdgeArray[uParent] = edge; } if (weight < vParentWeight) { closestEdgeArray[vParent] = edge; } } 

Kemudian, kita akan menentukan gelung dalaman kedua untuk membuat pokok. Kami akan menambah tepi dari langkah di atas ke pokok ini tanpa menambahkan tepi yang sama dua kali. Selain itu, kami akan melakukan kesatuan di UnionFind kami untuk mendapatkan dan menyimpan ibu bapa dan barisan bucu pokok yang baru dibuat:

for (int i = 0; i < size; i++) { EndpointPair edge = closestEdgeArray[i]; if (edge != null) { int u = edge.nodeU(); int v = edge.nodeV(); int weight = graph.edgeValueOrDefault(u, v, 0); if (uf.find(u) != uf.find(v)) { mst.putEdgeValue(u, v, weight); totalWeight += weight; uf.union(u, v); } } } 

Setelah mengulangi langkah-langkah ini pada kebanyakan masa log V atau sehingga kita mempunyai tepi V-1, pokok yang dihasilkan adalah MST kita.

4. Ujian

Akhirnya, mari kita lihat JUnit mudah untuk mengesahkan pelaksanaan kami:

@Test public void givenInputGraph_whenBoruvkaPerformed_thenMinimumSpanningTree() { BoruvkaMST boruvkaMST = new BoruvkaMST(graph); MutableValueGraph mst = boruvkaMST.getMST(); assertEquals(30, boruvkaMST.getTotalWeight()); assertEquals(4, mst.getEdgeCount()); } 

Seperti yang kita lihat, kita mendapat MST dengan berat 30 dan 4 tepi, sama dengan contoh bergambar .

5. Kesimpulan

Dalam tutorial ini, kami melihat pelaksanaan Java Algoritma Boruvka. Kerumitan waktunya adalah O (E log V), di mana E adalah bilangan tepi dan V adalah bilangan bucu .

Seperti biasa, kod sumber tersedia di GitHub.