SEN2212 · 10. hafta · Analyzing and implementing graph structure using Java.

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ı.

graphvertexedgedirected graphundirected graphdegreeadjacency matrixadjacency listbreadth-first searchdepth-first searchconnected componentDAGtopological sortin-degreeKahn's algorithm

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:

  1. Gösterim: “Bu grafın komşuluk listesini/matrisini yaz”, “bellek ne kadar?”
  2. Gezinme sırası: “0’dan BFS/DFS yap, ziyaret sırasını yaz.” Kural genelde “komşuları artan sırada gez”.
  3. Topolojik sıralama: önkoşul grafında geçerli bir ders sırası.
  4. Metot yazma: BFS, DFS ya da bileşen sayma.

Terimler

TerimAnlamı
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
DAGyö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 V(V−1)/2V(V-1)/2 kenar olur (yönlüde V(V−1)V(V-1)). 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 O(1)O(1). Dezavantajı: kenar sayısından bağımsız olarak V2V^2 bellek. 1 milyon köşeli bir yol ağı için 101210^{12} 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 O(V+E)O(V + E).

İşlemMatrisListe
BellekO(V2)O(V^2)O(V+E)O(V + E)
(u, v) kenarı var mıO(1)O(1)O(deg⁡u)O(\deg u)
u’nun bütün komşularıO(V)O(V)O(deg⁡u)O(\deg u)
BFS / DFSO(V2)O(V^2)O(V+E)O(V + E)

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ı

BFSDFS
Yapıkuyrukyığın / özyineleme
Gezinmekat katderine, sonra geri
Ağırlıksız en kısa yolevethayır
Tipik kullanımen kısa yol, seviyelerdöngü bulma, topolojik sıra, bileşenler
Süre (liste)O(V+E)O(V + E)O(V+E)O(V + E)

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)

  1. Her köşenin giriş derecesini (in-degree) hesapla.
  2. Derecesi 0 olanları kuyruğa koy.
  3. 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 O(V+E)O(V + E).

Sık yapılan hatalar

Yanlış düşünceDoğ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şaretleEklerken işaretle; yoksa tekrar eklenir
Yığınlı DFS özyinelemeli ile aynı sırayı verirArtan iterse en büyük komşu önce çıkar
DFS en kısa yolu bulurAğırlıksız en kısa yol BFS ile
Tek BFS bütün grafı gezerSadece 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 tektirGenelde çok sayıda geçerli sıra var
Her yönlü graf topolojik sıralanırSadece DAG; Kahn çıktısı eksikse döngü var
Matrisle BFS de O(V+E)O(V + E)O(V2)O(V^2); her satır baştan sona taranır

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

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
18; 45

Her kenar iki uç noktasının derecesini birer artırır: derece toplamı = 2E = 18 (handshake lemma). Tam grafta her çift bağlı: (102)=10⋅9/2=45\binom{10}{2} = 10 \cdot 9 / 2 = 45. Yönlü olsaydı 90.

Soru 2kolay

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
6: [3, 4, 5]; 3: [0, 1, 6]

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.

Soru 3orta

Run BFS on graph G starting at vertex 0 (neighbors visited in ascending order). Give the visit order.

Cevabı göster
0 3 5 1 6 2 4 7

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.

Soru 4orta

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
[0, 2, 2, 1, 3, 1, 2, 3]

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.

Soru 5orta

Run recursive DFS on graph G starting at vertex 0 (neighbors in ascending order). Give the visit order.

Cevabı göster
0 3 1 4 6 5 2 7

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.

Soru 6zor

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
0 5 6 4 1 3 2 7

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.

Soru 7orta

Run BFS on graph G starting at vertex 4. Give the visit order.

Cevabı göster
4 1 6 3 5 0 2 7

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.

Soru 8kolay

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
Matrix: O(V²), O(1), O(V). Lists: O(V+E), O(deg u), O(deg u)

Matris her çift için bir hücre ayırır: V2V^2 bellek, kenar kontrolü tek indeks, ama komşuları bulmak için bütün satır taranır. Liste sadece var olan kenarları tutar: O(V+E)O(V+E). Seyrek graflarda (E, V2V^2'den çok küçük) liste seçilir; BFS/DFS listeyle O(V+E)O(V+E), matrisle O(V2)O(V^2) sürer.

Soru 9orta

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
4

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.

Soru 10orta

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
4 6 2 0 5 1 3

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.

Soru 11zor

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
finish 1 3 0 2 4 5 6; topo 6 5 4 2 0 3 1

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.

Soru 12orta

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
Start a DFS/BFS from every unvisited vertex; count the starts

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: O(V+E)O(V + E).

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).

Deftere not kartı

G = (V, E). Yönsüz: derece toplamı = 2E. Tam graf: V(V-1)/2 kenar (yönlü V(V-1)). Matris: V² bellek, kenar var mı O(1), komşular O(V). Yoğun graf için. Liste: V+E bellek, kenar var mı O(deg), komşular O(deg). Seyrek graf için (çoğu soru). Yönsüz kenar İKİ listeye yazılır. BFS = KUYRUK. Kat kat gezer, ağırlıksız en kısa yolu (kenar sayısı) verir. dist ilk görüldüğünde yazılır. DFS = özyineleme ya da YIĞIN. Bir yolda sonuna kadar gider, sonra geri döner. Yığınlı DFS'te artan sırayla itersen en büyük komşu önce çıkar; özyinelemeli sırayı istersen azalan it. İşaretlemeyi kuyruğa EKLERKEN yap (BFS), yoksa aynı köşe iki kez eklenir. BFS ve DFS listeyle O(V+E), matrisle O(V²). Bağlı bileşen: her görülmemiş köşeden yeni BFS/DFS, başlatma sayısı = bileşen sayısı. Topolojik sıra sadece DAG'da. Kahn: giriş derecesi 0 olanları kuyruğa, çıkarınca komşuların derecesini azalt. Kahn çıktısı V'den azsa graf döngülü. DFS ile topo: bitiş (postorder) sırasının TERSİ. Topolojik sıra genelde tek değildir; soru kuralı (kuyruk mu, en küçük etiket mi) belirler.