Bab Graph telah membekali kita dengan adjacency list dan adjacency matrix; sekarang saatnya meletakkan algoritma di atasnya. Banyak persoalan dunia nyata berbentuk jaringan — rute pengiriman, prasyarat mata kuliah, koneksi antar server — dan tiga pertanyaan muncul berulang kali: berapa lompatan minimum antara dua simpul, apakah jaringan memuat siklus, dan jalur termurah mana pada jaringan yang tiap sisinya punya bobot. BFS menjawab pertanyaan pertama, DFS menjawab yang kedua, dan Dijkstra dengan PriorityQueue menjawab yang ketiga. Bab ini menerapkan ketiganya pada graph kota Java dengan data tetap, lalu membandingkan kompleksitasnya dalam satu tabel.
BFS untuk Jarak Paling Sedikit Lompatan #
Breadth-First Search (BFS) menjelajahi graph berlapis-lapis: simpul awal, lalu semua tetangganya, lalu tetangga dari tetangganya. Sifat berlapis inilah yang membuat BFS cocok mencari jarak pada graph tak berbobot — simpul yang lebih dulu masuk antrean selalu memiliki jarak lompatan yang paling kecil. Program berikut menghitung jarak setiap kota dari Jakarta dan merekonstruksi jalurnya:
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class BfsJalur {
// Adjacency list kota tak berarah; satu sisi = satu lompatan.
private final Map<String, List<String>> tetangga = new LinkedHashMap<>();
public static void main(String[] args) {
BfsJalur graph = new BfsJalur();
graph.tambahSisi("Jakarta", "Bandung");
graph.tambahSisi("Jakarta", "Semarang");
graph.tambahSisi("Bandung", "Yogyakarta");
graph.tambahSisi("Semarang", "Yogyakarta");
graph.tambahSisi("Semarang", "Surabaya");
graph.tambahSisi("Yogyakarta", "Surabaya");
Map<String, Integer> jarakLompatan = graph.jarakLompatan("Jakarta");
System.out.println("=== BFS dari Jakarta: jarak minimum dalam lompatan ===");
for (Map.Entry<String, Integer> e : jarakLompatan.entrySet()) {
System.out.printf(" %-10s %d lompatan%n", e.getKey(), e.getValue());
}
System.out.println();
System.out.println("Jalur ke Surabaya : " + graph.jalurKe("Surabaya", jarakLompatan));
System.out.println("Jalur ke Yogyakarta: " + graph.jalurKe("Yogyakarta", jarakLompatan));
}
void tambahSisi(String a, String b) {
tetangga.computeIfAbsent(a, k -> new ArrayList<>()).add(b);
tetangga.computeIfAbsent(b, k -> new ArrayList<>()).add(a);
}
// BFS: setiap kota diproses berdasarkan urutan masuk antrean,
// sehingga jarak pertama yang tercatat selalu yang paling sedikit lompatan.
Map<String, Integer> jarakLompatan(String mulai) {
Map<String, Integer> jarak = new LinkedHashMap<>();
Deque<String> antrean = new ArrayDeque<>();
jarak.put(mulai, 0);
antrean.add(mulai);
while (!antrean.isEmpty()) {
String kota = antrean.poll();
for (String tetanggaKota : tetangga.get(kota)) {
if (!jarak.containsKey(tetanggaKota)) {
jarak.put(tetanggaKota, jarak.get(kota) + 1);
antrean.add(tetanggaKota);
}
}
}
return jarak;
}
// Jalur direkonstruksi dengan berjalan mundur: cari tetangga
// yang jaraknya tepat satu lebih kecil, lalu ulangi sampai titik awal.
List<String> jalurKe(String tujuan, Map<String, Integer> jarak) {
List<String> jalur = new ArrayList<>(List.of(tujuan));
String kota = tujuan;
while (jarak.get(kota) > 0) {
for (String kandidat : tetangga.get(kota)) {
if (jarak.get(kandidat) == jarak.get(kota) - 1) {
jalur.add(0, kandidat);
kota = kandidat;
break;
}
}
}
return jalur;
}
}Output yang Dihasilkan:
=== BFS dari Jakarta: jarak minimum dalam lompatan ===
Jakarta 0 lompatan
Bandung 1 lompatan
Semarang 1 lompatan
Yogyakarta 2 lompatan
Surabaya 2 lompatan
Jalur ke Surabaya : [Jakarta, Semarang, Surabaya]
Jalur ke Yogyakarta: [Jakarta, Bandung, Yogyakarta]Hasilnya sesuai intuisi: Surabaya berjarak dua lompatan melalui Semarang, Yogyakarta juga dua lompatan melalui Bandung. Trik pentingnya ada pada gabungan Map dan antrean — peta jarak sekaligus berperan sebagai penanda visited, karena kunci yang sudah ada tidak akan ditimpa.
Bedah baris-per-baris untuk bagian intinya:
- Baris 43-45 (
jarak.put(mulai, 0); antrean.add(mulai)): BFS dimulai dengan satu simpul berjarak nol. Konstruksi ini menjaga invarian penting: setiap elemen di antrean sudah punya jarak final. - Baris 48 (
String kota = antrean.poll()): antrean FIFO (first in first out) mengeluarkan simpul yang paling lama menunggu. Karena penyisipan dilakukan berlapis, urutan keluar mengikuti urutan jarak — inilah mekanisme yang membuat BFS menemukan jarak minimum tanpa perlu menimbang apa pun. - Baris 50 (
if (!jarak.containsKey(tetanggaKota))): kandidat yang sudah punya jarak dilewati. Jarak pertama yang tercatat tidak mungkin diperbaiki lagi pada graph tak berbobot, sehingga pemeriksaan ini sekaligus mencegah simpul masuk antrean dua kali. - Baris 51-52 (
jarak.put(...); antrean.add(...)): tetangga baru mendapat jarak = jarak simpul sekarang + 1, lalu mengantre. Setiap sisi dan simpun dikunjungi tepat satu kali, sehingga total kompleksitas O(V + E).
DFS dan Deteksi Siklus #
Depth-First Search (DFS) menempuh jalur yang berbeda: menelusuri sejauh mungkin ke satu arah sebelum mundur (backtrack). Karakter inilah yang dimanfaatkan untuk mendeteksi siklus pada directed graph — penelusuran yang menemukan simpul yang sedang diproses berarti ada jalur melingkar. Contoh nyatanya adalah prasyarat mata kuliah: bila Algoritma menuntut StrukturData dan StrukturData menuntut Algoritma, tidak ada urutan pengambilan yang sah. Program berikut memakai pewarnaan tiga status:
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class DfsSiklus {
// Warna status DFS pada directed graph:
// 1 = putih (belum dikunjungi), 2 = abu-abu (sedang diproses), 3 = hitam (selesai).
private static final int PUTIH = 1, ABU = 2, HITAM = 3;
private final Map<String, List<String>> sisi = new LinkedHashMap<>();
private final Map<String, Integer> status = new LinkedHashMap<>();
public static void main(String[] args) {
DfsSiklus matakuliah = new DfsSiklus();
matakuliah.prasyarat("DasarPemrograman", "OOP");
matakuliah.prasyarat("DasarPemrograman", "StrukturData");
matakuliah.prasyarat("StrukturData", "Algoritma");
matakuliah.prasyarat("OOP", "BasisData");
matakuliah.prasyarat("Algoritma", "KeamananAplikasi");
System.out.println("=== Kartu rencana studi tanpa siklus ===");
System.out.println("Siklus terdeteksi: " + matakuliah.adaSiklus());
// Tambahkan prasyarat balik: Algoritma menuntut DasarPemrograman.
matakuliah.prasyarat("Algoritma", "DasarPemrograman");
System.out.println();
System.out.println("=== Setelah menambah sisi Algoritma -> DasarPemrograman ===");
System.out.println("Siklus terdeteksi: " + matakuliah.adaSiklus());
}
void prasyarat(String dari, String ke) {
sisi.computeIfAbsent(dari, k -> new ArrayList<>()).add(ke);
sisi.computeIfAbsent(ke, k -> new ArrayList<>());
}
boolean adaSiklus() {
for (String node : sisi.keySet()) {
status.put(node, PUTIH);
}
for (String node : sisi.keySet()) {
if (status.get(node) == PUTIH && dfsDeteksi(node)) {
return true;
}
}
return false;
}
// DFS rekursif: node berstatus abu-abu berarti node itu berada
// di jalur penelusuran saat ini, jadi sisi menuju node itu menutup siklus.
private boolean dfsDeteksi(String node) {
status.put(node, ABU);
for (String tujuan : sisi.get(node)) {
if (status.get(tujuan) == ABU) {
System.out.println(" Sisi " + node + " -> " + tujuan
+ " kembali ke node yang masih diproses");
return true;
}
if (status.get(tujuan) == PUTIH && dfsDeteksi(tujuan)) {
return true;
}
}
status.put(node, HITAM);
return false;
}
}Output yang Dihasilkan:
=== Kartu rencana studi tanpa siklus ===
Siklus terdeteksi: false
=== Setelah menambah sisi Algoritma -> DasarPemrograman ===
Sisi Algoritma -> DasarPemrograman kembali ke node yang masih diproses
Siklus terdeteksi: trueSkenario pertama menghasilkan false — kelima mata kuliah membentuk hierarki yang bersih. Skenario kedua baru menangkap siklus setelah sisi balik Algoritma → DasarPemrograman ditambahkan, dan program melaporkan sisi mana yang menutup lingkaran itu.
Mari kita bedah pewarnaan statusnya:
- Baris 61 (
status.put(node, ABU)): simpul dicat abu-abu saat penelusuran masuk ke simpul itu. Selama masih abu-abu, simpul berada di call stack rekursi — artinya simpul adalah bagian dari jalur yang sedang diperiksa. - Baris 64-68 (
if (status.get(tujuan) == ABU)): sisi yang menuju simpul abu-abu membentuk jalur melingkar, karena simpul tujuan adalah leluhur dari simpul asal pada pohon rekursi. Pemeriksaan inilah deteksi siklus sesungguhnya. - Baris 74 (
status.put(node, HITAM)): simpul dicat hitam setelah semua keturunannya selesai ditelusuri. Sisi menuju simpul hitam aman dilewati — jalur itu sudah pernah dijelajahi menyeluruh dan terbukti tidak mengandung siklus yang melewati simpul sekarang.
Dijkstra pada Graph Berbobot #
Ketika setiap sisi membawa bobot — kilometer, tarif, waktu — BFS tidak lagi memadai karena dua lompatan bisa lebih mahal daripada lima lompatan. Algoritma Dijkstra menyelesaikannya dengan PriorityQueue: simpul dengan total jarak sementara terkecil selalu diproses lebih dulu, dan setiap relaksasi sisi dapat memperbarui jarak sementara tetangganya. Program berikut mencari rute termurah dari Jakarta ke seluruh kota pada peta sembilan jalan:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
public class DijkstraKota {
// Sisi berbobot: kota tujuan dan jarak tempuh dalam kilometer.
record Sisi(String tujuan, int jarak) { }
// Entri antrean prioritas: kota dan jarak sementara terbaik yang diketahui.
record Entri(String kota, int jarakSementara) { }
private final Map<String, List<Sisi>> jalan = new LinkedHashMap<>();
private final Map<String, String> induk = new HashMap<>(); // untuk rekonstruksi rute
public static void main(String[] args) {
DijkstraKota peta = new DijkstraKota();
peta.tambahJalan("Jakarta", "Bandung", 150);
peta.tambahJalan("Jakarta", "Semarang", 450);
peta.tambahJalan("Jakarta", "Surabaya", 810);
peta.tambahJalan("Bandung", "Yogyakarta", 430);
peta.tambahJalan("Semarang", "Yogyakarta", 125);
peta.tambahJalan("Semarang", "Surabaya", 350);
peta.tambahJalan("Yogyakarta", "Surabaya", 260);
peta.tambahJalan("Surabaya", "Denpasar", 500);
peta.tambahJalan("Yogyakarta", "Denpasar", 900);
Map<String, Integer> jarak = peta.jarakTerpendek("Jakarta");
System.out.println("=== Dijkstra dari Jakarta (jarak dalam km) ===");
for (Map.Entry<String, Integer> e : jarak.entrySet()) {
System.out.printf(" %-10s %d km%n", e.getKey(), e.getValue());
}
System.out.println();
System.out.println("Rute ke Denpasar : " + peta.ruteKe("Denpasar"));
System.out.println("Rute ke Yogyakarta: " + peta.ruteKe("Yogyakarta"));
}
void tambahJalan(String dari, String ke, int jarak) {
jalan.computeIfAbsent(dari, k -> new ArrayList<>()).add(new Sisi(ke, jarak));
jalan.computeIfAbsent(ke, k -> new ArrayList<>());
}
Map<String, Integer> jarakTerpendek(String mulai) {
Map<String, Integer> jarak = new LinkedHashMap<>();
Map<String, String> induk = new HashMap<>();
for (String kota : jalan.keySet()) {
jarak.put(kota, Integer.MAX_VALUE);
}
jarak.put(mulai, 0);
PriorityQueue<Entri> antrean = new PriorityQueue<>(
Comparator.comparingInt(Entri::jarakSementara));
antrean.add(new Entri(mulai, 0));
while (!antrean.isEmpty()) {
Entri sekarang = antrean.poll();
if (sekarang.jarakSementara() > jarak.get(sekarang.kota())) {
continue; // Entri basi: kota ini sudah ditemukan lewat rute lebih pendek.
}
for (Sisi sisi : jalan.get(sekarang.kota())) {
int kandidat = sekarang.jarakSementara() + sisi.jarak();
if (kandidat < jarak.get(sisi.tujuan())) {
jarak.put(sisi.tujuan(), kandidat);
induk.put(sisi.tujuan(), sekarang.kota());
antrean.add(new Entri(sisi.tujuan(), kandidat));
}
}
}
return jarak;
}
String ruteKe(String tujuan) {
List<String> rute = new ArrayList<>(List.of(tujuan));
String kota = tujuan;
while (induk.containsKey(kota)) {
kota = induk.get(kota);
rute.add(0, kota);
}
return String.join(" -> ", rute);
}
}Output yang Dihasilkan:
=== Dijkstra dari Jakarta (jarak dalam km) ===
Jakarta 0 km
Bandung 150 km
Semarang 450 km
Surabaya 800 km
Yogyakarta 575 km
Denpasar 1300 km
Rute ke Denpasar : Denpasar
Rute ke Yogyakarta: YogyakartaOutput memperlihatkan jebakan jalan langsung: sisi Jakarta → Surabaya sepanjang 810 km kalah oleh rute memutar Jakarta → Semarang → Surabaya yang totalnya hanya 800 km. Denpasar pun ternyata paling murah dicapai lewat Surabaya (total 1300 km), bukan lewat jalan langsung dari Yogyakarta yang berharga 900 km dari Yogyakarta ditambah 575 km perjalanan ke sana.
Bedah baris-per-baris untuk mesin Dijkstra-nya:
- Baris 58-62 (inisialisasi
Integer.MAX_VALUE): semua kota dianggap tak terjangkau kecuali titik awal berjarak nol. Nilai sentinelInteger.MAX_VALUEmembuat relaksasi pertama selalu berhasil. - Baris 65-66 (
PriorityQueuedenganComparator.comparingInt): recordEntridibandingkan berdasarkan jarak sementaranya, sehinggapoll()selalu mengembalikan kota termurah yang belum final. Bandingkan dengan BFS yang memakai antrean FIFO — perbedaan kecil ini yang mengubah kemampuan algoritma. - Baris 70-72 (lewatkan entri basi): satu kota bisa masuk antrean berkali-kali setiap kali jarak sementaranya membaik. Entri lama dikenali dari
jarakSementarayang lebih besar daripada nilai terkini di petajarak, lalu dilewati. Pendekatan lazy deletion ini lebih sederhana daripada memindahkan elemen di dalam heap. - Baris 74-79 (relaksasi sisi): untuk setiap tetangga,
kandidat= jarak simpul sekarang + bobot sisi. Bila kandidat lebih murah dari catatan lama, petajarakdiperbarui, petaindukmerekam asal rute, dan entri baru masuk antrean. - Baris 84-91 (
ruteKe): rute dibangun mundur dari tujuan ke titik awal mengikuti petainduk, lalu dibalik dengan menyisipkan di posisi nol. Teknik rekonstruksi ini sama persis dengan yang dipakai BFS — hanya sumber jejaknya berbeda.
Perbandingan Kompleksitas #
Ketiga algoritma memindai graph dengan cara berbeda, dan biayanya pun berbeda:
| Algoritma | Struktur inti | Kompleksitas waktu | Menjawab |
|---|---|---|---|
| BFS | Antrean FIFO | O(V + E) | Jarak lompatan minimum pada graph tak berbobot |
| DFS | Call stack (rekursi atau stack eksplisit) | O(V + E) | Siklus, urutan topologis, komponen terhubung |
| Dijkstra | PriorityQueue (binary heap) | O((V + E) log V) | Jarak termurah pada graph berbobot non-negatif |
Ketiga algoritma ini menutup daftar teknik klasik pada Section. Seluruh bekal sudah di tangan: kompleksitas, struktur data, rekursi, greedy, DP, dan kini graph. Bab terakhir mengubah kumpulan teknik itu menjadi praktik rekayasa — dari spesifikasi berisi kalimat, menjadi class, method, dan pengujian yang rapi dalam bahasa Java.
Referensi #
- Cormen dkk., Introduction to Algorithms (CLRS), Bab 20-22 Elementary Graph Algorithms dan Single-Source Shortest Paths
- Oracle: java.util PriorityQueue
- Oracle: java.util ArrayDeque
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
BfsJalur.java
Unduh berkasimport java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class BfsJalur {
// Adjacency list kota tak berarah; satu sisi = satu lompatan.
private final Map<String, List<String>> tetangga = new LinkedHashMap<>();
public static void main(String[] args) {
BfsJalur graph = new BfsJalur();
graph.tambahSisi("Jakarta", "Bandung");
graph.tambahSisi("Jakarta", "Semarang");
graph.tambahSisi("Bandung", "Yogyakarta");
graph.tambahSisi("Semarang", "Yogyakarta");
graph.tambahSisi("Semarang", "Surabaya");
graph.tambahSisi("Yogyakarta", "Surabaya");
Map<String, Integer> jarakLompatan = graph.jarakLompatan("Jakarta");
System.out.println("=== BFS dari Jakarta: jarak minimum dalam lompatan ===");
for (Map.Entry<String, Integer> e : jarakLompatan.entrySet()) {
System.out.printf(" %-10s %d lompatan%n", e.getKey(), e.getValue());
}
System.out.println();
System.out.println("Jalur ke Surabaya : " + graph.jalurKe("Surabaya", jarakLompatan));
System.out.println("Jalur ke Yogyakarta: " + graph.jalurKe("Yogyakarta", jarakLompatan));
}
void tambahSisi(String a, String b) {
tetangga.computeIfAbsent(a, k -> new ArrayList<>()).add(b);
tetangga.computeIfAbsent(b, k -> new ArrayList<>()).add(a);
}
// BFS: setiap kota diproses berdasarkan urutan masuk antrean,
// sehingga jarak pertama yang tercatat selalu yang paling sedikit lompatan.
Map<String, Integer> jarakLompatan(String mulai) {
Map<String, Integer> jarak = new LinkedHashMap<>();
Deque<String> antrean = new ArrayDeque<>();
jarak.put(mulai, 0);
antrean.add(mulai);
while (!antrean.isEmpty()) {
String kota = antrean.poll();
for (String tetanggaKota : tetangga.get(kota)) {
if (!jarak.containsKey(tetanggaKota)) {
jarak.put(tetanggaKota, jarak.get(kota) + 1);
antrean.add(tetanggaKota);
}
}
}
return jarak;
}
// Jalur direkonstruksi dengan berjalan mundur: cari tetangga
// yang jaraknya tepat satu lebih kecil, lalu ulangi sampai titik awal.
List<String> jalurKe(String tujuan, Map<String, Integer> jarak) {
List<String> jalur = new ArrayList<>(List.of(tujuan));
String kota = tujuan;
while (jarak.get(kota) > 0) {
for (String kandidat : tetangga.get(kota)) {
if (jarak.get(kandidat) == jarak.get(kota) - 1) {
jalur.add(0, kandidat);
kota = kandidat;
break;
}
}
}
return jalur;
}
}
DfsSiklus.java
Unduh berkasimport java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class DfsSiklus {
// Warna status DFS pada directed graph:
// 1 = putih (belum dikunjungi), 2 = abu-abu (sedang diproses), 3 = hitam (selesai).
private static final int PUTIH = 1, ABU = 2, HITAM = 3;
private final Map<String, List<String>> sisi = new LinkedHashMap<>();
private final Map<String, Integer> status = new LinkedHashMap<>();
public static void main(String[] args) {
DfsSiklus matakuliah = new DfsSiklus();
matakuliah.prasyarat("DasarPemrograman", "OOP");
matakuliah.prasyarat("DasarPemrograman", "StrukturData");
matakuliah.prasyarat("StrukturData", "Algoritma");
matakuliah.prasyarat("OOP", "BasisData");
matakuliah.prasyarat("Algoritma", "KeamananAplikasi");
System.out.println("=== Kartu rencana studi tanpa siklus ===");
System.out.println("Siklus terdeteksi: " + matakuliah.adaSiklus());
// Tambahkan prasyarat balik: Algoritma menuntut DasarPemrograman.
matakuliah.prasyarat("Algoritma", "DasarPemrograman");
System.out.println();
System.out.println("=== Setelah menambah sisi Algoritma -> DasarPemrograman ===");
System.out.println("Siklus terdeteksi: " + matakuliah.adaSiklus());
}
void prasyarat(String dari, String ke) {
sisi.computeIfAbsent(dari, k -> new ArrayList<>()).add(ke);
sisi.computeIfAbsent(ke, k -> new ArrayList<>());
}
boolean adaSiklus() {
for (String node : sisi.keySet()) {
status.put(node, PUTIH);
}
for (String node : sisi.keySet()) {
if (status.get(node) == PUTIH && dfsDeteksi(node)) {
return true;
}
}
return false;
}
// DFS rekursif: node berstatus abu-abu berarti node itu berada
// di jalur penelusuran saat ini, jadi sisi menuju node itu menutup siklus.
private boolean dfsDeteksi(String node) {
status.put(node, ABU);
for (String tujuan : sisi.get(node)) {
if (status.get(tujuan) == ABU) {
System.out.println(" Sisi " + node + " -> " + tujuan
+ " kembali ke node yang masih diproses");
return true;
}
if (status.get(tujuan) == PUTIH && dfsDeteksi(tujuan)) {
return true;
}
}
status.put(node, HITAM);
return false;
}
}
DijkstraKota.java
Unduh berkasimport java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
public class DijkstraKota {
// Sisi berbobot: kota tujuan dan jarak tempuh dalam kilometer.
record Sisi(String tujuan, int jarak) { }
// Entri antrean prioritas: kota dan jarak sementara terbaik yang diketahui.
record Entri(String kota, int jarakSementara) { }
private final Map<String, List<Sisi>> jalan = new LinkedHashMap<>();
private final Map<String, String> induk = new HashMap<>(); // untuk rekonstruksi rute
public static void main(String[] args) {
DijkstraKota peta = new DijkstraKota();
peta.tambahJalan("Jakarta", "Bandung", 150);
peta.tambahJalan("Jakarta", "Semarang", 450);
peta.tambahJalan("Jakarta", "Surabaya", 810);
peta.tambahJalan("Bandung", "Yogyakarta", 430);
peta.tambahJalan("Semarang", "Yogyakarta", 125);
peta.tambahJalan("Semarang", "Surabaya", 350);
peta.tambahJalan("Yogyakarta", "Surabaya", 260);
peta.tambahJalan("Surabaya", "Denpasar", 500);
peta.tambahJalan("Yogyakarta", "Denpasar", 900);
Map<String, Integer> jarak = peta.jarakTerpendek("Jakarta");
System.out.println("=== Dijkstra dari Jakarta (jarak dalam km) ===");
for (Map.Entry<String, Integer> e : jarak.entrySet()) {
System.out.printf(" %-10s %d km%n", e.getKey(), e.getValue());
}
System.out.println();
System.out.println("Rute ke Denpasar : " + peta.ruteKe("Denpasar"));
System.out.println("Rute ke Yogyakarta: " + peta.ruteKe("Yogyakarta"));
}
void tambahJalan(String dari, String ke, int jarak) {
jalan.computeIfAbsent(dari, k -> new ArrayList<>()).add(new Sisi(ke, jarak));
jalan.computeIfAbsent(ke, k -> new ArrayList<>());
}
Map<String, Integer> jarakTerpendek(String mulai) {
Map<String, Integer> jarak = new LinkedHashMap<>();
Map<String, String> induk = new HashMap<>();
for (String kota : jalan.keySet()) {
jarak.put(kota, Integer.MAX_VALUE);
}
jarak.put(mulai, 0);
PriorityQueue<Entri> antrean = new PriorityQueue<>(
Comparator.comparingInt(Entri::jarakSementara));
antrean.add(new Entri(mulai, 0));
while (!antrean.isEmpty()) {
Entri sekarang = antrean.poll();
if (sekarang.jarakSementara() > jarak.get(sekarang.kota())) {
continue; // Entri basi: kota ini sudah ditemukan lewat rute lebih pendek.
}
for (Sisi sisi : jalan.get(sekarang.kota())) {
int kandidat = sekarang.jarakSementara() + sisi.jarak();
if (kandidat < jarak.get(sisi.tujuan())) {
jarak.put(sisi.tujuan(), kandidat);
induk.put(sisi.tujuan(), sekarang.kota());
antrean.add(new Entri(sisi.tujuan(), kandidat));
}
}
}
return jarak;
}
String ruteKe(String tujuan) {
List<String> rute = new ArrayList<>(List.of(tujuan));
String kota = tujuan;
while (induk.containsKey(kota)) {
kota = induk.get(kota);
rute.add(0, kota);
}
return String.join(" -> ", rute);
}
}