Bölüm 15: Çizgeler
Giriş
Öğrenme Çıktıları
G = (V, E), yönlü/yönsüz çizge, komşuluk, alt çizge, yol, derece ve bağlantılılık kavramlarını kullanmak.
Derece toplamı = 2e (Teorem 1) ve bağlantılılık için e ≥ n − 1 koşulunu gerekçelendirmek.
Komşuluk matrisi, komşuluk listesi, sıralı dizi, ortogonal liste ve çok bağlı listeyi C ile kurmak.
Önce derinliğine (DFS) ve önce genişliğine (BFS) aramayı kodlayıp izlemek, O(n + e) analizini yapmak.
Bağlantılı bileşenleri, döngüyü ve iki parçalılığı DFS/BFS ile bulmak.
Problemin işlemlerine bakarak uygun gösterimi seçmek: “önce veri yapısı, sonra kod”.
Giriş
Problem: Königsberg’in 7 Köprüsü
Pregel nehrinde iki ada (A, D) ve iki kıyı (B, C) 7 köprüyle bağlı. Her köprüden bir kez geçerek başlanan yere dönülebilir mi?
Euler (1736) kara parçalarını düğüm, köprüleri kenar yaptı: şekil değil, bağlantı önemlidir (notasyon/çizim farkı).
Böyle bir yürüyüş ancak her düğümün derecesi çiftse vardır. Burada tüm dereceler tek (5, 3, 3, 3) → yürüyüş yok.
Tanımlar ve Terminoloji
Tanım: G = (V, E)
G çizgesi boş olmayan iki sonlu kümeden oluşur: V düğümler (vertex, node), E kenarlar (edge, arc). Yönsüzde (v0, v1) ≡ (v1, v0); yönlüde (digraph) <v0, v1> ≠ <v1, v0> — <kuyruk (tail), baş (head)>: ok kuyruktan çıkar, başa saplanır.
| Çizge | V(G) | E(G) |
|---|---|---|
| G1 | {0, 1, 2, 3} | {(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)} |
| G2 | {0, …, 6} | {(0,1), (0,2), (1,3), (1,4), (2,5), (2,6)} |
| G3 | {0, 1, 2} | {<0,1>, <1,0>, <1,2>} — yönlüde ikisi de yazılır |
E, V × V kartezyen çarpımının bir alt kümesidir (tüm ikilileri barındırmaz).
Tanımlar ve Terminoloji
Çizge Veri Yapısı Nerelerde Kullanılır?
Elektrik devrelerinin analizi, proje planlama (aktivite ağları), kimyasal bileşenleri tanıma.
En kısa yol, yönlendirme (router), bilgisayar ağlarıyla ilgili birçok problem.
Ağaçlar birer çizgedir; topolojik sıralama, sonlu özdevinirler (finite automata).
Taşıma ağlarında akış; max flow: kaynaktan hedefe en çok petrol/su taşıma.
Minimum kapsayan ağaç: köyleri en az kabloyla bağlamak.
Bağlantılı çizge, bileşen sayısı, bir ağda kopukluk var mı?
Tanımlar ve Terminoloji
Terminoloji: Komşu Düğüm, Alt Çizge
(v0, v1) yönsüz çizgede bir kenarsa v0 ile v1 komşudur; kenar bu iki düğüme bitişiktir.
G′, G’nin alt çizgesi ise V(G′) ⊆ V(G) ve E(G′) ⊆ E(G) olur. Tek düğüm de bir alt çizgedir.
Tanımlar ve Terminoloji
Yol, Bağlantılılık ve Derece
- Yol (path): vp’den vq’ya (vp, vi1), (vi1, vi2), …, (vin, vq) kenar olacak biçimde bir düğüm dizisi.
- v0’dan v1’e yol varsa ikisi bağlantılıdır (connected); tüm farklı (vi, vj) çiftleri arasında yol varsa G bağlantılıdır.
- Derece: düğümü uç kabul eden kenar sayısı. G2’de d(0) = 2, G1’de d(1) = 3, G2’de d(4) = 1.
- Yönlüde derece = giriş (in-degree) + çıkış (out-degree). G3’te d(0) = 1 + 1 = 2; d(1) = 1 + 2 = 3; d(2) = 1 + 0 = 1.
G3’te düğüm 0: d = 1 + 1 = 2
Tanımlar ve Terminoloji
Teorem 1: Derecelerin Toplamı = 2e
n düğüm ve e kenarlı G çizgesinde 2e = d0 + d1 + … + dn−1. İspat: her kenar, iki ucundaki düğümlerin derece toplamına tam 2 katkı yapar. Basit olan ve olmayan (çoklu kenarlı) çizgelerde geçerlidir. Basit çizge: iki düğüm arasında en fazla bir kenar, öz-döngü (self-loop) yok.
1. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.
2. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.
3. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.
4. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.
5. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.
6. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.
Gerçeklemeler
Çizgeyi Bellekte Nasıl Gerçekleriz?
Gerçeklemeler
1. Komşuluk Matrisi (Adjacency Matrix)
n düğümlü G için n × n matris: (i, j) ∈ E ise a[i][j] = 1. Yönsüzde matris simetriktir; yer kazanmak için alt ya da üst yarı üçgen saklanabilir — yine de bellek O(n²).
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 0 | 1 | 1 | 1 |
| 1 | 1 | 0 | 1 | 1 |
| 2 | 1 | 1 | 0 | 1 |
| 3 | 1 | 1 | 1 | 0 |
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
| 2 | 0 | 0 | 0 |
Satırdaki (ya da sütundaki) 1’ler sayılır: O(n). Yönlüde satır → out-degree, sütun → in-degree; toplamı O(2n). G3’te out-degree d(2) = 0, in-degree d(2) = 1.
Tüm 1’ler sayılıp 2’ye bölünür: O(n²). Simetri yüzünden yarısına bakmak yeter; yine de O(n²/2) = O(n²).
Gerçeklemeler
G4’ün Komşuluk Matrisi — Simetri Denetimi
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
| 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 2 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 3 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
| 4 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
| 6 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
| 7 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
G4: iki bileşen
1 0 1 0 …, 7. satırı … 0 0 1 yazmak. Yönsüz çizgenin matrisi simetrik olmalıdır; bu satırlar simetriyi bozar. Doğrusu: 1. satır 1 0 0 1 0 0 0 0 (komşular 0, 3), 7. satır 0 0 0 0 0 0 1 0 (komşu 6).Bileşen sayısı matristen de bulunur: her DFS komşuları O(n)’de taradığı için toplam O(n²).
Komşuluk matrisi · Kodu adım adım çalıştır
Kod İzleme: derece(a, 3, 1)
derece(a, 3, 1): G3’te düğüm 1’in derecesi aranıyor.
Sayaçlar sıfırlanır.
Satır 1, sütun 0: a[1][0] = 1 → cikis = 1.
Satır 1, sütun 1: a[1][1] = 0 → cikis = 1.
Satır 1, sütun 2: a[1][2] = 1 → cikis = 2.
Sütun 1, satır 0: a[0][1] = 1 → giris = 1.
Sütun 1, satır 1: a[1][1] = 0 → giris = 1.
Sütun 1, satır 2: a[2][1] = 0 → giris = 1.
return 2 + 1 = 3. İki döngü de n kez döner → O(2n) = O(n).
int derece(int a[][MAX_DUGUM], int n, int v) { int j, cikis = 0, giris = 0; for (j = 0; j < n; j++) cikis += a[v][j]; /* satir */ for (j = 0; j < n; j++) giris += a[j][v]; /* sutun */ return cikis + giris; }
Gerçeklemeler
2. Komşuluk Listesi (Adjacency List)
Matrisin her satırı yerine bir bağlı liste: baslik[i], i’nin komşularının listesini gösterir. Dezavantaj: yönsüzde her kenar için 2 öğe kullanılır.
#define MAX_DUGUM 50 /* en fazla dugum sayisi */ typedef struct oge *oge_gost; typedef struct oge { int dugum; /* komsu dugumun numarasi */ oge_gost bag; /* listedeki sonraki komsu */ } oge; /* ';' ve typedef adi unutulmamali */ oge_gost baslik[MAX_DUGUM]; /* her dugume bir liste */
G1’in komşuluk listesiyle gösterimi
Tüm öğeler sayılıp 2’ye bölünür (Teorem 1): O(n + e). Çizge kuramında karmaşıklık genelde e’ye bağlı verilir.
Yönsüzde i. başlıktaki öğeleri saymak yeter: O(di).
Gerçeklemeler
Komşuluk Listesi: Yönsüz ve Yönlü Örnekler
Liste sıralıysa her başlıkta kuyruğu baştan küçük (ör. (1,0)) öğeleri sayıp kalanını atlayabiliriz; düğümlerin yarısında geziniriz ama yine O(n + e).
<0,1> ≠ <1,0> → tüm kenarlar sayılmalı. Out-degree listeden kolay; in-degree için tüm listelerde gezinilir. Çözüm: listenin tersi (transpozu) de tutulur; bir kez gezinerek kurulur.
Gerçeklemeler
Seyrek Bir Çizge: 11 Düğüm, 2 Kenar
Listeler: baslik[1] → 4, baslik[4] → 1, baslik[7] → 8, baslik[8] → 7. Diğer 7 başlık NULL.
Toplam: 11 başlık + 2e = 4 öğe. Matris: 11 × 11 = 121 hücre.
Komşuluk listesi · Kodu adım adım çalıştır
Kod İzleme: kenarları yapıya eklemek
Kenar (0,1) → önce kenar_ekle(0, 1): yeni öğe için bellek alınır (NULL kontrolü!).
p->dugum = 1, p->bag = NULL, baslik[0] = p: öğe listenin başına girdi.
Kenar (0,1) → sonra kenar_ekle(1, 0): yeni öğe için bellek alınır (NULL kontrolü!).
p->dugum = 0, p->bag = NULL, baslik[1] = p: öğe listenin başına girdi.
Kenar (0,2) → önce kenar_ekle(0, 2): yeni öğe için bellek alınır (NULL kontrolü!).
p->dugum = 2, p->bag = &oge(1), baslik[0] = p: öğe listenin başına girdi.
Kenar (0,2) → sonra kenar_ekle(2, 0): yeni öğe için bellek alınır (NULL kontrolü!).
p->dugum = 0, p->bag = NULL, baslik[2] = p: öğe listenin başına girdi.
Kenar (1,2) → önce kenar_ekle(1, 2): yeni öğe için bellek alınır (NULL kontrolü!).
p->dugum = 2, p->bag = &oge(0), baslik[1] = p: öğe listenin başına girdi.
Kenar (1,2) → sonra kenar_ekle(2, 1): yeni öğe için bellek alınır (NULL kontrolü!).
p->dugum = 1, p->bag = &oge(0), baslik[2] = p: öğe listenin başına girdi.
void kenar_ekle(int u, int v) /* u -> v */ { oge_gost p = malloc(sizeof(oge)); if (p == NULL) exit(1); /* bellek yok */ p->dugum = v; p->bag = baslik[u]; /* basa ekle: O(1) */ baslik[u] = p; }
Gerçeklemeler
Çizgeyi Kim Kuracak?
Her kenar O(1)’de yerleşir; liste gelen sıranın tersidir. Toplam kurulum O(n + e).
Sıra korunur ama her seferinde NULL’a kadar yürümek gerekir (O(d)); ya da her liste için bir son işaretçisi tutulur.
Listeyi düğüm numarasına göre sıralı tutmak: kenar sayma ve gezinme sırası öngörülebilir olur, ekleme O(d).
Program sonunda her baslik[i] listesinde gezinilerek free edilmelidir.
Ters komşuluk listesi · Kodu adım adım çalıştır
Kod İzleme: devrik_kur()
Önce ters[] başlıkları NULL yapılır.
<0,1> kenarı: ters[1] listesine 0 eklenir (<1,0>).
<1,0> kenarı: ters[0] listesine 1 eklenir (<0,1>).
<1,2> kenarı: ters[2] listesine 1 eklenir (<2,1>).
bas[2] boş: 2’den çıkan kenar yok.
Her öğeye bir kez bakıldı: transpoz O(n + e)’de kuruldu. Artık in-degree de O(d)’de bulunur.
void devrik_kur(oge_gost bas[], oge_gost ters[], int n) { int i; oge_gost p; for (i = 0; i < n; i++) ters[i] = NULL; for (i = 0; i < n; i++) for (p = bas[i]; p; p = p->bag) ekle(ters, p->dugum, i); /* <j,i> */ }
Gerçeklemeler
3. Dizi ile Sıralı (Sequential) Gösterim
Yönsüz, n düğüm e kenar: tek dizi int dugum[n + 2*e + 1]. İlk n + 1 hücre başlangıç indisleridir; i’nin komşuları dugum[dugum[i]] … dugum[dugum[i+1] − 1] aralığındadır.
G4 için dizi. Her kenar yine 2 kez yazılır ama işaretçi yok.
Ör. 1: [0] = 9’dan (dahil) [1] = 11’e (hariç): indis 9 ve 10 → komşular 1, 2.
Ör. 2: [1] = 11, [2] = 13 → indis 11, 12 → komşular 0, 3.
Ör. 3: [7] = 22, [8] = 23 → yalnızca indis 22 → tek komşu 6.
Sıralı gösterim · Kodu adım adım çalıştır
Kod İzleme: komsulari_yaz(dugum, 5)
komsulari_yaz(dugum, 5): 5 numaralı düğümün komşuları.
k = 18 < dugum[6] = 20 → dugum[18] = 4 yazılır.
k = 19 < dugum[6] = 20 → dugum[19] = 6 yazılır.
k = 20 = dugum[6] → döngü biter. Komşu sayısı = dugum[6] − dugum[5] = derece: O(d).
void komsulari_yaz(const int dugum[], int i) { int k; for (k = dugum[i]; k < dugum[i + 1]; k++) printf("%d ", dugum[k]); printf("\n"); }
Gerçeklemeler
4. Yönlü Çizgelerin Ortogonal Gösterimi
G3: her kenar öğesi aynı anda bir satır (çıkış) ve bir sütun (giriş) listesinde
Baş için kolon bağı: bu düğüme nereden gelinir. Kuyruk için satır bağı: bu düğümden nereye çıkılır. Aynı kenar için tek öğe yeter.
Bağlı seyrek matris yapısına benzer; döngülü, çift yönlü bağlı, başlıklı yapılabilir. “2e adet öğe almak rahatsız etti, böylece bu örneği bulduk.”
Gerçeklemeler
5. Çok Bağlı Liste (Adjacency Multilist)
typedef struct kenar *kenar_gost; typedef struct kenar { short isaret; /* islendi mi */ int dugum1, dugum2; /* uclar */ kenar_gost list1; /* dugum1 sonraki */ kenar_gost list2; /* dugum2 sonraki */ } kenar; kenar_gost baslik[MAX_DUGUM];
Kenar temel öğedir: (vi, vj) için tek öğe vardır ama hem vi’nin hem vj’nin listesinde yer alır. isaret, işlenmiş kenarın tekrar ele alınmasını önler. Düğüme veri taşımak gerekirse öğeye char veri gibi bir alan eklenebilir.
| Öğe | dugum1 | dugum2 | list1 | list2 |
|---|---|---|---|---|
| N0 | 0 | 1 | N1 | N3 |
| N1 | 0 | 2 | N2 | N3 |
| N2 | 0 | 3 | NULL | N4 |
| N3 | 1 | 2 | N4 | N5 |
| N4 | 1 | 3 | NULL | N5 |
| N5 | 2 | 3 | NULL | NULL |
G1 için başlıklar: baslik[0] = N0, baslik[1] = N0, baslik[2] = N1, baslik[3] = N2. Hiç kenarı olmayan düğümün başlığı NULL’dır.
Çok bağlı liste · Kodu adım adım çalıştır
Kod İzleme: 2’nin komşuları üzerinde gezinmek
p = baslik[2] = N1: 2’nin ilk kenarı.
N1 = (0,2): v, dugum2’de → öteki uç 0 yazılır.
Bağ izlenir: p = p->list2 = N3.
N3 = (1,2): v, dugum2’de → öteki uç 1 yazılır.
Bağ izlenir: p = p->list2 = N5.
N5 = (2,3): v, dugum1’de → öteki uç 3 yazılır.
Bağ izlenir: p = p->list1 = NULL.
NULL: aranan özellikte öğe kalmadı. 2’nin komşuları 0, 1, 3 — her kenar yalnız bir kez saklandı.
void komsulari_yaz(int v) { kenar_gost p = baslik[v]; while (p) { if (p->dugum1 == v) { /* v: dugum1 */ printf("%d ", p->dugum2); p = p->list1; } else { /* v: dugum2 */ printf("%d ", p->dugum1); p = p->list2; } } }
Gerçeklemeler
Çok Bağlı Listeye Kenar Eklemek
- Gezinme: dugum1’de v varsa list1, dugum2’de v varsa list2 izlenir. NULL görünce biter. “Başka var mı? Bağı takip et!”
- Eklemek: ilgili listede NULL’a kadar ilerleyip sona eklemek mümkün; ama başa eklemek sona kadar gezinme maliyetinden kurtarır: O(1).
- Yönsüzde bir baş dizisi yeter; yönlüde baş dizisiyle birlikte bir kuyruk dizisi de tutulur.
- Kazanç: listede 2e öğe yerine yalnızca e öğe.
void ml_ekle(int u, int v) { kenar_gost p = malloc(sizeof(kenar)); if (p == NULL) exit(1); p->isaret = 0; p->dugum1 = u; p->dugum2 = v; p->list1 = baslik[u]; /* u listesine */ p->list2 = baslik[v]; /* v listesine */ baslik[u] = baslik[v] = p; /* ikisine de basa */ }
Gerçeklemeler
Alıştırma: <0,1>, <0,3>, <1,2>, <2,0>, <3,2>
Kenar listesi verilen yönlü çizge için komşuluk matrisini, komşuluk listesini (ve tersini) ve çok bağlı listeyi veriniz.
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 2 | 1 | 0 | 0 | 0 |
| 3 | 0 | 0 | 1 | 0 |
Matris: <i,j> varsa a[i][j] = 1. Satır toplamları out-degree (2, 1, 1, 1), sütun toplamları in-degree (1, 1, 2, 1).
çıkan (komşuluk listesi)
gelen (listenin transpozu)
Liste ve tersi: toplam 2e = 10 öğe.
| Öğe | kuyruk | baş | kuyruk bağı | baş bağı |
|---|---|---|---|---|
| N1 | 0 | 1 | N2 | NULL |
| N2 | 0 | 3 | NULL | NULL |
| N3 | 1 | 2 | NULL | N5 |
| N4 | 2 | 0 | NULL | NULL |
| N5 | 3 | 2 | NULL | NULL |
| Düğüm | kuyruk (çıkış) | baş (giriş) |
|---|---|---|
| 0 | N1 | N4 |
| 1 | N3 | N1 |
| 2 | N4 | N3 |
| 3 | N5 | N2 |
Çok bağlı: yalnızca e = 5 öğe (N1…N5). Her öğe, kuyruğunun çıkış listesinde ve başının giriş listesinde yer alır.
Gerçeklemeler
Çıkarım: Önce Verinin Nasıl Depolanacağı Düşünülür
| Gösterim | Bellek | (u,v) var mı? | u’nun komşuları | Ekleme/silme |
|---|---|---|---|---|
| Komşuluk matrisi | O(n²) | O(1) | O(n) | O(1) |
| Komşuluk listesi | O(n + 2e) | O(du) | O(du) | başa O(1) / arama O(d) |
| Sıralı dizi | n + 2e + 1 | O(du) | O(du) | pahalı (kaydırma) |
| Ortogonal (yönlü) | O(n + e) | O(d) | giriş ve çıkış O(d) | O(1) başa |
| Çok bağlı | O(n + e) | O(du) | O(du) | O(1) başa |
Gerçeklemeler
Ağırlıklı Çizge Gösterimi
ağırlık = mesafe, maliyet, süre…
| A | B | C | D | |
|---|---|---|---|---|
| A | 0 | 4 | 2 | ∞ |
| B | 4 | 0 | 1 | 5 |
| C | 2 | 1 | 0 | 8 |
| D | ∞ | 5 | 8 | 0 |
#define SONSUZ 1000000 /* INT_MAX degil: toplamda tasar */ int maliyet[MAX_DUGUM][MAX_DUGUM]; typedef struct aoge *aoge_gost; typedef struct aoge { int dugum; int agirlik; /* kenarin maliyeti */ aoge_gost bag; } aoge; aoge_gost abaslik[MAX_DUGUM];
- Matriste 1 yerine ağırlık, kenar yoksa ∞.
- Listede öğeye
agirlikalanı eklenir. - ∞ için
INT_MAXkullanmakd + wtoplamında taşmaya yol açar; güvenli büyük sabit seçilir. - MST ve en kısa yol algoritmaları bu gösterimi kullanır.
Gezinmeler
Çizge Üzerinde Temel İşlemler: Gezinme
- Yönsüz G = (V, E) ve bir v düğümü verildiğinde, v’den ulaşılabilen tüm düğümlere uğramak (ziyaret etmek = veri alanını yazdırmak).
- Önce derinliğine arama (depth first search, DFS) ağaçtaki kök önde gezinmeye benzer.
- Önce genişliğine arama (breadth first search, BFS) ağaçtaki düzey düzey gezinmeye benzer.
- Her ikisinde çizge komşuluk listesiyle gerçeklenmiştir; listeler sıralıdır.
Örnek çizge
baslik[] komşuluk listesi
Gezinmeler
Önce Derinliğine Arama (DFS)
- v ziyaret edilir; v’nin listesinden ziyaret edilmemiş bir w seçilir ve arama w üzerinde sürer.
- Listedeki konumu saklamak için yığın gerekir; u’da gidecek yer kalmayınca yığından bir düğüm alınıp devam edilir.
- Karmaşık görünen çözümleme özyinelemeli yordamla kolaylaşır: yığın, çağrı yığınıdır.
- Global
ugranildi[]dizisi başta FALSE olmalıdır.
#include <stdio.h> #include <stdbool.h> #define TRUE true #define FALSE false bool ugranildi[MAX_DUGUM]; /* ';' unutulmamali */
void dfs(int v) /* once derinligine arama */ { oge_gost w; ugranildi[v] = TRUE; /* ugradim */ printf("%d ", v); for (w = baslik[v]; w; w = w->bag) if (!ugranildi[w->dugum]) dfs(w->dugum); }
Önce derinliğine arama · Kodu adım adım çalıştır
Kod İzleme: dfs(0)
dfs(0) çağrıldı: 0 işaretlenir ve yazılır.
0 → 1: 1 ziyaret edilmemiş → dfs(1). 1 işaretlenir, yazılır.
w → 0: ugranildi[0] TRUE, atlanır.
1 → 3: 3 ziyaret edilmemiş → dfs(3). 3 işaretlenir, yazılır.
w → 1: ugranildi[1] TRUE, atlanır.
3 → 7: 7 ziyaret edilmemiş → dfs(7). 7 işaretlenir, yazılır.
w → 3: ugranildi[3] TRUE, atlanır.
7 → 4: 4 ziyaret edilmemiş → dfs(4). 4 işaretlenir, yazılır.
w → 1: ugranildi[1] TRUE, atlanır.
w → 7: ugranildi[7] TRUE, atlanır.
(dfs(4) bitti.) 7 → 5: 5 ziyaret edilmemiş → dfs(5). 5 işaretlenir, yazılır.
5 → 2: 2 ziyaret edilmemiş → dfs(2). 2 işaretlenir, yazılır.
w → 0: ugranildi[0] TRUE, atlanır.
w → 5: ugranildi[5] TRUE, atlanır.
void dfs(int v) /* once derinligine arama */ { oge_gost w; ugranildi[v] = TRUE; /* ugradim */ printf("%d ", v); for (w = baslik[v]; w; w = w->bag) if (!ugranildi[w->dugum]) dfs(w->dugum); }
Önce derinliğine arama · Kodu adım adım çalıştır
Kod İzleme: dfs(0) (devam)
2 → 6: 6 ziyaret edilmemiş → dfs(6). 6 işaretlenir, yazılır.
w → 2: ugranildi[2] TRUE, atlanır.
w → 7: ugranildi[7] TRUE, atlanır.
dfs(6), dfs(2) bitti; 5’te kalınan yerden devam. w → 7: ugranildi[7] TRUE, atlanır.
dfs(5) bitti; 7’de kalınan yerden devam. w → 6: ugranildi[6] TRUE, atlanır.
dfs(7), dfs(3) bitti; 1’de kalınan yerden devam. w → 4: ugranildi[4] TRUE, atlanır.
dfs(1) bitti; 0’da kalınan yerden devam. w → 2: ugranildi[2] TRUE, atlanır.
dfs(0) bitti. Gezinme sırası 0 1 3 7 4 5 2 6; turuncu kenarlar bir ağaç oluşturdu.
void dfs(int v) /* once derinligine arama */ { oge_gost w; ugranildi[v] = TRUE; /* ugradim */ printf("%d ", v); for (w = baslik[v]; w; w = w->bag) if (!ugranildi[w->dugum]) dfs(w->dugum); }
Gezinmeler
DFS’nin Sonucu Neye Bağlı?
0’dan: 0 1 3 7 4 5 2 6. Başlangıç 0 olmak zorunda değil: 6’dan 6 2 0 1 3 7 4 5; 4’ten 4 1 0 2 5 7 3 6. Aynı öğeler, farklı diziliş.
0’ın listesi {1, 2} değil de {2, 1} olsaydı sıra: 0 2 5 7 3 1 4 6. Liste başa mı sona mı ekleyerek kuruldu, sıralı mı — bunlar gezinmeyi değiştirir.
dfs her düğüm için en fazla 1 kez çağrılır; her liste bir kez taranır: O(n + e). Kenar sayısı en fazla n(n − 1)/2.
Matrisle gösterilseydi v’nin komşularını bulmak O(n), toplam O(n²) olurdu. Listeyle doğrusal zamanda geziniyoruz.
Gezinmeler
Önce Genişliğine Arama (BFS)
- v’den başlanır, v işaretlenir; sonra v’nin listesindeki düğümlerin hepsi ziyaret edilir.
- Sonra v’nin listesindeki ilk düğümün ziyaret edilmemiş komşuları, sonra ikincisininki…
- Ziyaret edilen düğüm kuyruğa atılır (ilk giren ilk çıkar); liste bitince kuyruktan bir düğüm alınır ve onun listesinde ilerlenir.
- Kuyruk boşalınca arama biter.
void bfs(int v) /* once genisligine arama */ { oge_gost w; kuyruk_gost on = NULL, arka = NULL; printf("%d ", v); ugranildi[v] = TRUE; kuyruk_ekle(&on, &arka, v); while (on) { v = kuyruk_al(&on, &arka); for (w = baslik[v]; w; w = w->bag) if (!ugranildi[w->dugum]) { printf("%d ", w->dugum); kuyruk_ekle(&on, &arka, w->dugum); ugranildi[w->dugum] = TRUE; } } }
Gezinmeler
BFS’nin Kullandığı Dinamik Kuyruk
typedef struct kuyruk *kuyruk_gost; typedef struct kuyruk { int dugum; kuyruk_gost bag; } kuyruk; void kuyruk_ekle(kuyruk_gost *on, kuyruk_gost *arka, int d) { kuyruk_gost p = malloc(sizeof(kuyruk)); if (p == NULL) exit(1); p->dugum = d; p->bag = NULL; if (*on) (*arka)->bag = p; else *on = p; *arka = p; }
int kuyruk_al(kuyruk_gost *on, kuyruk_gost *arka) { kuyruk_gost p = *on; /* on != NULL */ int d = p->dugum; *on = p->bag; if (*on == NULL) *arka = NULL; free(p); return d; }
int kuyruk_al(kuyruk_gost *) gibi yalnız ön işaretçiyi alan bir prototip yetmez. Kuyruk boşalınca arka da NULL yapılmalı; bu yüzden kuyruk_al’a arka da geçirilir.Önce genişliğine arama · Kodu adım adım çalıştır
Kod İzleme: bfs(0)
0 yazılır, işaretlenir ve kuyruğa eklenir.
Kuyruk boş değil: öndeki 0 alınır; şimdi 0’ın listesi taranacak.
w → 1: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir.
w → 2: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir.
Kuyruk boş değil: öndeki 1 alınır; şimdi 1’in listesi taranacak.
w → 3: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir. (0 zaten işaretliydi.)
w → 4: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir.
Kuyruk boş değil: öndeki 2 alınır; şimdi 2’nin listesi taranacak.
w → 5: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir. (0 zaten işaretliydi.)
w → 6: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir.
Kuyruk boş değil: öndeki 3 alınır; şimdi 3’ün listesi taranacak.
w → 7: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir. (1 zaten işaretliydi.)
on = arka = NULL; printf("%d ", v); ugranildi[v] = TRUE; kuyruk_ekle(&on, &arka, v); while (on) { v = kuyruk_al(&on, &arka); for (w = baslik[v]; w; w = w->bag) if (!ugranildi[w->dugum]) { printf("%d ", w->dugum); kuyruk_ekle(&on, &arka, w->dugum); ugranildi[w->dugum] = TRUE; } }
Önce genişliğine arama · Kodu adım adım çalıştır
Kod İzleme: bfs(0) (devam)
Kuyruk boş değil: öndeki 4 alınır; şimdi 4’ün listesi taranacak.
Kuyruk boş değil: öndeki 5 alınır; şimdi 5’in listesi taranacak.
Kuyruk boş değil: öndeki 6 alınır; şimdi 6’nın listesi taranacak.
Kuyruk boş değil: öndeki 7 alınır; şimdi 7’nin listesi taranacak.
Kuyruk boşaldı → arama biter. Sıra 0 1 2 3 4 5 6 7: düzey düzey. Turuncu kenarlar önce genişliğine kapsayan ağaç.
on = arka = NULL; printf("%d ", v); ugranildi[v] = TRUE; kuyruk_ekle(&on, &arka, v); while (on) { v = kuyruk_al(&on, &arka); for (w = baslik[v]; w; w = w->bag) if (!ugranildi[w->dugum]) { printf("%d ", w->dugum); kuyruk_ekle(&on, &arka, w->dugum); ugranildi[w->dugum] = TRUE; } }
Gezinmeler
BFS: Analiz ve İlerisi
Her düğüm kuyruğa 1 kez girer → while n kez döner. Listelerde toplam 2e öğe taranır: d0 + … + dn−1 = 2e. Toplam O(n + e); matrisle O(n²).
Başlangıca olan kenar sayısı (düzey) sırasıyla ziyaret; ağırlıksız çizgede en kısa yol ağacı.
Önce derinliğine/genişliğine arama kavramları; yapay zekâda arama uzayını ağaca benzetip gezinmede, min-max, B* araması, dal-sınır (branch and bound) konularında geçer.
DFS: yığın/özyineleme, derin gider (bileşen, döngü, topolojik düşünme). BFS: kuyruk, katman katman (en az kenarlı yol).
Bileşenler ve Ötesi
Bağlantılı Bileşenler (Connected Components)
void bitisik_bilesenler(int n) { int i; for (i = 0; i < n; i++) if (!ugranildi[i]) { /* yeni */ dfs(i); printf("\n"); } }
Yönsüz çizge bağlantılı mı, kaç bileşenli? DFS (ya da BFS) her çağrıldığında bir bileşeni tümüyle işaretler; her yeni satırda bir sayaç artırılırsa bileşen sayısı da bulunur.
Çıktı: 0 1 3 2 / 4 5 6 / 7 8 9
Çıktı: 0 / 1 2 4 3 / 5 6 7 8 / 9 10
Bağlantılı bileşenler · Kodu adım adım çalıştır
Kod İzleme: bitisik_bilesenler(10)
i = 0: işaretsiz → yeni bileşen; dfs(0) gezer: 0 1 3 2.
dfs döndü; yeni satıra geçilir.
i = 1: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).
i = 2: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).
i = 3: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).
i = 4: işaretsiz → yeni bileşen; dfs(4) gezer: 4 5 6.
dfs döndü; yeni satıra geçilir.
i = 5: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).
i = 6: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).
i = 7: işaretsiz → yeni bileşen; dfs(7) gezer: 7 8 9.
dfs döndü; yeni satıra geçilir.
i = 8: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).
i = 9: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).
Döngü bitti: 3 bileşen. Her düğüm ve her liste toplamda bir kez işlendi.
void bitisik_bilesenler(int n) { int i; for (i = 0; i < n; i++) if (!ugranildi[i]) { /* yeni */ dfs(i); printf("\n"); } }
Bileşenler ve Ötesi
Analiz: n × O(n + e) mi?
Dıştaki for n kez dönüyor, içteki dfs() O(n + e) idi. Karmaşıklık ikisinin çarpımı mı, O(n²) mi olur?
Tek bileşen varsa ilk turda O(n + e) çalışır ve tüm ugranildi[] TRUE olur; i’nin diğer değerlerinde if’e girilmez. Bileşenler ayrık olduğundan toplam iş O(n + e).
Her dfs çağrısı komşuları O(n)’de arar → bileşen bulma O(n²). “O da etkili yazılırsa…”
Bileşenler ve Ötesi
Teorem: Bağlantılı Çizgede En Az n − 1 Kenar
- Teorem: Çizgenin bağlantılı olması için en az n − 1 kenar bulunmalıdır.
- Tek yönlüdür: n − 1 kenar gerekli ama yeterli değil; n − 1 kenarlı çizge bağlantılı olabilir de olmayabilir de. Daha azsa kesinlikle bağlantılı değildir.
- En fazla: tüm düğümler birbirine komşuysa (tam çizge Kn) (n − 1) + (n − 2) + … + 1 = n(n − 1)/2. Demek ki e, n²’li bir ifade olabilir.
- Bağlantılı çizgede: n − 1 ≤ e ≤ n(n − 1)/2.
DFS ile döngü · Kodu adım adım çalıştır
Kod İzleme: yönsüz çizgede döngü var mı?
dongu_var(0, -1): 0 işaretlenir.
dongu_var(1, 0): 1 işaretlenir.
w → 0: işaretli ama 1’in atası; geldiğimiz kenar, döngü sayılmaz.
dongu_var(2, 1): 2 işaretlenir.
w → 1: işaretli ama 2’nin atası; geldiğimiz kenar, döngü sayılmaz.
dongu_var(3, 2): 3 işaretlenir.
w → 1: işaretli ve ata (2) değil → geri kenar (3,1): döngü var! TRUE yukarı taşınır.
bool dongu_var(int v, int ata) { oge_gost w; ugranildi[v] = TRUE; for (w = baslik[v]; w; w = w->bag) if (!ugranildi[w->dugum]) { if (dongu_var(w->dugum, v)) return TRUE; } else if (w->dugum != ata) return TRUE; /* geri kenar */ return FALSE; }
Bileşenler ve Ötesi
Döngü Tespiti: Yönsüz ve Yönlü
DFS’de ziyaretli ama ata olmayan bir komşu = geri kenar = döngü. O(n + e).
“Şu an yığında olan” (gri) düğüme giden kenar döngüdür; üç renk: beyaz/gri/siyah. Topolojik sıralama yordamı da döngüyü yakalar.
Önceliği olan işlerde döngü, projenin hiç bitmemesi demektir; MST’de döngü, fazladan kenar demektir.
Aynı DFS matrisle O(n²) olur.
Bileşenler ve Ötesi
İki Parçalı (Bipartite) Çizge Testi
- V, iki kümeye ayrılabilsin ve her kenar farklı kümeleri bağlasın: iki parçalı çizge (ör. K3,3, öğrenci–ders).
- Test: BFS ile iki renge boyanır; komşu ters renge. Aynı renkli bir kenar çıkarsa değildir.
- Teorem: çizge iki parçalıdır ⇔ tek uzunlukta döngü yoktur.
- Maliyet BFS ile aynı: O(n + e); her bileşen için ayrı başlatılır.
BFS: 0 boyandı (renk 0). Komşu, kendisinin tersi renge boyanır.
BFS: 1 boyandı (renk 1). Komşu, kendisinin tersi renge boyanır.
BFS: 5 boyandı (renk 1). Komşu, kendisinin tersi renge boyanır.
BFS: 2 boyandı (renk 0). Komşu, kendisinin tersi renge boyanır.
BFS: 4 boyandı (renk 0). Komşu, kendisinin tersi renge boyanır.
BFS: 3 boyandı (renk 1). Komşu, kendisinin tersi renge boyanır.
Üçgen: 0 boyandı.
Üçgen: 1 boyandı.
Üçgen: 2 boyandı.
Aynı renkli iki komşu → iki parçalı değil (tek uzunluklu döngü var).
Özet
Karmaşıklık Özeti
| İşlem | Matris | Liste | Not |
|---|---|---|---|
| Bellek | O(n²) | O(n + e) | yönsüz listede 2e öğe |
| Derece (yönsüz) | O(n) | O(d) | yönlüde giriş için ters liste |
| Kenar sayısı | O(n²) | O(n + e) | Teorem 1: toplam / 2 |
| DFS / BFS | O(n²) | O(n + e) | her düğüm bir kez |
| Bağlantılı bileşenler | O(n²) | O(n + e) | çarpım değil! |
| Döngü / iki parçalılık | O(n²) | O(n + e) | DFS / BFS üzerine |
| Transpoz liste kurma | — | O(n + e) | bir kez gezinerek |
n: düğüm sayısı · e: kenar sayısı · d: düğümün derecesi · n − 1 ≤ e ≤ n(n − 1)/2 (bağlantılı, basit çizge)
Özet
Sık Yapılan Hatalar
ugranildi[]’yi her gezinmeden önce FALSE yapmayı unutmak (global dizi!).malloc sonucunu kontrol etmemek; programın sonunda listeleri free etmemek.Özet
Alıştırmalar
- Derece dizisi (3, 3, 2, 2, 1) olan basit yönsüz bir çizge var mıdır? Kaç kenarı vardır?
- G4 için dizi ile sıralı gösterimde 5 numaralı düğümün komşularını hangi indisler tutar?
- 8 düğümlü örnek çizgede DFS ve BFS’yi 7’den başlatın (listeler sıralı).
- BFS kodunda
ugranildi[w->dugum] = TRUEsatırıkuyruk_al’dan sonraya taşınırsa ne olur? - Komşuluk matrisiyle verilen çizgenin bileşen sayısını bulan yordamı yazın; karmaşıklığı nedir?
- Yönlü çizgede in-degree’yi O(1)’de verebilmek için hangi yapıyı, nasıl güncellersiniz?
Cevaplar / ipuçları
- Toplam 11 tek sayı → 2e olamaz (Teorem 1). Böyle bir çizge yoktur.
- dugum[5] = 18, dugum[6] = 20 → indis 18, 19: komşular 4 ve 6.
- DFS(7): 7 3 1 0 2 5 6 4 · BFS(7): 7 3 4 5 6 1 2 0.
- Aynı düğüm birden çok kez kuyruğa girebilir (ör. 7, hem 3 hem 4 üzerinden); çıktı tekrar eder, iş O(e)’yi aşar.
- Her i için işaretli değilse satırı tarayan DFS: O(n²).
- Her düğüm için
giris[]sayacı (ya da ters liste); kenar eklerken/silerken güncellenir.
Özet
Bölüm Özeti
G = (V, E); yönlü/yönsüz, komşu, alt çizge, yol, derece; Σd = 2e; bağlantılılık için e ≥ n − 1.
Matris O(n²); liste O(n + e); sıralı dizi; ortogonal (yönlü); çok bağlı (her kenar tek öğe).
DFS = özyineleme/yığın; BFS = kuyruk. İkisi de listeyle O(n + e), matrisle O(n²).
Bağlantılı bileşenler O(n + e); döngü tespiti; iki parçalılık testi; kapsayan ağaçlara hazırlık.
Önce problem, sonra verinin nasıl depolanacağı düşünülür; ilk çözüme güvenilmez.
Özet
Kaynaklar
- E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, 2. baskı, Bölüm 6 (Graphs).
- T. H. Cormen ve ark., Introduction to Algorithms, 4. baskı, Bölüm 20 (Elementary Graph Algorithms).
- R. Sedgewick, Algorithms in C, Part 5: Graph Algorithms, 3. baskı.