Graflar: gösterim, BFS, DFS ve topolojik sıralama
Grafı komşuluk matrisi ve listesiyle saklamak, BFS ve DFS ile gezmek, bağlı bileşenleri saymak ve DAG'ı topolojik sıraya dizmek; sınavın 'ziyaret sırasını yaz' sorularının kuralları.
Ağaçlarda her düğümün tek ebeveyni vardı. Graf (graph) bu kısıtı kaldırır: herhangi iki köşe arasında bağlantı olabilir, döngü olabilir, hiç bağlantı olmayabilir. Yol haritası, sosyal ağ, ders önkoşulları, internet bağlantıları hep graftır. Gelecek hafta bunların üzerinde en kısa yol algoritmalarını çalıştıracağız; bu hafta temeli kuruyoruz: grafı nasıl saklarız, nasıl gezeriz.
Sınavda bu haftadan gelecek sorular:
- Gösterim: “Bu grafın komşuluk listesini/matrisini yaz”, “bellek ne kadar?”
- Gezinme sırası: “0’dan BFS/DFS yap, ziyaret sırasını yaz.” Kural genelde “komşuları artan sırada gez”.
- Topolojik sıralama: önkoşul grafında geçerli bir ders sırası.
- Metot yazma: BFS, DFS ya da bileşen sayma.
Terimler
| Terim | Anlamı |
|---|---|
| köşe (vertex, node) | grafın elemanı, V tane |
| kenar (edge) | iki köşe arasındaki bağlantı, E tane |
| yönsüz (undirected) | u-v kenarı iki yönde de geçilir |
| yönlü (directed, digraph) | u→v kenarı sadece u’dan v’ye |
| ağırlıklı (weighted) | her kenarın bir maliyeti var |
| derece (degree) | köşeye değen kenar sayısı; yönlüde giriş/çıkış derecesi (in/out-degree) |
| yol (path) | ardışık kenarlarla bağlı köşe dizisi |
| döngü (cycle) | başladığı köşede biten yol |
| bağlı (connected) | her köşe çiftinin arasında yol var |
| DAG | yönlü, döngüsüz graf (directed acyclic graph) |
Yönsüz grafta her kenar iki köşenin derecesine katkı yapar, o yüzden derece toplamı = 2E. V köşeli bir grafta en fazla kenar olur (yönlüde ). E bu sınıra yakınsa graf yoğun (dense), V mertebesindeyse seyrek (sparse).
Örnek graf G
Bu sayfanın bütün gezinme örnekleri şu grafta:
1 --- 3 --- 0
| | |
4 --- 6 --- 5 --- 2 --- 7
Kenarlar: 0-3, 0-5, 1-3, 1-4, 2-5, 2-7, 3-6, 4-6, 5-6. Toplam 9 kenar, derece toplamı 18.
Gösterim 1: komşuluk matrisi (adjacency matrix)
V×V boyutunda bir boolean ya da int dizi: a[u][v] = 1 ise kenar var. Yönsüz grafta matris simetriktir. Küçük bir örnek, kenarlar 0-1, 0-2, 1-2, 2-3:
0 1 2 3
0 [ 0, 1, 1, 0 ]
1 [ 1, 0, 1, 0 ]
2 [ 1, 1, 0, 1 ]
3 [ 0, 0, 1, 0 ]
Ağırlıklı grafta 1 yerine ağırlık yazılır, kenar yoksa sonsuz (ya da 0) konur. Avantajı: “u ile v bağlı mı?” sorusu . Dezavantajı: kenar sayısından bağımsız olarak bellek. 1 milyon köşeli bir yol ağı için hücre olurdu.
Gösterim 2: komşuluk listesi (adjacency list)
Her köşe için komşularının listesi. Java’da en pratik hali List<List<Integer>>:
int n = 8;
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
int[][] edges = {{0,3},{0,5},{3,1},{3,6},{5,6},{5,2},{1,4},{6,4},{2,7}};
for (int[] e : edges) {
adj.get(e[0]).add(e[1]); // yönsüz: iki yöne de ekle
adj.get(e[1]).add(e[0]);
}
for (List<Integer> l : adj) Collections.sort(l);
System.out.println(adj);
[[3, 5], [3, 4], [5, 7], [0, 1, 6], [1, 6], [0, 2, 6], [3, 4, 5], [2]]
Listelerin toplam uzunluğu yönsüzde 2E, yönlüde E. Bellek .
| İşlem | Matris | Liste |
|---|---|---|
| Bellek | ||
| (u, v) kenarı var mı | ||
| u’nun bütün komşuları | ||
| BFS / DFS |
Gerçek graflar neredeyse hep seyrek olduğu için varsayılan seçim listedir.
Genişlik öncelikli arama (BFS)
BFS (breadth-first search) başlangıçtan kat kat yayılır: önce 1 kenar uzaklıktakiler, sonra 2, sonra 3. Aracı kuyruktur.
static void bfs(List<List<Integer>> adj, int s) {
int[] dist = new int[adj.size()];
Arrays.fill(dist, -1); // -1 = görülmedi
Deque<Integer> q = new ArrayDeque<>();
dist[s] = 0;
q.add(s);
while (!q.isEmpty()) {
int v = q.poll();
System.out.print(v + " ");
for (int w : adj.get(v))
if (dist[w] == -1) { // ilk kez görülüyor
dist[w] = dist[v] + 1;
q.add(w);
}
}
}
G üzerinde 0’dan iz:
çıkan eklenen kuyruk
0 3, 5 [3, 5]
3 1, 6 [5, 1, 6]
5 2 [1, 6, 2]
1 4 [6, 2, 4]
6 - [2, 4]
2 7 [4, 7]
4 - [7]
7 - []
sıra: 0 3 5 1 6 2 4 7
dist: 0:0 1:2 2:2 3:1 4:3 5:1 6:2 7:3
BFS’in süper gücü: ağırlıksız grafta dist dizisi tam olarak en kısa yolun kenar sayısıdır. Bir köşe kuyruğa ilk girdiğinde en kısa mesafesiyle girer, çünkü kuyruk mesafeleri sırayla işler. parent dizisi tutarsan yolun kendisini de geri çıkarabilirsin.
TUZAK · işaretlemeyi ne zaman yaparsın? Köşeyi kuyruğa eklerken görüldü olarak işaretle. Çıkarırken işaretlersen aynı köşe kuyruğa birden fazla kez girer: 6, hem 3’ten hem 5’ten eklenirdi. Sonuç sırası ve karmaşıklık bozulur.
Derinlik öncelikli arama (DFS)
DFS (depth-first search) bir yolda gidebildiği kadar gider, çıkmaza girince geri döner (backtracking). Özyinelemeli yazımı ağacın preorder gezinmesine çok benzer; tek fark visited dizisi, çünkü grafta döngü var.
static void dfs(List<List<Integer>> adj, int v, boolean[] visited) {
visited[v] = true;
System.out.print(v + " ");
for (int w : adj.get(v))
if (!visited[w]) dfs(adj, w, visited);
}
G üzerinde 0’dan:
dfs(0) -> 3
dfs(3) -> 0 görüldü, 1
dfs(1) -> 3 görüldü, 4
dfs(4) -> 1 görüldü, 6
dfs(6) -> 3, 4 görüldü, 5
dfs(5) -> 0 görüldü, 2
dfs(2) -> 5 görüldü, 7
dfs(7) -> geri dön
sıra: 0 3 1 4 6 5 2 7
Yığın ile DFS
Özyineleme yerine açık bir yığın (stack) da kullanılabilir: köşeyi çıkar, görülmüşse atla, değilse ziyaret et ve görülmemiş komşularını it. Burada sıra değişir:
komşuları ARTAN sırada it: 0 5 6 4 1 3 2 7
komşuları AZALAN sırada it: 0 3 1 4 6 5 2 7
Yığın son giren ilk çıkar mantığıyla çalıştığı için artan iterse en büyük komşu önce çıkar. Özyinelemeli DFS ile aynı sırayı istiyorsan komşuları ters sırada it. Sınavda “using a stack” yazıyorsa bu farkı mutlaka hesaba kat.
BFS ve DFS karşılaştırması
| BFS | DFS | |
|---|---|---|
| Yapı | kuyruk | yığın / özyineleme |
| Gezinme | kat kat | derine, sonra geri |
| Ağırlıksız en kısa yol | evet | hayır |
| Tipik kullanım | en kısa yol, seviyeler | döngü bulma, topolojik sıra, bileşenler |
| Süre (liste) |
Bağlı bileşenler (connected components)
Graf bağlı değilse tek bir BFS/DFS her köşeye ulaşamaz. Çözüm: köşeleri sırayla dolaş, görülmemiş olan her köşeden yeni bir gezinme başlat. Her başlatma bir bileşen demektir.
Köşeler 0..8, kenarlar 0-1, 1-2, 3-4, 5-6, 6-7, 5-7:
0 - 1 - 2 3 - 4 5 - 6 8
\ /
7
bileşen etiketleri: [0,0,0,1,1,2,2,2,3]
bileşen sayısı: 4
Hiç kenarı olmayan 8 de tek başına bir bileşendir. Kodun tamamı soru bankasında.
Topolojik sıralama (topological sort)
Yönlü bir grafta her u→v kenarı için u’nun v’den önce geldiği bir köşe sırası. Klasik örnek ders önkoşulları: SEN2211 → SEN2212 ise önce 2211 alınır. Sadece DAG’da mümkündür; döngü varsa (A, B’nin önkoşulu ve B, A’nın) geçerli bir sıra yoktur.
Örnek DAG, kenarlar 4→2, 4→0, 6→0, 6→5, 2→1, 0→1, 0→3, 5→3:
4 ---> 2 ---> 1
\ ^
v |
0 ---------+
^ \
/ v
6 3
\ ^
v |
5 -+
Kahn algoritması (giriş derecesi ile)
- Her köşenin giriş derecesini (in-degree) hesapla.
- Derecesi 0 olanları kuyruğa koy.
- Kuyruktan çıkar, çıktıya yaz. Komşularının derecesini 1 azalt; 0’a düşeni kuyruğa ekle.
in-degree: 0:2 1:2 2:1 3:2 4:0 5:1 6:0
kuyruk [4, 6]
çık 4 -> 2 serbest [6, 2]
çık 6 -> 0, 5 serbest [2, 0, 5]
çık 2 -> (1 hâlâ 1) [0, 5]
çık 0 -> 1 serbest [5, 1]
çık 5 -> 3 serbest [1, 3]
çık 1, çık 3
sıra: 4 6 2 0 5 1 3
Kuyruk yerine min-heap kullanıp her seferinde en küçük etiketi seçersen sıra 4 2 6 0 1 5 3 olur. İkisi de geçerli: topolojik sıra genelde tek değildir, sorudaki kural belirler.
Döngü tespiti bedava gelir: Kahn bittiğinde çıktıda V’den az köşe varsa grafta döngü vardır, çünkü döngüdeki köşelerin derecesi hiç 0’a düşmez. Kenarları 0→1, 1→2, 2→0, 2→3 olan grafta hiçbir köşenin giriş derecesi 0 değil; çıktı 0 köşe.
DFS ile topolojik sıra
DFS’te bir köşe bütün torunları bittikten sonra biter. O yüzden u→v kenarında v, u’dan önce biter. Bitiş sırasını ters çevirince topolojik sıra çıkar.
dfs(0): 1 biter, 3 biter, 0 biter
dfs(2): 2 biter dfs(4): 4 biter
dfs(5): 5 biter dfs(6): 6 biter
bitiş: 1 3 0 2 4 5 6
ters: 6 5 4 2 0 3 1
Kontrol: 6→0 (6 önce), 4→2, 2→1, 0→3, 5→3: hepsi doğru yönde. İki algoritma da .
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Yönsüz kenarı tek listeye yaz | İki listeye: adj[u].add(v) ve adj[v].add(u) |
| BFS’te köşeyi çıkarırken işaretle | Eklerken işaretle; yoksa tekrar eklenir |
| Yığınlı DFS özyinelemeli ile aynı sırayı verir | Artan iterse en büyük komşu önce çıkar |
| DFS en kısa yolu bulur | Ağırlıksız en kısa yol BFS ile |
| Tek BFS bütün grafı gezer | Sadece başlangıcın bileşenini; bileşen başına bir başlatma |
| Topolojik sıra = DFS bitiş sırası | Bitiş sırasının tersi |
| Topolojik sıra tektir | Genelde çok sayıda geçerli sıra var |
| Her yönlü graf topolojik sıralanır | Sadece DAG; Kahn çıktısı eksikse döngü var |
| Matrisle BFS de | ; her satır baştan sona taranır |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
An undirected graph has 9 edges. What is the sum of the degrees of all vertices? How many edges does a complete undirected graph on 10 vertices have?
Cevabı göster
Her kenar iki uç noktasının derecesini birer artırır: derece toplamı = 2E = 18 (handshake lemma). Tam grafta her çift bağlı: . Yönlü olsaydı 90.
For graph G (edges 0-3, 0-5, 1-3, 1-4, 2-5, 2-7, 3-6, 4-6, 5-6), give the adjacency list of vertex 6 and of vertex 3 (ascending order).
Cevabı göster
Yönsüz grafta her kenar iki listeye yazılır. 6'yı içeren kenarlar 3-6, 4-6, 5-6 → [3, 4, 5]. 3'ü içerenler 0-3, 1-3, 3-6 → [0, 1, 6]. Derece(6) = derece(3) = 3.
Run BFS on graph G starting at vertex 0 (neighbors visited in ascending order). Give the visit order.
Cevabı göster
Kuyruk: [0]. 0 çıkar, komşuları 3, 5 eklenir → [3, 5].
3 çıkar: 0 görülmüş, 1 ve 6 eklenir → [5, 1, 6].
5 çıkar: 0, 6 görülmüş, 2 eklenir → [1, 6, 2].
1 çıkar: 4 eklenir → [6, 2, 4]. 6 çıkar: hepsi görülmüş. 2 çıkar: 7 eklenir. 4, 7 çıkar.
Sıra: 0 3 5 1 6 2 4 7.
Using the BFS from vertex 0 on graph G, give the shortest-path distance (number of edges) from 0 to every vertex 0..7.
Cevabı göster
BFS bir köşeyi ilk kez gördüğünde dist[w] = dist[v] + 1 yazar. 3 ve 5 0'dan doğrudan: 1. 1, 6 (3 üzerinden) ve 2 (5 üzerinden): 2. 4 (1 üzerinden) ve 7 (2 üzerinden): 3. Ağırlıksız grafta BFS en kısa yolu verir.
Run recursive DFS on graph G starting at vertex 0 (neighbors in ascending order). Give the visit order.
Cevabı göster
0 → ilk görülmemiş komşu 3 → 3'ün ilk görülmemiş komşusu 1 → 1'in komşusu 4 → 4'ün komşusu 6 (1 görülmüş) → 6'nın komşuları 3 (görülmüş), 4 (görülmüş), 5 → 5'in komşuları 0, 2 → 2 → 7.
7'den geri dönüş başlar, kalan komşuların hepsi görülmüş. Sıra: 0 3 1 4 6 5 2 7.
DFS on graph G is now implemented with an explicit stack: pop a vertex, skip it if visited, otherwise visit it and push all unvisited neighbors in ascending order. Starting at 0, what is the visit order?
Cevabı göster
Yığın LIFO olduğu için en son itilen (en büyük) komşu önce çıkar.
[0] → ziyaret 0, it 3, 5 → [3, 5]
pop 5 → ziyaret, it 2, 6 → [3, 2, 6]
pop 6 → ziyaret, it 3, 4 → [3, 2, 3, 4]
pop 4 → ziyaret, it 1 → [3, 2, 3, 1]
pop 1 → ziyaret, it 3 → [3, 2, 3, 3]
pop 3 → ziyaret; sonraki iki 3 atlanır → [2]
pop 2 → ziyaret, it 7 → pop 7.
Özyinelemeli sırayı (0 3 1 4 6 5 2 7) istersen komşuları azalan sırada itmelisin.
Run BFS on graph G starting at vertex 4. Give the visit order.
Cevabı göster
4'ün komşuları 1, 6 → [1, 6]. 1 çıkar: 3 → [6, 3]. 6 çıkar: 5 (3 zaten kuyrukta) → [3, 5]. 3 çıkar: 0 → [5, 0]. 5 çıkar: 2 → [0, 2]. 0 çıkar: yeni yok. 2 çıkar: 7. Sıra 4 1 6 3 5 0 2 7.
Compare an adjacency matrix and adjacency lists for a graph with V vertices and E edges: memory, checking whether edge (u, v) exists, and iterating over all neighbors of u.
Cevabı göster
Matris her çift için bir hücre ayırır: bellek, kenar kontrolü tek indeks, ama komşuları bulmak için bütün satır taranır. Liste sadece var olan kenarları tutar: . Seyrek graflarda (E, 'den çok küçük) liste seçilir; BFS/DFS listeyle , matrisle sürer.
An undirected graph has vertices 0..8 and edges 0-1, 1-2, 3-4, 5-6, 6-7, 5-7. How many connected components does it have?
Cevabı göster
Bileşenler: {0, 1, 2}, {3, 4}, {5, 6, 7} ve hiç kenarı olmayan {8}. Tek başına köşe de bir bileşendir. Algoritma: her görülmemiş köşeden BFS/DFS başlat, her başlatma bir bileşen.
A DAG has edges 4→2, 4→0, 6→0, 6→5, 2→1, 0→1, 0→3, 5→3. Run Kahn's algorithm with a FIFO queue (initial zero in-degree vertices enqueued in ascending order; newly freed vertices enqueued in adjacency order). Give the topological order.
Cevabı göster
Giriş dereceleri: 0:2, 1:2, 2:1, 3:2, 4:0, 5:1, 6:0. Kuyruk [4, 6].
4 çıkar: 2'nin derecesi 0 → ekle; 0'ın derecesi 1 → [6, 2]
6 çıkar: 0 → 0, 5 → 0 → [2, 0, 5]
2 çıkar: 1'in derecesi 1 → [0, 5]
0 çıkar: 1 → 0, 3 → 1 → [5, 1]
5 çıkar: 3 → 0 → [1, 3]. Sonra 1, 3.
Kuyruk yerine min-heap kullansaydın (her seferinde en küçük etiket) sıra 4 2 6 0 1 5 3 olurdu.
For the same DAG, run DFS from vertices 0, 1, ..., 6 in order (skipping visited ones, neighbors in ascending order). Give the finishing order and the topological order it produces.
Cevabı göster
DFS(0): 1'e git, 1'in çıkışı yok → bitiş 1. 3'e git → bitiş 3. 0 biter.
DFS(2): 1 görülmüş → bitiş 2. DFS(4): 0 ve 2 görülmüş → bitiş 4. DFS(5): 3 görülmüş → bitiş 5. DFS(6) → bitiş 6.
Bitiş sırası 1 3 0 2 4 5 6. Topolojik sıra bunun TERSİ: 6 5 4 2 0 3 1. Her kenar u→v için u, v'den önce geliyor; kontrol et.
Write a method int countComponents(List<List<Integer>> adj) that returns the number of connected components of an undirected graph given as adjacency lists.
Cevabı göster
Her yeni başlatma yeni bir bileşen demektir.
static int countComponents(List<List<Integer>> adj) {
int n = adj.size(), count = 0;
boolean[] visited = new boolean[n];
for (int v = 0; v < n; v++) {
if (!visited[v]) {
count++;
dfs(adj, v, visited);
}
}
return count;
}static void dfs(List<List<Integer>> adj, int v, boolean[] visited) {
visited[v] = true;
for (int w : adj.get(v))
if (!visited[w]) dfs(adj, w, visited);
}Her köşe bir kez işaretlenir, her liste bir kez taranır: .
Doğrulama: Komşuluk listeleri, BFS/DFS sıraları (özyinelemeli ve yığınlı), BFS mesafeleri, bileşen sayısı, Kahn (kuyruk ve min-PQ) ve DFS bitiş sırası topolojik sıraları Java ile çalıştırıldı (verify/SEN2212/W10.java, java W10.java).