Bölüm 14: Çizgeler
Giriş
Öğrenme Çıktıları
Düğüm, kenar, yön, ağırlık, yol ve bağlantılılık kavramlarını açıklamak.
Komşuluk matrisi ile komşuluk listesini maliyetleriyle karşılaştırmak.
BFS, DFS ve DAG'lerde topolojik sıralamayı uygulayıp sonuçlarını yorumlamak.
Ağırlıksız BFS, Dijkstra, Kruskal ve Prim algoritmaları için uygun problem türünü seçmek.
Temel Kavramlar
Çizge (Graph)
- Düğümlerin (vertex / node) ve bu düğümleri birbirine bağlayan kenarların (edge) bir araya gelmesiyle oluşur.
- Karmaşık ilişkileri, ağları ve yapıları temsil etmek için kullanılır.
- Çizge teorisi, düğümler ve kenarlar arasındaki ilişkileri inceler.
Örnek: V = {0, 1, 2, 3, 4}, E = {(0,1), (0,4), (1,2), (1,3), (1,4), (2,3), (3,4)} · n = |V| = 5, m = |E| = 7
Temel Kavramlar
Düğüm (Vertex, Node)
- Çizgenin temel yapı taşıdır.
- Şehirler, kişiler, bilgisayarlar gibi nesneleri temsil eder.
- Düğüm, bir değer (etiket, ad, konum…) saklayabilir.
- Örnek: haritadaki şehirler ve kavşaklar.
Karayolu haritası: şehirler düğüm, yollar kenar
Temel Kavramlar
Kenar (Edge)
- Düğümleri birbirine bağlayan bağlantılardır.
- Kenarlar düğümler arasındaki ilişkiyi temsil eder.
- (v, w) bir kenar ise v ve w bitişik (komşu, adjacent) düğümlerdir; kenar bu iki düğüme bağlıdır (incident).
- Örnek: şehirler arasındaki yollar, bilgisayar ağındaki bağlantılar, sosyal ağdaki arkadaşlıklar.
Bir sosyal ağ: kişiler düğüm, ilişkiler kenar
Temel Kavramlar
Derece (Degree)
- Bir düğümün derecesi, ona bağlı kenar sayısıdır:
deg(v). - Derecesi 0 olan (yalnız, isolated) düğümler de olabilir.
- Öz döngü (self-loop) dereceye 2 katkı yapar.
- Örnek: bir kavşaktaki yolların sayısı.
Her düğümün içinde kendi derecesi yazıyor
Temel Kavramlar
Yönlü Çizge (Digraph)
- Kenarlar bir düğümden diğerine belirli bir yöndedir: (u, v) ≠ (v, u).
- Geri dönüş yolu olmayabilir: örnekte B'den A'ya ulaşılamaz.
- Yönlü çizge (directed graph), kısaca digraph olarak da adlandırılır.
- Örnek: web sayfaları ve bağlantıları, ders ön koşulları, tek yönlü sokaklar.
| Düğüm | A | B | C | D | E | F |
|---|---|---|---|---|---|---|
| giriş derecesi | 0 | 2 | 1 | 1 | 1 | 1 |
| çıkış derecesi | 1 | 1 | 1 | 1 | 2 | 0 |
Temel Kavramlar
Yol (Path) ve Döngü (Cycle)
- Yol (path): ardışık düğümleri kenarlarla bağlanan düğüm dizisi.
- Basit yol: hiçbir düğümü tekrar etmeyen yol.
- Döngü (cycle): aynı düğümde başlayıp biten yol.
- Basit döngü: başlangıç düğümü dışında hiçbir düğümü tekrar etmez.
- Çizgelerde döngü olabilir; ağaçta döngü yoktur.
Yeşil: A-B-C-D-E-F-A · Kırmızı (kesikli): G-H-I-J-K-L-G
Temel Kavramlar
Örnek Çizge
- V = {1, 2, 3, 4, 5}
- E = {(1,2), (1,3), (1,4), (2,5), (3,4), (4,5)}
- Düğüm 1 ve düğüm 2 komşu.
- deg(1) = 3, deg(4) = 3, diğerleri 2 → toplam 12 = 2·6 ✓
| Dizi | Türü |
|---|---|
| 1 → 2 → 5 | basit yol |
| 1 → 3 → 4 → 1 | basit döngü |
| 1 → 3 → 4 → 1 → 2 | basit olmayan yol (1 tekrar) |
| 1 → 3 → 4 → 1 → 4 → 1 | basit olmayan döngü |
Temel Kavramlar
Bağlantılı ve Tam Çizge
- Bağlantılı (connected) çizge: her iki farklı düğüm çifti arasında bir yol vardır.
- Bağlantılı bileşen (connected component): birbirine yolla ulaşabilen düğümlerin en büyük grubu.
- Tam (complete) çizge: her iki farklı düğüm çifti arasında bir kenar vardır.
n düğümlü basit yönsüz çizgede m ≤ n(n−1)/2; bu üst sınıra tam çizgede ulaşılır. Bağlantılı bir çizgede m ≥ n − 1.
Temel Kavramlar
Yönlü Çizge Örneği
- V = {1, 2, 3, 4, 5}
- E = {(1,2), (1,4), (2,5), (4,5), (3,1), (4,3)}
- Düğüm 2, düğüm 1'in komşusu (1 → 2 kenarı var).
- Düğüm 1, düğüm 2'nin komşusu değil!
| Dizi | Türü |
|---|---|
| 1 → 2 → 5 | yönlü yol |
| 1 → 4 → 3 → 1 | yönlü döngü |
| 5 → … | 5'ten çıkan kenar yok |
Yönlü kenar (u, v) sıralı bir çifttir: u kuyruk (başlangıç), v baş (hedef) düğümdür.
Temel Kavramlar
Ağırlıklı Çizge
- Her kenara bir sayı (ağırlık, weight) atanır: uzaklık, süre, maliyet, kapasite…
- Bir yolun uzunluğu = üzerindeki kenar ağırlıklarının toplamı. Yönsüz veya yönlü olabilir.
Temel Kavramlar
Çizge Soyut Veri Tipi (Graph ADT)
Goodrich'teki Graph arayüzünün temel işlemleri (V: düğüm, E: kenar):
| İşlem | Açıklama |
|---|---|
numVertices(), numEdges() | Düğüm sayısı n, kenar sayısı m |
vertices(), edges() | Tüm düğümler / tüm kenarlar üzerinde gezinme |
getEdge(u, v) | u'dan v'ye kenar varsa onu, yoksa null döndürür |
endVertices(e), opposite(v, e) | Kenarın iki ucu / e kenarının v dışındaki ucu |
outDegree(v), inDegree(v) | Çıkış / giriş derecesi (yönsüzde ikisi eşit) |
outgoingEdges(v), incomingEdges(v) | v'den çıkan / v'ye gelen kenarlar |
insertVertex(x), insertEdge(u, v, x) | Yeni düğüm / yeni kenar ekler |
removeVertex(v), removeEdge(e) | Düğümü (bağlı kenarlarıyla) / kenarı siler |
Bu işlemlerin maliyeti, çizgenin bellekte nasıl temsil edildiğine bağlıdır → sonraki alt başlık: Çizge Gösterimleri.
Çizge Gösterimleri
Çizge Nasıl Saklanır? Üç Temel Gösterim
Kenar listesi (edge list) en basit gösterimdir:
- Alan O(n + m); kenar eklemek O(1).
- Ama "u ile v komşu mu?" ya da "u'nun komşuları kim?" için tüm listeyi taramak gerekir: O(m).
Çizge Gösterimleri
Komşuluk Matrisi (Adjacency Matrix)
- n×n boyutunda iki boyutlu bir dizidir:
matris[i][j]= 1, düğüm i'den j'ye kenar varsa; yoksa 0. - Ağırlıklı çizgede
matris[i][j]kenarın ağırlığını tutar; kenar yoksa∞(veya özel bir değer). - Yönsüz çizgede matris simetriktir:
matris[i][j] == matris[j][i]. - Alan karmaşıklığı O(n²); düğüm sayısı arttıkça bellek hızla büyür.
Kolay
- İki düğüm arasında kenar var mı? O(1)
- Kenar ekleme / silme O(1)
Zor
- Bir düğümün komşularını bulmak: tüm satır, O(n)
- Seyrek çizgede hücrelerin çoğu boş (0)
Çizge Gösterimleri
Komşuluk Matrisi: Üç Örnek
| A | B | C | D | E | |
|---|---|---|---|---|---|
| A | 0 | 1 | 0 | 0 | 1 |
| B | 1 | 0 | 1 | 0 | 1 |
| C | 0 | 1 | 0 | 1 | 0 |
| D | 0 | 0 | 1 | 0 | 1 |
| E | 1 | 1 | 0 | 1 | 0 |
Yönsüz çizge: her kenar iki hücreye yazılır, matris köşegene göre simetrik. Satır toplamı = derece (B: 3).
| A | B | C | D | E | |
|---|---|---|---|---|---|
| A | 0 | 1 | 0 | 0 | 1 |
| B | 0 | 0 | 1 | 1 | 0 |
| C | 0 | 0 | 0 | 0 | 0 |
| D | 1 | 0 | 1 | 0 | 1 |
| E | 0 | 1 | 0 | 1 | 0 |
Yönlü çizge: satır = kaynak, sütun = hedef. Simetrik değil; satır toplamı çıkış, sütun toplamı giriş derecesi.
| A | B | C | D | E | |
|---|---|---|---|---|---|
| A | 0 | 4 | ∞ | ∞ | ∞ |
| B | ∞ | 0 | 3 | 3 | ∞ |
| C | 2 | ∞ | 0 | ∞ | 2 |
| D | ∞ | ∞ | ∞ | 0 | ∞ |
| E | ∞ | 5 | ∞ | 2 | 0 |
Ağırlıklı çizge: hücrede ağırlık, kenar yoksa ∞ (köşegende 0). Boş hücreye 0 yazmak, 0 ağırlıklı bir kenarla karıştırılabilir.
Çizge Gösterimleri
Komşuluk Listesi (Adjacency List)
- n düğüm için n adet liste (çoğunlukla bağlı liste veya dinamik dizi) tutulur.
liste[i], düğüm i'ye komşu olan düğümleri (ağırlıklıysa ağırlıklarıyla) içerir.- Yönsüz çizgede her kenar (v, w) iki listeye de eklenir: w, v'nin listesine; v, w'nin listesine.
- Alan karmaşıklığı O(n + m); seyrek çizgeler için idealdir.
Kolay
- Bir düğümün komşularını gezmek: O(deg(v))
- Kenar ekleme O(1)
Zor
- u ile v arasında kenar var mı? Listeyi taramak gerekir: O(deg(u))
Çizge Gösterimleri
Komşuluk Listesi: Üç Örnek
Yönsüz: 6 kenar → listelerde 12 kayıt (her kenar iki kez). Liste uzunluğu = derece.
Yönlü: yalnızca çıkan kenarlar listelenir; 9 kenar → 9 kayıt. C'den çıkan kenar yok, listesi boş.
Ağırlıklı: her kayıt (komşu, ağırlık) çifti tutar; ör. B'nin listesi: (C, 3), (D, 3).
Çizge Gösterimleri
Gösterimlerin Bellek ve İşlem Maliyeti
| İşlem (n düğüm, m kenar) | Kenar listesi | Komşuluk listesi | Komşuluk matrisi |
|---|---|---|---|
| Bellek | O(n + m) | O(n + m) | O(n²) |
u–v kenarı var mı? getEdge | O(m) | yönsüz: O(min(deg u, deg v)) yönlü u→v: O(outDeg u) | O(1) |
| v'nin komşularını gez | O(m) | O(deg v) | O(n) |
Çizge Gösterimleri
Java ile Komşuluk Listesi
class Cizge { private final int n; // düğümler: 0..n-1 private final List<List<Integer>> komsu = new ArrayList<>(); Cizge(int n) { this.n = n; for (int i = 0; i < n; i++) komsu.add(new ArrayList<>()); } void kenarEkle(int u, int v) { komsu.get(u).add(v); komsu.get(v).add(u); // yönlü çizgede bu satır yok } List<Integer> komsular(int u) { return komsu.get(u); } // O(1) int derece(int u) { return komsu.get(u).size(); } }
boolean[][] m = new boolean[n][n]; kenar ekle: m[u][v] = m[v][u] = true; sorgu m[u][v] O(1).int[]{v, w} veya küçük bir Kenar sınıfı; matriste int[][], kenar yoksa Integer.MAX_VALUE.Çizge Gösterimleri · Kodu adım adım çalıştır
Kod İzleme: Komşuluk Listesi Kurma
Cizge(int n) { this.n = n; for (int i = 0; i < n; i++) komsu.add(new ArrayList<>()); } void kenarEkle(int u, int v) { komsu.get(u).add(v); komsu.get(v).add(u); // yönlüde yok } // main: Cizge g = new Cizge(5); int[][] E = {{0,1}, {0,4}, {1,2}, {1,4}, {2,3}, {3,4}}; for (int[] e : E) g.kenarEkle(e[0], e[1]);
new Cizge(5): yapıcı çağrılır; n = 5 atanır. komsu henüz boş bir dış liste.
i = 0: 0 numaralı düğüm için boş bir iç liste eklenir.
i = 1: 1 numaralı düğüm için boş bir iç liste eklenir.
i = 2: 2 numaralı düğüm için boş bir iç liste eklenir.
i = 3: 3 numaralı düğüm için boş bir iç liste eklenir.
i = 4: 4 numaralı düğüm için boş bir iç liste eklenir. Artık her düğümün (boş) komşu listesi var.
Kenar 0–1: 1, 0'ın listesinin sonuna eklenir.
Yönsüz çizge: aynı kenar ters yönde de eklenir → 0, 1'in listesine.
Kenar 0–4: 4, 0'ın listesinin sonuna eklenir.
Yönsüz çizge: aynı kenar ters yönde de eklenir → 0, 4'ün listesine.
Kenar 1–2: 2, 1'in listesinin sonuna eklenir.
Yönsüz çizge: aynı kenar ters yönde de eklenir → 1, 2'nin listesine.
Kenar 1–4: 4, 1'in listesinin sonuna eklenir.
Yönsüz çizge: aynı kenar ters yönde de eklenir → 1, 4'ün listesine.
Kenar 2–3: 3, 2'nin listesinin sonuna eklenir.
Yönsüz çizge: aynı kenar ters yönde de eklenir → 2, 3'ün listesine.
Kenar 3–4: 4, 3'ün listesinin sonuna eklenir.
Yönsüz çizge: aynı kenar ters yönde de eklenir → 3, 4'ün listesine.
Bitti: 6 kenar → listelerde 2·6 = 12 kayıt. Her kenarEkle O(1); bellek O(n + m). derece(1) = liste uzunluğu = 3.
Çizge Gösterimleri · Kodu adım adım çalıştır
Kod İzleme: Komşuluk Matrisi Kurma
5×5 boolean matris oluşturulur; Java tüm hücreleri false (0) ile başlatır. Bellek O(n²) = 25 hücre.
Kenar 0–1: m[0][1] = true (satır 0, sütun 1).
Simetrik hücre: m[1][0] = true. Yönsüz çizgenin matrisi köşegene göre simetriktir.
Kenar 0–4: m[0][4] = true (satır 0, sütun 4).
Simetrik hücre: m[4][0] = true. Yönsüz çizgenin matrisi köşegene göre simetriktir.
Kenar 1–2: m[1][2] = true (satır 1, sütun 2).
Simetrik hücre: m[2][1] = true. Yönsüz çizgenin matrisi köşegene göre simetriktir.
Kenar 1–4: m[1][4] = true (satır 1, sütun 4).
Simetrik hücre: m[4][1] = true. Yönsüz çizgenin matrisi köşegene göre simetriktir.
Kenar 2–3: m[2][3] = true (satır 2, sütun 3).
Simetrik hücre: m[3][2] = true. Yönsüz çizgenin matrisi köşegene göre simetriktir.
Kenar 3–4: m[3][4] = true (satır 3, sütun 4).
Simetrik hücre: m[4][3] = true. Yönsüz çizgenin matrisi köşegene göre simetriktir.
Bitti: 12 hücre true, 13'ü boşa false. "2–4 kenarı var mı?" → m[2][4] tek erişim, O(1); ama 2'nin komşularını bulmak için tüm satır taranır: O(n).
boolean[][] m = new boolean[n][n]; void kenarEkle(int u, int v) { m[u][v] = true; m[v][u] = true; // yönlü çizgede yok } // main: aynı 6 kenar (n = 5) for (int[] e : E) g.kenarEkle(e[0], e[1]);
Gezinme (BFS / DFS)
Gezinme Algoritmaları
Gezinme (traversal): bir başlangıç düğümünden ulaşılabilen tüm düğümleri, her birini bir kez sistematik olarak ziyaret etmek.
boolean[] ziyaret) şarttır; yoksa algoritma sonsuza kadar döner.Gezinme (BFS / DFS)
Genişlik Öncelikli Arama (BFS)
- Düğümleri seviye seviye keşfeder: önce başlangıca 1 kenar uzaklıktakiler, sonra 2 kenar uzaklıktakiler…
- Kuyruk yapısını kullanır: ilk keşfedilen ilk işlenir.
- Her düğüm yalnızca bir kez ziyaret edilir.
Numara = ziyaret sırası; kalın = BFS ağacı
Artıları
- Ağırlıksız çizgede en kısa yolu (en az kenar) bulur.
- Yakın düğümleri hızlı keşfeder.
Eksisi
- Bellek kullanımı yüksek olabilir: bir seviyenin tamamı kuyrukta bekler.
Gezinme (BFS / DFS)
BFS Algoritması
BFS(başlangıç): kuyruk.ekle(başlangıç) başlangıç.ziyaretEdildi() while kuyruk boş değil: w = kuyruk.çıkar() for w'nin ziyaret edilmemiş her komşusu u: u.ziyaretEdildi() kuyruk.ekle(u)
Düğüm kuyruğa girerken işaretlenir; böylece aynı düğüm kuyruğa iki kez girmez.
void bfs(int s) { boolean[] ziyaret = new boolean[n]; Queue<Integer> q = new ArrayDeque<>(); ziyaret[s] = true; q.add(s); while (!q.isEmpty()) { int w = q.poll(); System.out.print(w + " "); for (int u : komsu.get(w)) if (!ziyaret[u]) { ziyaret[u] = true; q.add(u); } } }
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: bfs(A)
void bfs(int s) { boolean[] ziyaret = new boolean[n]; Queue<Integer> q = new ArrayDeque<>(); ziyaret[s] = true; q.add(s); while (!q.isEmpty()) { int w = q.poll(); System.out.print(w + " "); for (int u : komsu.get(w)) if (!ziyaret[u]) { ziyaret[u] = true; q.add(u); } } }
s = A (A=0, B=1, …, I=8; komşular alfabetik). ziyaret tümü false, kuyruk boş.
A kuyruğa girerken işaretlenir: ziyaret[A] = true, q = [A].
Kuyruğun önündeki A çıkarılır (poll) ve yazdırılır. Şimdi komşuları B, F, I taranır.
u = B ziyaret edilmemiş → işaretlenir ve kuyruğun arkasına eklenir (seviye 1).
u = F ziyaret edilmemiş → işaretlenir ve kuyruğun arkasına eklenir (seviye 1).
u = I ziyaret edilmemiş → işaretlenir ve kuyruğun arkasına eklenir (seviye 1).
Kuyruğun önündeki B çıkarılır (poll) ve yazdırılır. Şimdi komşuları A, C, E taranır.
u = A: zaten ziyaret edilmiş → atlanır. Ziyaret işareti olmasa kuyruğa tekrar girerdi.
u = C ziyaret edilmemiş → işaretlenir ve kuyruğun arkasına eklenir (seviye 2).
u = E ziyaret edilmemiş → işaretlenir ve kuyruğun arkasına eklenir (seviye 2).
Kuyruğun önündeki F çıkarılır (poll) ve yazdırılır. Şimdi komşuları A, G taranır.
u = A: zaten ziyaret edilmiş → atlanır.
u = G ziyaret edilmemiş → işaretlenir ve kuyruğun arkasına eklenir (seviye 2).
Kuyruğun önündeki I çıkarılır (poll) ve yazdırılır. Şimdi komşusu A taranır.
u = A: zaten ziyaret edilmiş → atlanır.
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: bfs(A) (devam)
void bfs(int s) { boolean[] ziyaret = new boolean[n]; Queue<Integer> q = new ArrayDeque<>(); ziyaret[s] = true; q.add(s); while (!q.isEmpty()) { int w = q.poll(); System.out.print(w + " "); for (int u : komsu.get(w)) if (!ziyaret[u]) { ziyaret[u] = true; q.add(u); } } }
Kuyruğun önündeki C çıkarılır (poll) ve yazdırılır. Şimdi komşuları B, D, E taranır.
u = B: zaten ziyaret edilmiş → atlanır.
u = D ziyaret edilmemiş → işaretlenir ve kuyruğun arkasına eklenir (seviye 3).
u = E: zaten ziyaret edilmiş → atlanır.
Kuyruğun önündeki E çıkarılır (poll) ve yazdırılır. Şimdi komşuları B, C, G taranır.
u = B, C, G: hepsi zaten ziyaret edilmiş → atlanır.
Kuyruğun önündeki G çıkarılır (poll) ve yazdırılır. Şimdi komşuları D, E, F taranır.
u = D, E, F: hepsi zaten ziyaret edilmiş → atlanır.
Kuyruğun önündeki D çıkarılır (poll) ve yazdırılır. Şimdi komşuları C, G, H taranır.
u = C, G: hepsi zaten ziyaret edilmiş → atlanır.
u = H ziyaret edilmemiş → işaretlenir ve kuyruğun arkasına eklenir (seviye 4).
Kuyruğun önündeki H çıkarılır (poll) ve yazdırılır. Şimdi komşusu D taranır.
u = D: zaten ziyaret edilmiş → atlanır.
Kuyruk boş → biter. Sıra A B F I C E G D H: seviye seviye. Her düğüm bir kez kuyruğa girer, her liste bir kez taranır → O(n + m).
Gezinme (BFS / DFS)
Derinlik Öncelikli Arama (DFS)
- Düğümleri bir yol boyunca keşfeder: gidebildiği kadar derine iner.
- Çıkmaza gelince (ziyaret edilmemiş komşu kalmayınca) geri döner (backtracking) ve başka dalı dener.
- Yığın (stack) yapısı veya özyinelemeli çağrılar kullanılır.
s→a→d→b derine; çıkmazda geri, sonra c→e
Artıları
- Bellek kullanımı genellikle düşüktür (yalnızca mevcut yol yığında).
- Bir yolun sonuna kadar gitme yeteneği sağlar.
Eksisi
- En kısa yolu garanti etmez.
Gezinme (BFS / DFS)
DFS Algoritması
void dfs(int v, boolean[] ziyaret) { ziyaret[v] = true; System.out.print(v + " "); for (int u : komsu.get(v)) if (!ziyaret[u]) dfs(u, ziyaret); }
Özyineleme, yığını bizim yerimize çağrı yığını ile tutar.
void dfsYigin(int s) { boolean[] ziyaret = new boolean[n]; Deque<Integer> yigin = new ArrayDeque<>(); yigin.push(s); ziyaret[s] = true; while (!yigin.isEmpty()) { int v = yigin.peek(); Integer yeni = null; for (int u : komsu.get(v)) if (!ziyaret[u]) { yeni = u; break; } if (yeni == null) yigin.pop(); // geri else { ziyaret[yeni] = true; yigin.push(yeni); } } }
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: dfs(A) (özyinelemeli)
void dfs(int v, boolean[] ziyaret) { ziyaret[v] = true; System.out.print(v + " "); for (int u : komsu.get(v)) if (!ziyaret[u]) dfs(u, ziyaret); }
dfs(A, ziyaret) çağrıldı (A=0, B=1, …, I=8; komşular alfabetik). A işaretlenir ve yazdırılır.
A'nın komşusu B ziyaret edilmemiş → dfs(B) çağrılır: yeni çerçeve açılır, B işaretlenip yazdırılır.
B'nin komşusu A zaten ziyaret edilmiş → atla.
B'nin komşusu C ziyaret edilmemiş → dfs(C) çağrılır: yeni çerçeve açılır, C işaretlenip yazdırılır.
C'nin komşusu B zaten ziyaret edilmiş → atla.
C'nin komşusu D ziyaret edilmemiş → dfs(D) çağrılır: yeni çerçeve açılır, D işaretlenip yazdırılır.
D'nin komşusu C zaten ziyaret edilmiş → atla.
D'nin komşusu G ziyaret edilmemiş → dfs(G) çağrılır: yeni çerçeve açılır, G işaretlenip yazdırılır.
G'nin komşusu D zaten ziyaret edilmiş → atla.
G'nin komşusu E ziyaret edilmemiş → dfs(E) çağrılır: yeni çerçeve açılır, E işaretlenip yazdırılır.
E'nin komşuları B, C, G zaten ziyaret edilmiş → atla.
E'nin gidilecek komşusu kalmadı → dfs(E) biter, çerçeve yığından çıkar (geri dönüş).
G'nin komşusu F ziyaret edilmemiş → dfs(F) çağrılır: yeni çerçeve açılır, F işaretlenip yazdırılır.
F'nin komşuları A, G zaten ziyaret edilmiş → atla.
F'nin gidilecek komşusu kalmadı → dfs(F) biter, çerçeve yığından çıkar (geri dönüş).
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: dfs(A) (özyinelemeli) (devam)
void dfs(int v, boolean[] ziyaret) { ziyaret[v] = true; System.out.print(v + " "); for (int u : komsu.get(v)) if (!ziyaret[u]) dfs(u, ziyaret); }
G'nin gidilecek komşusu kalmadı → dfs(G) biter, çerçeve yığından çıkar (geri dönüş).
D'nin komşusu H ziyaret edilmemiş → dfs(H) çağrılır: yeni çerçeve açılır, H işaretlenip yazdırılır.
H'nin komşusu D zaten ziyaret edilmiş → atla.
H'nin gidilecek komşusu kalmadı → dfs(H) biter, çerçeve yığından çıkar (geri dönüş).
D'nin gidilecek komşusu kalmadı → dfs(D) biter, çerçeve yığından çıkar (geri dönüş).
C'nin komşusu E zaten ziyaret edilmiş → atla.
C'nin gidilecek komşusu kalmadı → dfs(C) biter, çerçeve yığından çıkar (geri dönüş).
B'nin komşusu E zaten ziyaret edilmiş → atla.
B'nin gidilecek komşusu kalmadı → dfs(B) biter, çerçeve yığından çıkar (geri dönüş).
A'nın komşusu F zaten ziyaret edilmiş → atla.
A'nın komşusu I ziyaret edilmemiş → dfs(I) çağrılır: yeni çerçeve açılır, I işaretlenip yazdırılır.
I'nın komşusu A zaten ziyaret edilmiş → atla.
I'nın gidilecek komşusu kalmadı → dfs(I) biter, çerçeve yığından çıkar (geri dönüş).
dfs(A) biter, yığın boşalır. Sıra: A B C D G E F H I.
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: dfsYigin(A)
void dfsYigin(int s) { boolean[] ziyaret = new boolean[n]; Deque<Integer> yigin = new ArrayDeque<>(); yigin.push(s); ziyaret[s] = true; while (!yigin.isEmpty()) { int v = yigin.peek(); Integer yeni = null; for (int u : komsu.get(v)) if (!ziyaret[u]) { yeni = u; break; } if (yeni == null) yigin.pop(); // geri else { ziyaret[yeni] = true; yigin.push(yeni); } } }
Başlangıç s = A. ziyaret tümü false, yığın boş.
A yığına itilir ve işaretlenir. Yığının üstü = şu an bulunduğumuz düğüm.
peek = A; ilk ziyaret edilmemiş komşusu B (break) → işaretlenir ve yığına itilir.
peek = B; ilk ziyaret edilmemiş komşusu C (break) → işaretlenir ve yığına itilir.
peek = C; ilk ziyaret edilmemiş komşusu D (break) → işaretlenir ve yığına itilir.
peek = D; ilk ziyaret edilmemiş komşusu G (break) → işaretlenir ve yığına itilir.
peek = G; ilk ziyaret edilmemiş komşusu E (break) → işaretlenir ve yığına itilir.
peek = E; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür.
peek = G; ilk ziyaret edilmemiş komşusu F (break) → işaretlenir ve yığına itilir.
peek = F; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür.
peek = G; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür.
peek = D; ilk ziyaret edilmemiş komşusu H (break) → işaretlenir ve yığına itilir.
peek = H; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür.
peek = D; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür.
peek = C; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür.
peek = B; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür.
peek = A; ilk ziyaret edilmemiş komşusu I (break) → işaretlenir ve yığına itilir.
peek = I; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür.
peek = A; ziyaret edilmemiş komşusu yok (yeni = null) → pop, geri dönülür. Yığın boş: bitti. Ziyaret sırası özyinelemeli sürümle aynı.
Gezinme (BFS / DFS)
BFS ve DFS Karşılaştırması
| BFS | DFS | |
|---|---|---|
| Yardımcı yapı | Kuyruk (FIFO) | Yığın (LIFO) / özyineleme |
| Zaman (komşuluk listesi) | O(n + m) | O(n + m) |
| Ek bellek | O(n): en geniş seviye kuyrukta | O(n): en uzun yol yığında |
| En kısa yol? | Evet (ağırlıksız çizgede) | Hayır |
| Tipik kullanım | en az adım, seviye bulma | döngü tespiti, bileşenler, labirent |
Sıralar, komşuların alfabetik sırayla gezildiği varsayımıyla elde edildi; komşu sırası değişirse ziyaret sırası da değişir.
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: DFS ile bileşenleri numaralama
Tüm düğümler −1 (henüz hiçbir bileşene atanmadı).
Bileşen sayacı say = 0.
bil[0] = −1: yeni bileşen. say++ 0 verir, say = 1 olur; dfs(0, 0) çağrılır.
bil[0] = 0: 0, 0 numaralı bileşene boyandı.
bil[1] = 0: 1, 0 numaralı bileşene boyandı.
Komşu 0 zaten boyalı (bil = 0) → atla. Her kenara iki kez bakılır: O(n + m).
bil[2] = 0: 2, 0 numaralı bileşene boyandı.
Komşu 0 zaten boyalı (bil = 0) → atla. Her kenara iki kez bakılır: O(n + m).
bil[1] = 0: 1 önceki bir DFS'te zaten boyandı → atla.
bil[2] = 0: 2 önceki bir DFS'te zaten boyandı → atla.
bil[3] = −1: yeni bileşen. say++ 1 verir, say = 2 olur; dfs(3, 1) çağrılır.
bil[3] = 1: 3, 1 numaralı bileşene boyandı.
bil[4] = 1: 4, 1 numaralı bileşene boyandı.
Komşu 3 zaten boyalı (bil = 1) → atla. Her kenara iki kez bakılır: O(n + m).
bil[4] = 1: 4 önceki bir DFS'te zaten boyandı → atla.
bil[5] = −1: yeni bileşen. say++ 2 verir, say = 3 olur; dfs(5, 2) çağrılır.
bil[5] = 2: 5, 2 numaralı bileşene boyandı.
3 bileşen: {0, 1, 2}, {3, 4}, {5}. Aynı bil = aynı bileşen; bil[u] == bil[v] sorgusu O(1).
int bilesenler(int[] bil) { Arrays.fill(bil, -1); int say = 0; for (int s = 0; s < n; s++) if (bil[s] == -1) dfs(s, say++, bil); return say; } void dfs(int v, int c, int[] bil) { bil[v] = c; for (int u : komsu.get(v)) if (bil[u] == -1) dfs(u, c, bil); }
Gezinme (BFS / DFS)
İki Parçalı (Bipartite) Çizge Testi
- İki parçalı (bipartite): düğümler iki gruba ayrılır, her kenar gruplar arasında.
- Test = BFS ile 2-renkleme: komşuya zıt renk ver; aynı renkli iki komşu → değil.
- Eşdeğer: tek uzunlukta döngü yok.
// renk[] = -1 ile başlar; s'den BFS renk[s] = 0; q.add(s); while (!q.isEmpty()) { int w = q.poll(); for (int u : komsu.get(w)) if (renk[u] == -1) { renk[u] = 1 - renk[w]; q.add(u); } else if (renk[u] == renk[w]) return false; } return true;
BFS ile aynı maliyet: O(n + m). Kullanım: öğrenci–proje eşleştirme, iki vardiyalı çizelgeleme, çakışmasız gruplama.
Gezinme (BFS / DFS)
Topolojik Sıralama: Yalnızca DAG
- Bir yönlü çizgenin düğümlerini, her
u → vkenarında u, v'den önce gelecek biçimde sıralar. - Yalnızca yönlü döngüsüz çizgelerde (DAG) mümkündür.
- Sonuç tek olmak zorunda değildir; aynı anda seçilebilen birden çok giriş derecesi 0 düğüm olabilir.
- Kullanım: ders ön koşulları, görev bağımlılıkları, derleme ve iş akışı planlama.
Kahn(G): q = girişDerecesi 0 olan düğümler sonuc = [] while q boş değil: u = q'dan çıkar sonuc'a u ekle for u'dan çıkan her (u, v): girişDerecesi[v]-- if girişDerecesi[v] == 0: q'ya v ekle if sonuc.size < n: döngü var return sonuc
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: Topolojik Sıralama (Kahn)
List<Integer> kahn(List<List<Integer>> adj) {
int n = adj.size();
int[] giris = new int[n];
for (List<Integer> l : adj)
for (int v : l) giris[v]++;
Queue<Integer> q = new ArrayDeque<>();
for (int v = 0; v < n; v++)
if (giris[v] == 0) q.add(v);
List<Integer> sonuc = new ArrayList<>();
while (!q.isEmpty()) {
int u = q.poll();
sonuc.add(u);
for (int v : adj.get(u))
if (--giris[v] == 0) q.add(v);
}
return sonuc; // boyut < n → döngü
}Ders ön koşulu örneği: 6 düğüm, 7 yönlü kenar (u → v: u, v'den önce). giris[] sıfırlarla başlar.
0'dan çıkan kenarlar 0→3: hedeflerin giriş derecesi birer artar.
1'den çıkan kenarlar 1→3, 1→4: hedeflerin giriş derecesi birer artar.
2'den çıkan kenarlar 2→0, 2→4: hedeflerin giriş derecesi birer artar.
3'ten çıkan kenarlar 3→5: hedeflerin giriş derecesi birer artar.
4'ten çıkan kenarlar 4→5: hedeflerin giriş derecesi birer artar.
Giriş derecesi 0 olanlar (1, 2) hiçbir şeye bağımlı değil → kuyruğa. (5'in listesi boş olduğundan sayma turu bir şey değiştirmedi.)
1 kuyruktan çıkar ve sonuca eklenir: ön koşullarının hepsi zaten sonuçta.
1→3 silinmiş sayılır: giris[3] = 1; 3 hâlâ başka bir ön koşul bekliyor.
1→4 silinmiş sayılır: giris[4] = 1; 4 hâlâ başka bir ön koşul bekliyor.
2 kuyruktan çıkar ve sonuca eklenir: ön koşullarının hepsi zaten sonuçta.
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: Topolojik Sıralama (Kahn) (devam)
List<Integer> kahn(List<List<Integer>> adj) {
int n = adj.size();
int[] giris = new int[n];
for (List<Integer> l : adj)
for (int v : l) giris[v]++;
Queue<Integer> q = new ArrayDeque<>();
for (int v = 0; v < n; v++)
if (giris[v] == 0) q.add(v);
List<Integer> sonuc = new ArrayList<>();
while (!q.isEmpty()) {
int u = q.poll();
sonuc.add(u);
for (int v : adj.get(u))
if (--giris[v] == 0) q.add(v);
}
return sonuc; // boyut < n → döngü
}2→0 silinmiş sayılır: giris[0] = 0 → 0 artık hazır, kuyruğa eklenir.
2→4 silinmiş sayılır: giris[4] = 0 → 4 artık hazır, kuyruğa eklenir.
0 kuyruktan çıkar ve sonuca eklenir: ön koşullarının hepsi zaten sonuçta.
0→3 silinmiş sayılır: giris[3] = 0 → 3 artık hazır, kuyruğa eklenir.
4 kuyruktan çıkar ve sonuca eklenir: ön koşullarının hepsi zaten sonuçta.
4→5 silinmiş sayılır: giris[5] = 1; 5 hâlâ başka bir ön koşul bekliyor.
3 kuyruktan çıkar ve sonuca eklenir: ön koşullarının hepsi zaten sonuçta.
3→5 silinmiş sayılır: giris[5] = 0 → 5 artık hazır, kuyruğa eklenir.
5 kuyruktan çıkar ve sonuca eklenir: ön koşullarının hepsi zaten sonuçta.
Kuyruk boş; sonuç 1 2 0 4 3 5 ve boyutu 6 = n → döngü yok. Her kenar bir kez azaltıldı: O(n + m).
Gezinme (BFS / DFS) · Kodu adım adım çalıştır
Kod İzleme: yönlü çizgede döngü (3 renkli DFS)
renk[0] = 1 (gri): 0 şu anki DFS yolunda.
Kenar 0 → 1: 1 beyaz → özyinelemeli çağrı.
renk[1] = 1 (gri): 1 şu anki DFS yolunda.
Kenar 1 → 2: 2 beyaz → özyinelemeli çağrı.
renk[2] = 1 (gri): 2 şu anki DFS yolunda.
2'nin çıkan kenarı yok: renk[2] = 2 (siyah), false döner; yığından çıkar.
1'in tüm komşuları bitti: renk[1] = 2 (siyah), false döner; yığından çıkar.
Kenar 0 → 3: 3 beyaz → özyinelemeli çağrı.
renk[3] = 1 (gri): 3 şu anki DFS yolunda.
Kenar 3 → 2: 2 siyah (bitmiş). Döngü değil: 2'den geri 3'e yol yok. Sadece gri düğüm döngü demektir.
Kenar 3 → 4: 4 beyaz → özyinelemeli çağrı.
renk[4] = 1 (gri): 4 şu anki DFS yolunda.
Kenar 4 → 0: 0 gri, yani yolda! Bu bir geri kenar → döngü 0 → 3 → 4 → 0 bulundu.
Alttaki çağrı true döndü → 3 de true döndürür.
Alttaki çağrı true döndü → 0 da true döndürür.
// renk: 0 beyaz, 1 gri (yolda), 2 siyah boolean donguVar(int v, int[] renk) { renk[v] = 1; for (int u : komsu.get(v)) { if (renk[u] == 1) return true; // geri kenar if (renk[u] == 0 && donguVar(u, renk)) return true; } renk[v] = 2; return false; }
En Kısa Yol
Ağırlıksız En Kısa Yol Problemi
- Kaynak düğümden diğer tüm düğümlere olan en kısa yolları bulur (tek kaynaklı en kısa yol).
- Her kenarın aynı ağırlığa (1) sahip olduğu varsayılır: yol uzunluğu = kenar sayısı.
- Çözüm: BFS. Seviye k'daki düğümün uzaklığı tam olarak k'dır.
Kaynak 1; düğümlerin yanındaki sayı 1'e uzaklık
En Kısa Yol
Ağırlıksız En Kısa Yol Algoritması
mesafe[tüm v] = ∞ mesafe[s] = 0 kuyruk.ekle(s) while kuyruk boş değil: w = kuyruk.çıkar() for w'nin her komşusu u: if mesafe[u] == ∞: mesafe[u] = mesafe[w] + 1 kuyruk.ekle(u) return mesafe
"mesafe = ∞" aynı zamanda "ziyaret edilmedi" demektir; ayrı bir dizi gerekmez. Java'da cizge: düğüm → komşu listesi (Map<Integer, List<Integer>>), çünkü düğümler 1'den başlayarak numaralanmış.
Map<Integer, Integer> enKisaYol(int s) { final int INF = Integer.MAX_VALUE; // ∞ Map<Integer, Integer> mesafe = new HashMap<>(); for (int v : cizge.keySet()) mesafe.put(v, INF); mesafe.put(s, 0); Queue<Integer> q = new ArrayDeque<>(); q.offer(s); while (!q.isEmpty()) { int w = q.poll(); for (int u : cizge.get(w)) if (mesafe.get(u) == INF) { mesafe.put(u, mesafe.get(w) + 1); q.offer(u); } } return mesafe; }
En Kısa Yol · Kodu adım adım çalıştır
Kod İzleme: enKisaYol(1)
Map<Integer, Integer> enKisaYol(int s) { final int INF = Integer.MAX_VALUE; // ∞ Map<Integer, Integer> mesafe = new HashMap<>(); for (int v : cizge.keySet()) mesafe.put(v, INF); mesafe.put(s, 0); Queue<Integer> q = new ArrayDeque<>(); q.offer(s); while (!q.isEmpty()) { int w = q.poll(); for (int u : cizge.get(w)) if (mesafe.get(u) == INF) { mesafe.put(u, mesafe.get(w) + 1); q.offer(u); } } return mesafe; }
Kaynak s = 1. Tüm mesafeler INF (∞): "henüz ulaşılmadı" = "ziyaret edilmedi".
mesafe[1] = 0 ve 1 kuyruğa girer.
1 çıkarılır; mesafe[1] = 0. Komşuları 2, 3, 4 incelenir.
mesafe[2] = INF → mesafe[2] = 0 + 1 = 1, 2 kuyruğa eklenir.
mesafe[3] = INF → mesafe[3] = 0 + 1 = 1, 3 kuyruğa eklenir.
mesafe[4] = INF → mesafe[4] = 0 + 1 = 1, 4 kuyruğa eklenir.
2 çıkarılır; mesafe[2] = 1. Komşuları 1, 5 incelenir.
1: mesafesi zaten belli (0) → değişmez. BFS'de ilk bulunan mesafe en kısadır.
mesafe[5] = INF → mesafe[5] = 1 + 1 = 2, 5 kuyruğa eklenir.
3 çıkarılır; mesafe[3] = 1. Komşuları 1, 8 incelenir.
1: mesafesi zaten belli (0) → değişmez. BFS'de ilk bulunan mesafe en kısadır.
mesafe[8] = INF → mesafe[8] = 1 + 1 = 2, 8 kuyruğa eklenir.
4 çıkarılır; mesafe[4] = 1. Komşuları 1, 6 incelenir.
En Kısa Yol · Kodu adım adım çalıştır
Kod İzleme: enKisaYol(1) (devam)
Map<Integer, Integer> enKisaYol(int s) { final int INF = Integer.MAX_VALUE; // ∞ Map<Integer, Integer> mesafe = new HashMap<>(); for (int v : cizge.keySet()) mesafe.put(v, INF); mesafe.put(s, 0); Queue<Integer> q = new ArrayDeque<>(); q.offer(s); while (!q.isEmpty()) { int w = q.poll(); for (int u : cizge.get(w)) if (mesafe.get(u) == INF) { mesafe.put(u, mesafe.get(w) + 1); q.offer(u); } } return mesafe; }
1: mesafesi zaten belli (0) → değişmez. BFS'de ilk bulunan mesafe en kısadır.
mesafe[6] = INF → mesafe[6] = 1 + 1 = 2, 6 kuyruğa eklenir.
5 çıkarılır; mesafe[5] = 2. Komşuları 2, 8 incelenir.
2, 8: mesafesi zaten belli (1, 2) → değişmez. BFS'de ilk bulunan mesafe en kısadır.
8 çıkarılır; mesafe[8] = 2. Komşuları 3, 5, 7 incelenir.
3, 5: mesafesi zaten belli (1, 2) → değişmez. BFS'de ilk bulunan mesafe en kısadır.
mesafe[7] = INF → mesafe[7] = 2 + 1 = 3, 7 kuyruğa eklenir.
6 çıkarılır; mesafe[6] = 2. Komşuları 4, 7 incelenir.
4, 7: mesafesi zaten belli (1, 3) → değişmez. BFS'de ilk bulunan mesafe en kısadır.
7 çıkarılır; mesafe[7] = 3. Komşuları 6, 8 incelenir.
6, 8: mesafesi zaten belli (2, 2) → değişmez. BFS'de ilk bulunan mesafe en kısadır.
Kuyruk boş → return mesafe. 7'ye uzaklık 3 (1 → 3 → 8 → 7 bulundu; 1 → 4 → 6 → 7 de 3 kenar). O(n + m).
En Kısa Yol
Ağırlıklı En Kısa Yol Problemi
- Kaynak düğümden diğer tüm düğümlere olan en kısa yolları bulur.
- Her kenarın farklı bir ağırlığı olabilir: uzaklık, süre, maliyet…
- Yol uzunluğu = kenar ağırlıklarının toplamı.
A'dan F'ye en kısa yol: A → C → E → D → F = 2 + 3 + 4 + 11 = 20
En Kısa Yol
Dijkstra Algoritması: Fikir ve Sözde Kod
- Kesinleşmemiş düğümlerden mesafesi en küçük olanı seç.
- Komşuların mesafesini gevşetme ile güncelle.
- Seçim için öncelikli kuyruk, yolu kurmak için
önceki[]kullan.
Dijkstra(G, s): for her düğüm v: mesafe[v] = ∞ önceki[v] = tanımsız mesafe[s] = 0 Q = tüm düğümler // öncelikli kuyruk while Q boş değil: u = Q.çıkar() // en küçük mesafe[u] for u'nun Q'daki her komşusu v: alt = mesafe[u] + ağırlık(u, v) if alt < mesafe[v]: mesafe[v] = alt önceki[v] = u // Q'da v'nin önceliğini azalt
En Kısa Yol
Dijkstra Algoritması: Java
long[] dijkstra(List<List<int[]>> adj, int s, int[] onceki) { // ağırlıklar ≥ 0 long[] d = new long[adj.size()]; Arrays.fill(d, Long.MAX_VALUE); d[s] = 0; Arrays.fill(onceki, -1); PriorityQueue<long[]> pq = // {düğüm, mesafe} new PriorityQueue<>((a, b) -> Long.compare(a[1], b[1])); pq.add(new long[]{s, 0}); while (!pq.isEmpty()) { long[] e = pq.poll(); int u = (int) e[0]; if (e[1] > d[u]) continue; // bayat kayıt: atla for (int[] k : adj.get(u)) { int v = k[0]; long alt = d[u] + k[1]; if (alt < d[v]) { d[v] = alt; onceki[v] = u; pq.add(new long[]{v, alt}); } } } return d; }
PriorityQueue'sunda "önceliği azalt" yok: yeni kayıt eklenir, eskisi çıkınca bayat olarak atlanır. onceki[v] = u en kısa yol ağacını tutar; yol hedeften geriye izlenir.En Kısa Yol · Kodu adım adım çalıştır
Kod İzleme: dijkstra(adj, A)
// d[] = ∞, d[s] = 0, onceki[] = -1 pq.add(new long[]{s, 0}); while (!pq.isEmpty()) { long[] e = pq.poll(); int u = (int) e[0]; if (e[1] > d[u]) continue; // bayat for (int[] k : adj.get(u)) { int v = k[0]; long alt = d[u] + k[1]; if (alt < d[v]) { d[v] = alt; onceki[v] = u; pq.add(new long[]{v, alt}); } } }
Kaynak A: d[A] = 0, diğerleri ∞. Kuyruğa {A, 0} eklenir. Öncelik = mesafe.
En küçük kayıt {A, 0} çıkar; 0 = d[A] (güncel) → A kesinleşir. Komşuları B, F, I gevşetilir.
v = B: alt = 0 + 4 = 4 < ∞ → d[B] = 4, onceki[B] = A, {B, 4} kuyruğa.
v = F: alt = 0 + 7 = 7 < ∞ → d[F] = 7, onceki[F] = A, {F, 7} kuyruğa.
v = I: alt = 0 + 3 = 3 < ∞ → d[I] = 3, onceki[I] = A, {I, 3} kuyruğa.
En küçük kayıt {I, 3} çıkar; 3 = d[I] (güncel) → I kesinleşir. Komşusu A gevşetilir.
v = A: alt = 6 ≥ d[A] = 0 → güncelleme yok.
En küçük kayıt {B, 4} çıkar; 4 = d[B] (güncel) → B kesinleşir. Komşuları A, C, E gevşetilir.
v = A: alt = 8 ≥ d[A] = 0 → güncelleme yok.
v = C: alt = 4 + 1 = 5 < ∞ → d[C] = 5, onceki[C] = B, {C, 5} kuyruğa.
v = E: alt = 4 + 2 = 6 < ∞ → d[E] = 6, onceki[E] = B, {E, 6} kuyruğa.
En küçük kayıt {C, 5} çıkar; 5 = d[C] (güncel) → C kesinleşir. Komşuları B, D, E gevşetilir.
v = B: alt = 6 ≥ d[B] = 4 → güncelleme yok.
v = D: alt = 5 + 7 = 12 < ∞ → d[D] = 12, onceki[D] = C, {D, 12} kuyruğa.
v = E: alt = 8 ≥ d[E] = 6 → güncelleme yok.
En Kısa Yol · Kodu adım adım çalıştır
Kod İzleme: dijkstra(adj, A) (devam)
// d[] = ∞, d[s] = 0, onceki[] = -1 pq.add(new long[]{s, 0}); while (!pq.isEmpty()) { long[] e = pq.poll(); int u = (int) e[0]; if (e[1] > d[u]) continue; // bayat for (int[] k : adj.get(u)) { int v = k[0]; long alt = d[u] + k[1]; if (alt < d[v]) { d[v] = alt; onceki[v] = u; pq.add(new long[]{v, alt}); } } }
En küçük kayıt {E, 6} çıkar; 6 = d[E] (güncel) → E kesinleşir. Komşuları B, C, G gevşetilir.
v = B: alt = 8 ≥ d[B] = 4; v = C: alt = 9 ≥ d[C] = 5 → güncelleme yok.
v = G: alt = 6 + 2 = 8 < ∞ → d[G] = 8, onceki[G] = E, {G, 8} kuyruğa.
En küçük kayıt {F, 7} çıkar; 7 = d[F] (güncel) → F kesinleşir. Komşuları A, G gevşetilir.
v = A: alt = 14 ≥ d[A] = 0; v = G: alt = 9 ≥ d[G] = 8 → güncelleme yok.
En küçük kayıt {G, 8} çıkar; 8 = d[G] (güncel) → G kesinleşir. Komşuları D, E, F gevşetilir.
v = D: alt = 8 + 3 = 11 < 12 → d[D] = 11, onceki[D] = G, {D, 11} kuyruğa. Eski {D, 12} kaydı kuyrukta bayat kalır.
v = E: alt = 10 ≥ d[E] = 6; v = F: alt = 10 ≥ d[F] = 7 → güncelleme yok.
En küçük kayıt {D, 11} çıkar; 11 = d[D] (güncel) → D kesinleşir. Komşuları C, G, H gevşetilir.
v = C: alt = 18 ≥ d[C] = 5; v = G: alt = 14 ≥ d[G] = 8 → güncelleme yok.
v = H: alt = 11 + 5 = 16 < ∞ → d[H] = 16, onceki[H] = D, {H, 16} kuyruğa.
{D, 12} çıktı ama d[D] = 11 < 12: bayat kayıt (daha iyi yol sonradan bulundu) → continue, atlanır.
En küçük kayıt {H, 16} çıkar; 16 = d[H] (güncel) → H kesinleşir. Komşusu D gevşetilir.
v = D: alt = 21 ≥ d[D] = 11 → güncelleme yok.
Kuyruk boş → bitti. onceki[] geri izlenir: H ← D ← G ← E ← B ← A. En kısa yol A → B → E → G → D → H = 16.
En Kısa Yol · Kodu adım adım çalıştır
Kod İzleme: onceki[] ile en kısa yolu yazdırma
Dijkstra'nın bıraktığı onceki[]: her düğüm için en kısa yolda kendinden önceki düğüm. Boş liste y.
Hedeften başla: v = F.
F listenin başına eklenir (sonra gelenler daha önceki düğümler).
v = onceki[…] = D: bir adım geri.
D listenin başına eklenir (sonra gelenler daha önceki düğümler).
v = onceki[…] = E: bir adım geri.
E listenin başına eklenir (sonra gelenler daha önceki düğümler).
v = onceki[…] = C: bir adım geri.
C listenin başına eklenir (sonra gelenler daha önceki düğümler).
v = onceki[…] = A: bir adım geri.
A listenin başına eklenir (sonra gelenler daha önceki düğümler).
onceki[A] = −1: kaynağa varıldı, döngü biter. 5 tur = yoldaki düğüm sayısı → O(yol uzunluğu).
İlk öğe kaynak A mı? Evet → t ulaşılabilir. (Ulaşılamayan t için zincir A'ya varmaz; boş liste döner.)
Sonuç A → C → E → D → F, uzunluk d[F] = 2 + 3 + 4 + 11 = 20.
List<Integer> yol(int[] onceki, int s, int t) { LinkedList<Integer> y = new LinkedList<>(); for (int v = t; v != -1; v = onceki[v]) y.addFirst(v); if (y.getFirst() != s) return List.of(); return y; // s ... t }
En Kısa Yol
Dijkstra Algoritmasının Analizi
| Uygulama | Karmaşıklık | Neden? |
|---|---|---|
| Öncelikli kuyruk (ikili heap) | O((n + m) log n) | n kez en küçüğü çıkar, en çok m kez güncelle/ekle; her biri O(log n) |
| Kuyruksuz (dizi taraması) | O(n² + m) = O(n²) | Her adımda en küçük mesafeli düğümü bulmak için n düğüm taranır; komşu kontrolleri toplam m |
Minimum Kapsayan Ağaç
Minimum Kapsayan Ağaç (Minimum Spanning Tree)
- Kapsayan ağaç (spanning tree): bağlantılı, ağırlıklı bir çizgenin tüm düğümlerini içeren ve döngü içermeyen alt çizgesi; n düğüm için n − 1 kenar.
- Minimum kapsayan ağaç (MKA, MST): toplam kenar ağırlığı en düşük olan kapsayan ağaç.
- Uygulama: şehirleri en az kabloyla / boruyla birbirine bağlamak.
- Kruskal algoritması, "bu kenar döngü oluşturur mu?" sorusunu Union-Find (ayrık kümeler) ile yanıtlar:
find(x)x'in kümesinin temsilcisini verir;union(u, v)farklı iki kümeyi birleştiriptrue, aynı kümedeysefalsedöndürür.
Minimum Kapsayan Ağaç
Kruskal Algoritması
Kruskal(G): T = ∅ // seçilen kenarlar for her düğüm v: makeSet(v) kenarları ağırlığa göre sırala for her (u, v), küçükten büyüğe: if find(u) != find(v): // döngü yok T.ekle((u, v)) union(u, v) if |T| == n − 1: break return T // MST (bağlı değilse: orman)
- Açgözlü: her zaman döngü oluşturmayan en hafif kenarı seç.
- u ve v zaten aynı kümedeyse, kenar bir döngü kapatır → atla.
- Başta n ayrık küme; her kabul edilen kenar iki kümeyi birleştirir.
- Çizge bağlantısızsa n−1 kenara ulaşılamaz; algoritma her bağlantılı bileşen için bir ağaçtan oluşan minimum kapsayan orman döndürür.
| Sıralama | O(m log m) |
| 2m find + (n−1) union | O(m · α(n)) |
| Toplam | O(m log n) |
Minimum Kapsayan Ağaç
Kruskal Algoritması Adım Adım
Kenarlar ağırlığa göre sıralandı (solda). Başlangıçta 10 düğüm = 10 ayrı küme, T boş.
I–J (0): find(I) ≠ find(J) → kabul, kümeler birleşir.
A–E (1): find(A) ≠ find(E) → kabul, kümeler birleşir.
C–I (1): find(C) ≠ find(I) → kabul, kümeler birleşir.
E–F (1): find(E) ≠ find(F) → kabul, kümeler birleşir.
G–H (1): find(G) ≠ find(H) → kabul, kümeler birleşir.
B–D (2): find(B) ≠ find(D) → kabul, kümeler birleşir.
C–J (2): C ve J zaten aynı kümede → döngü oluşturur, reddedilir.
D–E (2): find(D) ≠ find(E) → kabul, kümeler birleşir.
D–H (2): find(D) ≠ find(H) → kabul, kümeler birleşir.
A–D (4): A ve D zaten aynı kümede → döngü oluşturur, reddedilir.
B–C (4): find(B) ≠ find(C) → kabul, kümeler birleşir. 9 = n − 1 kenar seçildi: bitti, toplam ağırlık 14.
Minimum kapsayan ağaç: A–E, E–F, D–E, B–D, D–H, G–H, B–C, C–I, I–J. Kalan kenarlara bakmaya gerek yok.
Minimum Kapsayan Ağaç · Kodu adım adım çalıştır
Kod İzleme: kruskal() + Union-Find
List<int[]> kruskal(int n, List<int[]> E) { E.sort((a, b) -> // {u, v, w} Integer.compare(a[2], b[2])); UnionFind uf = new UnionFind(n); List<int[]> T = new ArrayList<>(); for (int[] e : E) { if (uf.union(e[0], e[1])) // döngüsüz T.add(e); if (T.size() == n - 1) break; } return T; }
Kenarlar ağırlığa göre sıralanır (A=0 … J=9; sort kararlıdır, eşit ağırlıklar girdi sırasında kalır). O(m log m).
new UnionFind(10): her düğüm kendi kümesi (10 kök). T boş.
I–J (0): kökler I ≠ J → union true: ağaçlar birleşir (I → J), kenar T'ye eklenir.
A–E (1): kökler A ≠ E → union true: ağaçlar birleşir (A → E), kenar T'ye eklenir.
C–I (1): kökler C ≠ J → union true: ağaçlar birleşir (C → J), kenar T'ye eklenir.
E–F (1): kökler E ≠ F → union true: ağaçlar birleşir (F → E), kenar T'ye eklenir.
G–H (1): kökler G ≠ H → union true: ağaçlar birleşir (G → H), kenar T'ye eklenir.
B–D (2): kökler B ≠ D → union true: ağaçlar birleşir (B → D), kenar T'ye eklenir.
C–J (2): find(C) = find(J) = J → aynı ağaç, union false döner: kenar döngü kurar, atlanır.
D–E (2): kökler D ≠ E → union true: ağaçlar birleşir (D → E), kenar T'ye eklenir.
D–H (2): kökler E ≠ H → union true: ağaçlar birleşir (H → E), kenar T'ye eklenir.
A–D (4): find(A) = find(D) = E → aynı ağaç, union false döner: kenar döngü kurar, atlanır.
B–C (4): kökler E ≠ J → union true: ağaçlar birleşir (J → E), kenar T'ye eklenir. |T| = 9 = n − 1 → break.
return T: 9 kenar, toplam ağırlık 14. Tek ağaç kaldı. Kalan 7 kenara hiç bakılmadı.
Minimum Kapsayan Ağaç
İkinci Örnek: Havalimanları
Goodrich'teki örnek: 9 havalimanı, 19 uçuş hattı (mil). Kruskal sırası:
| 144, 184, 187, 337, 621 | kabul |
| 740 ORD–JFK | ret (döngü) |
| 802 | kabul |
| 849, 867 | ret |
| 946 | kabul |
| 1090, 1121 | ret |
| 1235 LAX–DFW | kabul → 8 kenar |
Toplam: 4456 mil.
Minimum Kapsayan Ağaç
Prim Algoritması
Prim(G, s):
ağaç = {s}, T = ∅
s'nin kenarlarını kuyruğa ekle
while |T| < n − 1:
(u, v) = en hafif kenar // u ağaçta
if v ağaçta: atla // döngü
v'yi ağaca, (u, v)'yi T'ye ekle
v'nin dışarı giden kenarlarını
kuyruğa ekle
return T- Ağaç tek bir düğümden başlar ve her adımda bir düğüm büyür.
- Her adımda, ağaçtaki bir düğümü ağaç dışındaki bir düğüme bağlayan kenarların en hafifi seçilir.
- Ağacı dışarıya bağlayan kenarlardan en hafifi her zaman bir minimum kapsayan ağaçta bulunur: ağaç dışarıya bir yolla bağlanmak zorundadır ve bu kenar en ucuz bağlantıdır.
- Dijkstra'ya benzer; fark kuyruğun anahtarıdır: kaynağa uzaklık değil, tek kenarın ağırlığı.
- Kruskal'dan farkı: ara durumda hep tek bir ağaç vardır, Union-Find gerekmez.
Minimum Kapsayan Ağaç
Prim: Java (öncelikli kuyrukla, tembel sürüm)
List<int[]> prim(List<List<int[]>> adj, int s) { int n = adj.size(); // adj.get(u): {v, w} boolean[] agacta = new boolean[n]; PriorityQueue<int[]> pq = new PriorityQueue<>( (a, b) -> Integer.compare(a[2], b[2])); // {u, v, w} List<int[]> T = new ArrayList<>(); agacta[s] = true; for (int[] e : adj.get(s)) pq.add(new int[]{s, e[0], e[1]}); while (!pq.isEmpty() && T.size() < n - 1) { int[] e = pq.poll(); // en hafif aday if (agacta[e[1]]) continue; // bayat: döngü kurar agacta[e[1]] = true; T.add(e); for (int[] f : adj.get(e[1])) if (!agacta[f[0]]) pq.add(new int[]{e[1], f[0], f[1]}); } return T; }
- Kuyrukta {u, v, w} kayıtları: u ağaçta, v aday.
- Tembel sürüm: v ağaca girince v'ye giden eski kayıtlar silinmez; kuyruktan çıktıklarında
continueile atlanır (bayat kayıt). - Her kenar en çok iki kez kuyruğa girer: en çok 2m ekleme ve çıkarma, her biri O(log m).
- Toplam O(m log m) = O(m log n); ek bellek O(m).
Minimum Kapsayan Ağaç
Prim Algoritması Adım Adım (A'dan)
Başlangıç: ağaçta yalnız A. A'nın kenarları kuyruğa girer: A–E 1, A–D 4, A–B 5. Eşit ağırlıklarda alfabetik sıra izlenir.
A–E (1): ağaçla dışarıyı bağlayan en hafif kenar → E ağaca girer. Yeni adaylar: E–F, E–D.
E–F (1): ağaçla dışarıyı bağlayan en hafif kenar → F ağaca girer. Yeni adaylar: F–D, F–G.
E–D (2): ağaçla dışarıyı bağlayan en hafif kenar → D ağaca girer. Yeni adaylar: D–B, D–H, D–G. Üstü çizili kayıtlar bayatladı (iki ucu da ağaçta); kuyruktan çıkınca atlanır.
D–B (2): ağaçla dışarıyı bağlayan en hafif kenar → B ağaca girer. Yeni adaylar: B–C.
D–H (2): ağaçla dışarıyı bağlayan en hafif kenar → H ağaca girer. Yeni adaylar: H–G, H–C, H–I.
H–G (1): ağaçla dışarıyı bağlayan en hafif kenar → G ağaca girer. Yeni adaylar: G–I. Üstü çizili kayıtlar bayatladı (iki ucu da ağaçta); kuyruktan çıkınca atlanır.
B–C (4): ağaçla dışarıyı bağlayan en hafif kenar → C ağaca girer. Yeni adaylar: C–I, C–J.
C–I (1): ağaçla dışarıyı bağlayan en hafif kenar → I ağaca girer. Yeni adaylar: I–J.
I–J (0): ağaçla dışarıyı bağlayan en hafif kenar → J ağaca girer. 9 = n − 1 kenar: bitti, toplam ağırlık 14.
Prim ağacı: A–E, E–F, E–D, D–B, D–H, H–G, B–C, C–I, I–J. Kruskal ile aynı toplam ağırlık (14). Eşit ağırlıklar başka sırayla çıkarsa (ör. B–C yerine H–C) farklı ama yine 14 ağırlıklı bir ağaç bulunabilir.
Minimum Kapsayan Ağaç
Kruskal ve Prim Karşılaştırması
| Kruskal | Prim | |
|---|---|---|
| Fikir | kenarları küçükten büyüğe dener, ağaçları birleştirir | tek ağacı bir düğümden başlayıp büyütür |
| Ara durum | orman (birden çok ağaç) | tek ağaç |
| Veri yapısı | sıralı kenar listesi + Union-Find | öncelikli kuyruk + işaret dizisi, komşuluk listesi |
| Döngü kontrolü | find(u) == find(v) | v zaten ağaçta mı? |
| Maliyet | O(m log n) (sıralama baskın) | O(m log n) (heap ile); matrisle O(n²) |
| Ne zaman? | seyrek çizge, kenarlar liste olarak hazır | komşuluk listesi hazır; çok yoğun çizgede O(n²) sürüm |
Özet
Özet
| Algoritma | Yapı | Zaman (liste ile) | Ne verir? |
|---|---|---|---|
| BFS | kuyruk | O(n + m) | seviye sırası, ağırlıksız en kısa yol |
| DFS | yığın / özyineleme | O(n + m) | derinlik öncelikli gezinme, döngü, bileşenler |
| Topolojik | kuyruk / DFS | O(n + m) | DAG için bağımlılık sırası |
| Dijkstra | öncelikli kuyruk | O((n + m) log n) | ağırlıklı en kısa yol (ağırlık ≥ 0) |
| Kruskal | sıralama + Union-Find | O(m log n) | minimum kapsayan ağaç |
| Prim | öncelikli kuyruk | O(m log n) | minimum kapsayan ağaç |
Özet
Etkileşimli Simülatörler
Özet
Örnek Problemler
- 10 düğümlü basit yönsüz bir çizgede en fazla kaç kenar olabilir? Derecelerin toplamı 30 ise kaç kenar vardır?
- n = 10.000 düğüm, m = 30.000 kenar olan bir yol ağını hangi gösterimle saklarsınız? Neden?
- Gezinme örneğindeki çizgede BFS ve DFS'yi H'den başlatın (komşular alfabetik).
- DFS'yi yığın yerine kuyrukla yazarsak hangi algoritmayı elde ederiz?
- Ağırlıklı en kısa yol örneğinde B → C kenarı 5 yerine 1 olsaydı A'dan D'ye en kısa yol ne olurdu?
- Kruskal örneğinde C–J kenarı neden reddedildi?
- Prim'i aynı çizgede A yerine J'den başlatınca ilk üç seçilen kenar ve toplam ağırlık ne olur?
Cevapları göster
- 10·9/2 = 45 kenar; Σ deg = 2m → m = 15.
- Komşuluk listesi: O(n + m) ≈ 40 bin kayıt; matris 10⁸ hücre ister ve çoğu 0 olur.
- BFS: H D C G B E F A I · DFS: H D C B A F G E I.
- Ziyaret sırası BFS'ye dönüşür (yığının üstü yerine kuyruğun önü işlenir).
- A → C yolu değişmez (2 < 4 + 1); D için A → C → E → D = 2 + 3 + 4 = 9, yine 9.
- C–I ve I–J daha önce seçilmişti; C ile J aynı kümede, kenar C–I–J–C döngüsünü kapatır.
- J–I (0), I–C (1); kuyruktan çıkan C–J (2) bayattır, atlanır; üçüncü kenar C–B (4). Başlangıç düğümü toplamı değiştirmez: yine 14.
Özet
Alıştırmalar
- Yönsüz kenarlar (0,1), (0,2), (1,3), (2,3), (3,4), (4,5): 0'dan BFS sırası ve mesafeler? (komşular küçükten büyüğe)
- Aynı çizgenin komşuluk matrisinde kaç hücre 1'dir? Komşuluk listesinde toplam kaç kayıt vardır?
- Yönlü 0→1, 1→2, 2→0, 2→3 için
donguVar(0)hangi kenarı incelerken true döner? - 0→1, 0→2, 1→3, 2→3 DAG'ı için kaç farklı topolojik sıra vardır? Yazın.
onceki = [-1, 0, 0, 1, 3]ise 0'dan 4'e yol nedir? 2'den 4'e?- 5 düğümlü bir döngü (C5) iki parçalı mıdır? Bağlantılı bileşen sayısı kaçtır?
- Tembel Prim'de kuyruğa en çok kaç kayıt girer? Kayıt ne zaman bayatlar?
Cevaplar / ipuçları
- Sıra 0 1 2 3 4 5; mesafeler 0, 1, 1, 2, 3, 4.
- 2m = 12 hücre (simetrik); listede de 2m = 12 kayıt.
- 2 → 0 kenarında: 0 gri (yolda) → geri kenar.
- İki: 0 1 2 3 ve 0 2 1 3.
- 0 → 1 → 3 → 4. onceki zinciri 2'ye uğramaz: 2'den değil, 0 kaynağından kurulmuş yol ağacıdır.
- Hayır (tek uzunlukta döngü). Bağlantılı: 1 bileşen.
- Her kenar en çok iki kez (iki ucundan): 2m. Hedef düğümü ağaca girince bayatlar.
Özet
Kaynaklar
- Goodrich, Tamassia ve Goldwasser, Data Structures and Algorithms in Java, 6. baskı, 2014.
- Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, 2022.
- Sedgewick ve Wayne, Algorithms, 4. baskı, 2011.