En kısa yol ve döngü tespiti: BFS, Dijkstra, Bellman-Ford
Ağırlıksız grafta BFS ile, pozitif ağırlıkta Dijkstra ile, negatif kenarda Bellman-Ford ile en kısa yol; yönlü ve yönsüz grafta döngü bulma. Sınavın tablo doldurma soruları buradan gelir.
Geçen hafta grafı nasıl saklayacağını ve BFS/DFS ile nasıl gezeceğini gördün. Bu hafta o gezintileri işe koşuyoruz: bir kaynaktan (single-source) her düğüme en kısa yol ve grafta döngü var mı soruları. Sınavda neredeyse kesin bir “Dijkstra tablosunu doldur” sorusu gelir. Yanında “Dijkstra burada neden yanlış?” ya da “bu grafta döngü var mı?” türü kısa sorular olur.
Altın kurallar:
- Ağırlık yoksa BFS yeter. En kısa yol = en az kenar sayısı = BFS katmanı.
- Ağırlık pozitifse Dijkstra. Her adımda bilinmeyenler içinde en küçük mesafeli düğümü seç, kesinleştir, kenarlarını gevşet.
- Negatif kenar varsa Bellman-Ford. Dijkstra sessizce yanlış cevap verebilir.
- Yol, prev dizisinden geriye okunur. Hedeften kaynağa yürü, sonra ters çevir.
- Yönlü grafta döngü = gri düğüme kenar. Yönsüzde = iki ucu aynı kümede olan kenar.
Ağırlıksız en kısa yol: BFS
Her kenarın maliyeti 1 ise en kısa yol, en az kenarlı yoldur. BFS düğümleri kaynaktan uzaklık sırasına göre (katman katman) çıkardığı için bir düğümü ilk keşfettiği an mesafesi kesindir. Kural: dist[w] = dist[v] + 1, prev[w] = v.
Örnek yönsüz graf (komşu listeleri artan sırada):
0
/ \
1 2
\ /
3
|
4
/ \
5 6
\ /
7
kuyruk çıkan keşfedilen (dist)
[0] 0 1(1) 2(1)
[1,2] 1 3(2)
[2,3] 2 - (3 zaten görüldü)
[3] 3 4(3)
[4] 4 5(4) 6(4)
[5,6] 5 7(5)
[6,7] 6 -
[7] 7 -
BFS dist: [0, 1, 1, 2, 3, 4, 4, 5]
BFS prev: [-1, 0, 0, 1, 3, 4, 4, 5]
BFS path 0->7: [0, 1, 3, 4, 5, 7]
Süre : her düğüm kuyruğa bir kez girer, her kenar bir kez (yönsüzde iki kez) bakılır.
TUZAK · mesafeyi kuyruktan çıkarken değil, keşfederken yaz. Düğümü kuyruğa koyduğun anda
distveprevver ve “görüldü” say. Yoksa aynı düğüm kuyruğa iki kez girer.
Dijkstra algoritması
Kenarların ağırlığı var ve hepsi negatif değil. Fikir: kaynaktan en yakın düğüm, başka hiçbir düğüm üzerinden daha kısa yola sahip olamaz (her ek kenar maliyeti artırır). O yüzden en küçük geçici mesafeli düğümü kesinleştir (known) ve ondan çıkan kenarları gevşet (relax):
Adımlar:
d[kaynak] = 0, diğerleri ∞. Hiçbiri bilinmiyor.- Bilinmeyenler içinden
ddeğeri en küçük olanv’yi seç. Eşitlikte küçük etiketi seç. v’yi bilinen yap. Bilinmeyen her komşuuiçin gevşet.- Bilinmeyen kalmayana (ya da hepsi ∞ olana) kadar 2’ye dön.
Çözümlü örnek
G1 grafı (yönlü), komşu listesi gösterimiyle:
A: B(4) C(1)
B: D(5)
C: B(2) D(8) E(10)
D: E(2) F(6)
E: F(2)
F: -
Sınavda tablo şöyle doldurulur. Her satır bir seçim; * bilinen demek.
adım seç A B C D E F
0 - 0 ∞ ∞ ∞ ∞ ∞
1 A 0* 4 1 ∞ ∞ ∞
2 C 0* 3 1* 9 11 ∞
3 B 0* 3* 1* 8 11 ∞
4 D 0* 3* 1* 8* 10 14
5 E 0* 3* 1* 8* 10* 12
6 F 0* 3* 1* 8* 10* 12*
Programın çıktısı:
Dijkstra order: A C B D E F
Dijkstra final: A=0 B=3 C=1 D=8 E=10 F=12
prev: A=- B=C C=A D=B E=D F=E
path to F: [A, C, B, D, E, F] cost 12
Dikkat edilecek iki an: 2. adımda B’nin değeri 4’ten 3’e düştü (C üzerinden daha ucuz). 4. adımda E, 11’den 10’a düştü. Gevşetme budur: daha iyi bir yol bulunca değeri ve prev’i güncellersin.
İPUCU · tabloda prev sütunu da tut. Sınav “path from A to F” diye sorarsa her güncellemede kimin üzerinden geldiğini yazmış olman gerekir. Sonradan geri hesaplamak zaman kaybettirir.
Karmaşıklık
| Uygulama | En küçüğü bulma | Toplam |
|---|---|---|
| Dizi taraması | , V kez | |
| Binary heap (PriorityQueue) |
Java’da PriorityQueue ile yazarken “decreaseKey” yoktur. Pratik çözüm: mesafe düşünce düğümü yeni değeriyle tekrar ekle, kuyruktan çıkan düğüm zaten biliniyorsa atla (lazy deletion).
PriorityQueue<int[]> pq = new PriorityQueue<>((x, y) -> x[1] - y[1]);
pq.add(new int[]{src, 0});
while (!pq.isEmpty()) {
int[] top = pq.poll();
int v = top[0];
if (known[v]) continue; // eski kopya
known[v] = true;
for (int[] e : adj.get(v)) { // e = {komşu, ağırlık}
int u = e[0], w = e[1];
if (!known[u] && d[v] + w < d[u]) {
d[u] = d[v] + w;
prev[u] = v;
pq.add(new int[]{u, d[u]});
}
}
}
Negatif kenar ve Bellman-Ford
Dijkstra’nın “en yakını kesinleştir” adımı, sonradan bulunan bir kenarın mesafeyi azaltamayacağına güvenir. Negatif kenar bu güveni bozar.
A --2--> B --1--> D
| ^
5 -4
| |
+-> C ---+
Dijkstra order: A B D C
Dijkstra final: A=0 B=2 C=5 D=3
BF round 1: A=0 B=1 C=5 D=2
Dijkstra B’yi 2 ile kesinleştirdi, D’yi 3 yaptı. C en son geldi; C→B kenarı B’yi 1’e indirebilirdi ama B kapandı. Gerçek cevap D = 2.
Bellman-Ford seçim yapmaz: her turda bütün kenarları sırayla gevşetir. En kısa yol en fazla V−1 kenar içerdiği için V−1 tur yeter. Süre , Dijkstra’dan yavaş ama negatif kenarla doğru çalışır.
Arrays.fill(d, INF);
d[src] = 0;
for (int round = 1; round <= V - 1; round++)
for (Edge e : edges)
if (d[e.from] != INF && d[e.from] + e.w < d[e.to])
d[e.to] = d[e.from] + e.w;
Kenar sırası tur sayısını etkiler. e1 B→C 2, e2 A→B −1, e3 S→A 4, e4 S→B 5, e5 C→D 3, e6 A→C 6 sırasıyla, S’den:
BF round 1: S=0 A=4 B=5 C=10 D=inf
BF round 2: S=0 A=4 B=3 C=7 D=10
BF round 3: S=0 A=4 B=3 C=5 D=8
BF round 4: S=0 A=4 B=3 C=5 D=8 (no change)
Bir turda hiçbir şey değişmediyse erken durabilirsin. V−1 turdan sonra bir kenar hâlâ gevşiyorsa toplamı negatif bir döngü var demektir; o zaman “en kısa yol” tanımsızdır (döngüde dönüp durarak maliyet sonsuza kadar azalır). S→A 1, A→B 2, B→C −4, C→A 1 grafında A→B→C→A toplamı −1 ve program BF negative cycle: true basar.
TUZAK · “tüm ağırlıklara sabit ekle” hilesi çalışmaz. Negatif kenardan kurtulmak için her kenara 5 eklersen çok kenarlı yollar daha fazla ceza alır, en kısa yol değişir.
Döngü tespiti (cycle detection)
Yönlü graf: üç renkli DFS
- Beyaz (0): hiç görülmedi.
- Gri (1): DFS’e girildi, henüz bitmedi (özyineleme yığınında).
- Siyah (2): tüm torunları bitti.
Gri bir düğüme giden kenar, şu anki yolun bir atasına geri dönüyor demek: geri kenar (back edge), yani döngü. Siyah düğüme giden kenar döngü değildir; o dal zaten bitmiş.
0 --> 1 --> 2
| ^ |
v | v
4 +---- 3
DFS 0, 1, 2, 3 sırasıyla iner, dördü de gri. 3→1 gri düğüme gidiyor:
G2 directed cycle: true back edge 3->1
DAG directed cycle: false
Döngüsüz yönlü grafa DAG denir. Sadece DAG’ların topolojik sırası vardır; Kahn algoritmasında (geçen hafta) tüm düğümler çıkarılamıyorsa da döngü var demektir.
Yönsüz graf
Yönsüz grafta her kenar iki yönlü olduğu için “komşu zaten görülmüş” her zaman döngü değildir: geldiğin ebeveyn de görülmüştür. İki yol:
- DFS + parent: görülmüş bir komşu, ebeveynin değilse döngü.
- Union-find: kenarları tek tek ekle. İki uç zaten aynı kümedeyse bu kenar döngü kapatır. Kruskal (13. hafta) tam olarak bunu kullanır.
undirected: first cycle-closing edge 1-3
Hızlı kontrol: bağlantılı yönsüz grafta ise mutlaka döngü vardır (ağaçta ).
Hangi algoritma ne zaman?
| Durum | Algoritma | Süre |
|---|---|---|
| Ağırlıksız | BFS | |
| Ağırlıklar ≥ 0 | Dijkstra (heap) | |
| Negatif kenar olabilir | Bellman-Ford | |
| DAG, ağırlık herhangi | Topolojik sırayla gevşetme | |
| Yönlü döngü var mı | 3 renkli DFS | |
| Yönsüz döngü var mı | DFS + parent ya da union-find |
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Dijkstra’da sıradaki düğüm, son seçilenin komşusu | Bütün bilinmeyenler içindeki en küçük d seçilir |
| Bilinen düğümün değeri sonradan güncellenir | Dijkstra’da güncellenmez; değişiyorsa negatif kenar var |
| Az kenarlı yol = kısa yol | Sadece ağırlıksız grafta. Ağırlıklıda toplam ağırlık |
| Negatif kenarla Dijkstra da doğru çalışır | Yanlış cevap verebilir, Bellman-Ford kullan |
| Bellman-Ford V tur yapar | V−1 tur yeter; V. tur sadece negatif döngü kontrolü |
| Yönlü grafta görülmüş düğüme kenar = döngü | Sadece GRİ düğüme kenar döngüdür |
| Yönsüz DFS’te ebeveyne dönüş döngüdür | Ebeveyn hariç tutulur |
| Yol, prev dizisini baştan okuyarak bulunur | Hedeften geriye izlenir, sonra ters çevrilir |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
The undirected graph has edges 0-1, 0-2, 1-3, 2-3, 3-4, 4-5, 4-6, 5-7, 6-7 (adjacency lists in ascending order). Run BFS from vertex 0. Give the shortest-path distance (number of edges) of every vertex 0..7.
Cevabı göster
Ağırlıksız grafta en kısa yol = BFS katmanı. Kuyruk: 0 → (1, 2) mesafe 1 → 3 mesafe 2 (1 üzerinden) → 4 mesafe 3 → (5, 6) mesafe 4 → 7 mesafe 5. Dizi: [0, 1, 1, 2, 3, 4, 4, 5].
In the same BFS from 0, using the prev array (first discoverer wins), what is the shortest path reported from 0 to 7?
Cevabı göster
prev dizisi [-1, 0, 0, 1, 3, 4, 4, 5]. 7'den geriye: prev[7]=5, prev[5]=4, prev[4]=3, prev[3]=1, prev[1]=0. Ters çevir: 0, 1, 3, 4, 5, 7. 3'ü ilk keşfeden 1'dir çünkü 1 kuyruktan 2'den önce çıktı.
Directed graph G1: A→B 4, A→C 1, C→B 2, B→D 5, C→D 8, C→E 10, D→E 2, D→F 6, E→F 2. Run Dijkstra from A. Give the final distances of A, B, C, D, E, F.
Cevabı göster
Her adımda bilinmeyenler arasından en küçük mesafeyi seç, çıkan kenarlarını gevşet (relax):
pick A: B=4 C=1
pick C: B=min(4,1+2)=3 D=9 E=11
pick B: D=min(9,3+5)=8
pick D: E=min(11,8+2)=10 F=14
pick E: F=min(14,10+2)=12
pick F: bittiFor the Dijkstra run on G1 from A, in which order do the vertices become known?
Cevabı göster
Seçim her zaman bilinmeyenler içindeki en küçük mesafe: A(0), C(1), B(3), D(8), E(10), F(12). Mesafeler artan sırada kesinleşir; Dijkstra'nın temel özelliği budur.
Using the Dijkstra result on G1, what is the shortest path from A to F and its cost? Is the direct-looking route A→B→D→F shorter?
Cevabı göster
prev zinciri: F←E←D←B←C←A. Maliyet 1+2+5+2+2 = 12. A→B→D→F = 4+5+6 = 15. Daha az kenarlı yol daha kısa olmak zorunda değil; ağırlıklı grafta kenar sayısı değil toplam ağırlık sayılır.
What is the running time of Dijkstra's algorithm (a) with a simple array scan to find the minimum, (b) with a binary heap priority queue? V = vertices, E = edges.
Cevabı göster
(a) V kez en küçüğü bulmak için V elemanı tara: , kenar gevşetmeleri toplam . (b) Her gevşetme heap'e maliyetle ekleme yapar, toplam . Seyrek grafta (E ≈ V) heap, yoğun grafta (E ≈ V²) dizi daha iyidir.
Graph: A→B 2, A→C 5, C→B -4, B→D 1. Dijkstra from A (a known vertex is never updated again) reports dist(D) = ? What is the true shortest distance to D?
Cevabı göster
Dijkstra sırası A, B, D, C. B mesafe 2 ile erken kesinleşir, D = 2+1 = 3 olur. C en son seçilir; C→B -4 ile B = 1 olabilirdi ama B zaten bilinen, güncellenmez. Doğru yol A→C→B→D = 5-4+1 = 2. Bellman-Ford ilk turda B=1, D=2 bulur. Ders: negatif kenar varsa Dijkstra'ya güvenme.
Bellman-Ford on vertices S, A, B, C, D from S. Edges are relaxed in this fixed order each round: e1 B→C 2, e2 A→B -1, e3 S→A 4, e4 S→B 5, e5 C→D 3, e6 A→C 6. Give dist after round 1 and after round 2.
Cevabı göster
Tur 1: e1, e2 atlanır (B, A henüz ∞). e3 A=4, e4 B=5, e5 atlanır (C ∞), e6 C=4+6=10.
Tur 2: e1 C=5+2=7, e2 B=4-1=3, e5 D=7+3=10, e6 10 > 7 değişmez.
Tur 3'te C=3+2=5, D=8 olur; tur 4'te değişiklik yok. Final: A=4, B=3, C=5, D=8. Kenar sırası kötü olduğu için doğru cevap birkaç tur sürdü; en fazla V-1 = 4 tur gerekir.
How many rounds does Bellman-Ford need in the worst case on a graph with V vertices, what is its running time, and how does it detect a negative cycle?
Cevabı göster
En kısa yol (döngüsüz) en fazla V-1 kenar içerir; her tur en az bir kenar daha doğru yerleştirir. Her tur E kenarı gezer: . V-1 turdan sonra hâlâ bir kenar gevşiyorsa sonsuza kadar azalan bir döngü var: negatif döngü. Örnek: S→A 1, A→B 2, B→C -4, C→A 1 döngüsünün toplamı -1, kontrol turu true döndürür.
Directed graph: 0→1, 1→2, 2→3, 3→1, 0→4. Run DFS from 0 with white/gray/black colors (adjacency in ascending order). Does the graph have a cycle? Which edge is found as the back edge?
Cevabı göster
DFS 0'dan 1'e, 2'ye, 3'e iner; hepsi gri (yığında). 3→1 kenarı gri bir düğüme gidiyor: geri kenar (back edge), yani döngü 1→2→3→1. Siyah (bitmiş) düğüme giden kenar döngü demek değildir; sadece gri düğüme giden kenar döngüdür.
Undirected graph on 0..5. Edges are added in the order 0-1, 1-2, 3-4, 2-4, 4-5, 1-3, 0-5 using union-find. Which edge is the first one that closes a cycle?
Cevabı göster
0-1, 1-2 ile {0,1,2}; 3-4 ile {3,4}; 2-4 bunları birleştirir {0,1,2,3,4}; 4-5 ile 5 de katılır. 1-3 geldiğinde find(1) = find(3), iki uç aynı kümede: döngü. Yönsüz grafta kural: iki ucu zaten bağlı olan kenar döngü kapatır.
Write a method boolean hasCycle(List<List<Integer>> adj) that returns true if the directed graph (vertices 0..n-1) contains a cycle. Use DFS with three colors.
Cevabı göster
Gri = şu anki DFS yolunda. Gri düğüme dönmek geri kenardır. Bağlantısız grafta her beyaz düğümden ayrıca başla:
static boolean hasCycle(List<List<Integer>> adj) {
int[] color = new int[adj.size()];
for (int v = 0; v < adj.size(); v++)
if (color[v] == 0 && dfs(adj, v, color)) return true;
return false;
}
static boolean dfs(List<List<Integer>> adj, int v, int[] color) {
color[v] = 1; // gray
for (int w : adj.get(v)) {
if (color[w] == 1) return true; // back edge
if (color[w] == 0 && dfs(adj, w, color)) return true;
}
color[v] = 2; // black
return false;
}Süre . 0→1→2→3→0 halkasında true, 0→1, 0→2, 1→3, 2→3, 3→4 DAG'ında false döner.Doğrulama: Tüm izler (BFS mesafeleri, Dijkstra adım tabloları ve yolları, negatif kenarda Dijkstra hatası, Bellman-Ford turları, negatif döngü, renkli DFS ile geri kenar, union-find ile döngü kapatan kenar) verify/SEN2212/W11.java ile JDK'da çalıştırılıp çıktıdan birebir alındı (java W11.java).