Bölüm 15: Çizgeler

C ile Veri Yapıları ve Algoritmalar
KönigsbergTanımlarMatris ve listelerÇok bağlı listeDFSBFSBileşenler
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Tanımlamak

G = (V, E), yönlü/yönsüz çizge, komşuluk, alt çizge, yol, derece ve bağlantılılık kavramlarını kullanmak.

İspatlamak

Derece toplamı = 2e (Teorem 1) ve bağlantılılık için e ≥ n − 1 koşulunu gerekçelendirmek.

Gerçeklemek

Komşuluk matrisi, komşuluk listesi, sıralı dizi, ortogonal liste ve çok bağlı listeyi C ile kurmak.

Gezinmek

Önce derinliğine (DFS) ve önce genişliğine (BFS) aramayı kodlayıp izlemek, O(n + e) analizini yapmak.

Uygulamak

Bağlantılı bileşenleri, döngüyü ve iki parçalılığı DFS/BFS ile bulmak.

Seçmek

Problemin işlemlerine bakarak uygun gösterimi seçmek: “önce veri yapısı, sonra kod”.

Giriş

Problem: Königsberg’in 7 Köprüsü

C (kuzey kıyısı)B (güney kıyısı)A (ada)D7 köprü

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?

C (kuzey kıyısı)B (güney kıyısı)A (ada)D7 köprüCADB

Euler (1736) kara parçalarını düğüm, köprüleri kenar yaptı: şekil değil, bağlantı önemlidir (notasyon/çizim farkı).

C (kuzey kıyısı)B (güney kıyısı)A (ada)D7 köprüCADBd=3d=5d=3d=3

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.

Çizge kuramının başlangıcı sayılır. Euler (Oyler okunur) sonucu teorem olarak verdi; ispatı Çizge Kuramı derslerine kalıyor (4. sınıf: “Çizge Kuramı ve Kombinatorik”).

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.

0123G1: bağlantılı (K4)0123456G2: ağaç, döngü yok012G3: yönlü02134567G4: iki bileşen
ÇizgeV(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?

Mühendislik

Elektrik devrelerinin analizi, proje planlama (aktivite ağları), kimyasal bileşenleri tanıma.

Yol ve ağ

En kısa yol, yönlendirme (router), bilgisayar ağlarıyla ilgili birçok problem.

Ağaç ve sıralama

Ağaçlar birer çizgedir; topolojik sıralama, sonlu özdevinirler (finite automata).

Akış

Taşıma ağlarında akış; max flow: kaynaktan hedefe en çok petrol/su taşıma.

Kapsayan ağaç

Minimum kapsayan ağaç: köyleri en az kabloyla bağlamak.

Bağlılık

Bağlantılı çizge, bileşen sayısı, bir ağda kopukluk var mı?

Hocadan: “Nerelerde kullanılmaz ki! Çizgelerin çok geniş bir alanda kullanımı söz konusudur.”

Tanımlar ve Terminoloji

Terminoloji: Komşu Düğüm, Alt Çizge

Komşu (adjacent)

(v0, v1) yönsüz çizgede bir kenarsa v0 ile v1 komşudur; kenar bu iki düğüme bitişiktir.

Alt çizge (subgraph)

G′, G’nin alt çizgesi ise V(G′) ⊆ V(G) ve E(G′) ⊆ E(G) olur. Tek düğüm de bir alt çizgedir.

0123G100121230123G1’in olası alt çizgelerinden bazıları012G301012G3’ün alt çizgeleri

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.
012giriş: 1çıkış: 1

G3’te düğüm 0: d = 1 + 1 = 2

“G3 çizgesinde d = 1 + 1 = 2” ifadesi düğüm 0 içindir: derece her zaman belirli bir düğüm için söylenir.

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.

0123d=1d=1d=0d=0işlenen kenar: e = 1Σ d = 1 + 1 + 0 + 0 = 22e = 2

1. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.

0123d=2d=1d=1d=0işlenen kenar: e = 2Σ d = 2 + 1 + 1 + 0 = 42e = 4

2. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.

0123d=3d=1d=1d=1işlenen kenar: e = 3Σ d = 3 + 1 + 1 + 1 = 62e = 6

3. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.

0123d=3d=2d=2d=1işlenen kenar: e = 4Σ d = 3 + 2 + 2 + 1 = 82e = 8

4. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.

0123d=3d=3d=2d=2işlenen kenar: e = 5Σ d = 3 + 3 + 2 + 2 = 102e = 10

5. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.

0123d=3d=3d=3d=3işlenen kenar: e = 6Σ d = 3 + 3 + 3 + 3 = 122e = 12

6. kenar eklendi: iki ucun derecesi 1’er arttı → toplam 2 arttı.

Hocadan: “‘Para kazanacağın kitabı oku!’ diyor hoca; ‘Yok, almayacağım!’ deniyor. Mesleğe ilişkin kitap okuma alışkanlığı bu yaşlarda kazanılmalı.”

Gerçeklemeler

Çizgeyi Bellekte Nasıl Gerçekleriz?

1Komşuluk matrisi
2Komşuluk listesi (+ tersi)
3Dizi ile sıralı gösterim
4Ortogonal liste (yönlü)
5Çok bağlı liste
+Ağırlıklı gösterim
Hocadan: “Önce kavramı anlatıyoruz, sonra bilgisayar ortamında gerçeklenmesini… Burada amacımız discrete mathematics anlatmak değil. ‘Çizgeyi bellekte nasıl gerçekleriz?’i anlatmak.”

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

G1 — (0,2) ∈ E(G1): a[0][2] = a[2][0] = 1
0123
00111
11011
21101
31110
G3 — <0,1> ve <1,2> işaretli; simetrik değil
012
0010
1101
2000
Derece bulma

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.

Kenar sayısı bulma

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

G4; kırmızı çerçeve: sık karıştırılan hücreler
01234567
001100000
110010000
210010000
301100000
400000100
500001010
600000101
700000010
02134567

G4: iki bileşen

Sık yapılan hata: 1. satırı 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)

012001011012000a[][] (G3)012

derece(a, 3, 1): G3’te düğüm 1’in derecesi aranıyor.

012001011012000a[][] (G3)012

Sayaçlar sıfırlanır.

012001011012000a[][] (G3)012

Satır 1, sütun 0: a[1][0] = 1 → cikis = 1.

012001011012000a[][] (G3)012

Satır 1, sütun 1: a[1][1] = 0 → cikis = 1.

012001011012000a[][] (G3)012

Satır 1, sütun 2: a[1][2] = 1 → cikis = 2.

012001011012000a[][] (G3)012

Sütun 1, satır 0: a[0][1] = 1 → giris = 1.

012001011012000a[][] (G3)012

Sütun 1, satır 1: a[1][1] = 0 → giris = 1.

012001011012000a[][] (G3)012

Sütun 1, satır 2: a[2][1] = 0 → giris = 1.

012001011012000a[][] (G3)012

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 */
0123102320133012

G1’in komşuluk listesiyle gösterimi

Kenar sayısı

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.

Derece

Yönsüzde i. başlıktaki öğeleri saymak yeter: O(di).

Gerçeklemeler

Komşuluk Listesi: Yönsüz ve Yönlü Örnekler

0121032033124554665776G4’ün komşuluk listesi011022G3 (çıkan kenarlar)011021G3’ün tersi (gelen kenarlar)012
Yönsüz (G1): sıralılık avantajı

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

Yönlü (G3)

<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

056914710238G5

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.

11 düğüm, 2 kenar, 9 bileşen. Matris 121 hücrenin 117’sini boşa harcar; liste yalnızca 11 başlık + 4 öğe tutar. Seyrek çizgede liste, yoğun çizgede matris düşünülür — ama karar her zaman yapılacak işlemlere göre verilir.

Komşuluk listesi · Kodu adım adım çalıştır

Kod İzleme: kenarları yapıya eklemek

012012kurulan kenarlar koyu

Kenar (0,1) → önce kenar_ekle(0, 1): yeni öğe için bellek alınır (NULL kontrolü!).

0112012kurulan kenarlar koyu

p->dugum = 1, p->bag = NULL, baslik[0] = p: öğe listenin başına girdi.

0112012kurulan kenarlar koyu

Kenar (0,1) → sonra kenar_ekle(1, 0): yeni öğe için bellek alınır (NULL kontrolü!).

01102012kurulan kenarlar koyu

p->dugum = 0, p->bag = NULL, baslik[1] = p: öğe listenin başına girdi.

01102012kurulan kenarlar koyu

Kenar (0,2) → önce kenar_ekle(0, 2): yeni öğe için bellek alınır (NULL kontrolü!).

021102012kurulan kenarlar koyu

p->dugum = 2, p->bag = &oge(1), baslik[0] = p: öğe listenin başına girdi.

021102012kurulan kenarlar koyu

Kenar (0,2) → sonra kenar_ekle(2, 0): yeni öğe için bellek alınır (NULL kontrolü!).

0211020012kurulan kenarlar koyu

p->dugum = 0, p->bag = NULL, baslik[2] = p: öğe listenin başına girdi.

0211020012kurulan kenarlar koyu

Kenar (1,2) → önce kenar_ekle(1, 2): yeni öğe için bellek alınır (NULL kontrolü!).

02112020012kurulan kenarlar koyu

p->dugum = 2, p->bag = &oge(0), baslik[1] = p: öğe listenin başına girdi.

02112020012kurulan kenarlar koyu

Kenar (1,2) → sonra kenar_ekle(2, 1): yeni öğe için bellek alınır (NULL kontrolü!).

021120210012kurulan kenarlar koyu

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?

Hocadan: “Kenarlar geliyor, yapıya eklenebilmeli. Ben bakıp çizdim; program bakıp çizemez ki! Algoritma çalışacak.”
Başa ekleme

Her kenar O(1)’de yerleşir; liste gelen sıranın tersidir. Toplam kurulum O(n + e).

Sona ekleme

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.

Sıralı ekleme

Listeyi düğüm numarasına göre sıralı tutmak: kenar sayma ve gezinme sırası öngörülebilir olur, ekleme O(d).

Serbest bırakma

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

bas[] (G3)011022ters[] (transpoz)012

Önce ters[] başlıkları NULL yapılır.

bas[] (G3)011022ters[] (transpoz)0102

<0,1> kenarı: ters[1] listesine 0 eklenir (<1,0>).

bas[] (G3)011022ters[] (transpoz)01102

<1,0> kenarı: ters[0] listesine 1 eklenir (<0,1>).

bas[] (G3)011022ters[] (transpoz)011021

<1,2> kenarı: ters[2] listesine 1 eklenir (<2,1>).

bas[] (G3)011022ters[] (transpoz)011021

bas[2] boş: 2’den çıkan kenar yok.

bas[] (G3)011022ters[] (transpoz)011021

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.

0911121331541751862072282391102110123130143151162175184196205217226n + 1 = 9 başlık (düğüm[n] = n + 2e + 1 = 23)2e = 14 komşu kaydı

G4 için dizi. Her kenar yine 2 kez yazılır ama işaretçi yok.

0911121331541751862072282391102110123130143151162175184196205217226n + 1 = 9 başlık (düğüm[n] = n + 2e + 1 = 23)2e = 14 komşu kaydı0’ın komşuları: dugum[0] = 9 … dugum[1] = 11 (hariç) → 1, 2

Ör. 1: [0] = 9’dan (dahil) [1] = 11’e (hariç): indis 9 ve 10 → komşular 1, 2.

0911121331541751862072282391102110123130143151162175184196205217226n + 1 = 9 başlık (düğüm[n] = n + 2e + 1 = 23)2e = 14 komşu kaydı1’in komşuları: dugum[1] = 11 … dugum[2] = 13 (hariç) → 0, 3

Ör. 2: [1] = 11, [2] = 13 → indis 11, 12 → komşular 0, 3.

0911121331541751862072282391102110123130143151162175184196205217226n + 1 = 9 başlık (düğüm[n] = n + 2e + 1 = 23)2e = 14 komşu kaydı7’nin komşuları: dugum[7] = 22 … dugum[8] = 23 (hariç) → 6

Ör. 3: [7] = 22, [8] = 23 → yalnızca indis 22 → tek komşu 6.

Hocadan: “Depolamak ayrı konudur, o veri üzerinde çalışacak algoritmaların etkinliği ayrı konudur… Çok sayıda ekleme-silme işlemi için bu yapı verimli değildir. Örneğin bir ağdaki bilgisayarlar sabittir; problem onları tutmaksa bu yapı uygun olabilir.”

Sıralı gösterim · Kodu adım adım çalıştır

Kod İzleme: komsulari_yaz(dugum, 5)

0911121331541751862072282391102110123130143151162175184196205217226n + 1 = 9 başlık (düğüm[n] = n + 2e + 1 = 23)2e = 14 komşu kaydı

komsulari_yaz(dugum, 5): 5 numaralı düğümün komşuları.

0911121331541751862072282391102110123130143151162175184196205217226k = 18

k = 18 < dugum[6] = 20 → dugum[18] = 4 yazılır.

0911121331541751862072282391102110123130143151162175184196205217226k = 19

k = 19 < dugum[6] = 20 → dugum[19] = 6 yazılır.

0911121331541751862072282391102110123130143151162175184196205217226n + 1 = 9 başlık (düğüm[n] = n + 2e + 1 = 23)2e = 14 komşu kaydı5’in komşuları: dugum[5] = 18 … dugum[6] = 20 (hariç) → 4, 6

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

001122001122satır başlıklarısütun başlıkları (aynı öğeler, iki kez çizildi)011012kuyruk | baş | kolon bağı | satır bağıkoyu hücre = NULL

G3: her kenar öğesi aynı anda bir satır (çıkış) ve bir sütun (giriş) listesinde

İki bağ

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.

Seyrek matris gibi

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

Hocadan: “Akla gelen ilk çözüme de, o koda da hiç güvenilmemeli. Arka plandaki veri yapısı bu değil de başka bir şey olsaydı, daha verimli çözebilir miydim?”

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.

Öğedugum1dugum2list1list2
N001N1N3
N102N2N3
N203NULLN4
N312N4N5
N413NULLN5
N523NULLNULL

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

01··N0N1N302··N1N2N303··N2∅N412··N3N4N513··N4∅N523··N5∅∅alanlar: dugum1 | dugum2 | list1 | list2 (∅ = NULL)p

p = baslik[2] = N1: 2’nin ilk kenarı.

01··N0N1N302··N1N2N303··N2∅N412··N3N4N513··N4∅N523··N5∅∅alanlar: dugum1 | dugum2 | list1 | list2 (∅ = NULL)p

N1 = (0,2): v, dugum2’de → öteki uç 0 yazılır.

01··N0N1N302··N1N2N303··N2∅N412··N3N4N513··N4∅N523··N5∅∅alanlar: dugum1 | dugum2 | list1 | list2 (∅ = NULL)p

Bağ izlenir: p = p->list2 = N3.

01··N0N1N302··N1N2N303··N2∅N412··N3N4N513··N4∅N523··N5∅∅alanlar: dugum1 | dugum2 | list1 | list2 (∅ = NULL)p

N3 = (1,2): v, dugum2’de → öteki uç 1 yazılır.

01··N0N1N302··N1N2N303··N2∅N412··N3N4N513··N4∅N523··N5∅∅alanlar: dugum1 | dugum2 | list1 | list2 (∅ = NULL)p

Bağ izlenir: p = p->list2 = N5.

01··N0N1N302··N1N2N303··N2∅N412··N3N4N513··N4∅N523··N5∅∅alanlar: dugum1 | dugum2 | list1 | list2 (∅ = NULL)p

N5 = (2,3): v, dugum1’de → öteki uç 3 yazılır.

01··N0N1N302··N1N2N303··N2∅N412··N3N4N513··N4∅N523··N5∅∅alanlar: dugum1 | dugum2 | list1 | list2 (∅ = NULL)

Bağ izlenir: p = p->list1 = NULL.

01··N0N1N302··N1N2N303··N2∅N412··N3N4N513··N4∅N523··N5∅∅alanlar: dugum1 | dugum2 | list1 | list2 (∅ = 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 */
}
Hocadan: “Bu sırayla gelip “O(1)’de yerleşsin” denilirse liste başına eklenecek! Böyle (liste sonuna ekleyerek) çizene puan vermem. Çünkü liste sonuna eklediğini görüyorum.”

Gerçeklemeler

Alıştırma: <0,1>, <0,3>, <1,2>, <2,0>, <3,2>

0132

Kenar listesi verilen yönlü çizge için komşuluk matrisini, komşuluk listesini (ve tersini) ve çok bağlı listeyi veriniz.

0132
satır = kuyruk, sütun = baş
0123
00101
10010
21000
30010

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

013122032

çıkan (komşuluk listesi)

021021330

gelen (listenin transpozu)

Liste ve tersi: toplam 2e = 10 öğe.

Öğekuyrukbaşkuyruk bağıbaş bağı
N101N2NULL
N203NULLNULL
N312NULLN5
N420NULLNULL
N532NULLNULL
Düğümkuyruk (çıkış)baş (giriş)
0N1N4
1N3N1
2N4N3
3N5N2

Ç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österimBellek(u,v) var mı?u’nun komşularıEkleme/silme
Komşuluk matrisiO(n²)O(1)O(n)O(1)
Komşuluk listesiO(n + 2e)O(du)O(du)başa O(1) / arama O(d)
Sıralı dizin + 2e + 1O(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
Hocadan: “Akla gelen ilk çözüm iyi bir yöntem olmayabilir. Belki n³’lük bir algoritmayı n’e indirmek mümkün olabilir. Bu noktada veri yapısını iyi tasarlamak gerekir. Daha kod yazmadan önce veriyi nasıl depolayacağımı düşünüyorum. Veriyi depolamak o kadar kolay değil!”

Gerçeklemeler

Ağırlıklı Çizge Gösterimi

42581ABCD

ağırlık = mesafe, maliyet, süre…

maliyet matrisi: kenar yoksa ∞ (C’de büyük sabit)
ABCD
A042∞
B4015
C2108
D∞580
#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 agirlik alanı eklenir.
  • ∞ için INT_MAX kullanmak d + w toplamı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.
01234567

Örnek çizge

0121034205631741752762773456

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)

01234567ugranildi[]0T1F2F3F4F5F6F7Fbaslik[0]12

dfs(0) çağrıldı: 0 işaretlenir ve yazılır.

01234567ugranildi[]0T1T2F3F4F5F6F7Fbaslik[1]034

0 → 1: 1 ziyaret edilmemiş → dfs(1). 1 işaretlenir, yazılır.

01234567ugranildi[]0T1T2F3F4F5F6F7Fbaslik[1]0w34

w → 0: ugranildi[0] TRUE, atlanır.

01234567ugranildi[]0T1T2F3T4F5F6F7Fbaslik[3]17

1 → 3: 3 ziyaret edilmemiş → dfs(3). 3 işaretlenir, yazılır.

01234567ugranildi[]0T1T2F3T4F5F6F7Fbaslik[3]1w7

w → 1: ugranildi[1] TRUE, atlanır.

01234567ugranildi[]0T1T2F3T4F5F6F7Tbaslik[7]3456

3 → 7: 7 ziyaret edilmemiş → dfs(7). 7 işaretlenir, yazılır.

01234567ugranildi[]0T1T2F3T4F5F6F7Tbaslik[7]3w456

w → 3: ugranildi[3] TRUE, atlanır.

01234567ugranildi[]0T1T2F3T4T5F6F7Tbaslik[4]17

7 → 4: 4 ziyaret edilmemiş → dfs(4). 4 işaretlenir, yazılır.

01234567ugranildi[]0T1T2F3T4T5F6F7Tbaslik[4]1w7

w → 1: ugranildi[1] TRUE, atlanır.

01234567ugranildi[]0T1T2F3T4T5F6F7Tbaslik[4]17w

w → 7: ugranildi[7] TRUE, atlanır.

01234567ugranildi[]0T1T2F3T4T5T6F7Tbaslik[5]27

(dfs(4) bitti.) 7 → 5: 5 ziyaret edilmemiş → dfs(5). 5 işaretlenir, yazılır.

01234567ugranildi[]0T1T2T3T4T5T6F7Tbaslik[2]056

5 → 2: 2 ziyaret edilmemiş → dfs(2). 2 işaretlenir, yazılır.

01234567ugranildi[]0T1T2T3T4T5T6F7Tbaslik[2]0w56

w → 0: ugranildi[0] TRUE, atlanır.

01234567ugranildi[]0T1T2T3T4T5T6F7Tbaslik[2]05w6

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)

01234567ugranildi[]0T1T2T3T4T5T6T7Tbaslik[6]27

2 → 6: 6 ziyaret edilmemiş → dfs(6). 6 işaretlenir, yazılır.

01234567ugranildi[]0T1T2T3T4T5T6T7Tbaslik[6]2w7

w → 2: ugranildi[2] TRUE, atlanır.

01234567ugranildi[]0T1T2T3T4T5T6T7Tbaslik[6]27w

w → 7: ugranildi[7] TRUE, atlanır.

01234567ugranildi[]0T1T2T3T4T5T6T7Tbaslik[5]27w

dfs(6), dfs(2) bitti; 5’te kalınan yerden devam. w → 7: ugranildi[7] TRUE, atlanır.

01234567ugranildi[]0T1T2T3T4T5T6T7Tbaslik[7]3456w

dfs(5) bitti; 7’de kalınan yerden devam. w → 6: ugranildi[6] TRUE, atlanır.

01234567ugranildi[]0T1T2T3T4T5T6T7Tbaslik[1]034w

dfs(7), dfs(3) bitti; 1’de kalınan yerden devam. w → 4: ugranildi[4] TRUE, atlanır.

01234567ugranildi[]0T1T2T3T4T5T6T7Tbaslik[0]12w

dfs(1) bitti; 0’da kalınan yerden devam. w → 2: ugranildi[2] TRUE, atlanır.

01234567ugranildi[]0T1T2T3T4T5T6T7T

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

Başlangıç düğümü

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

Listenin sıraya etkisi

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.

Analiz

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.

Veri yapısı

Matrisle gösterilseydi v’nin komşularını bulmak O(n), toplam O(n²) olurdu. Listeyle doğrusal zamanda geziniyoruz.

Gezinme sonucunda bir ağaç oluştu (önce derinliğine kapsayan ağaç). Bu ağaca bir kenar daha eklersek döngü oluşur ve ağaç olmaktan çıkar.

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.
Hocadan: “Nasıl ki, önce derinliğine aramada problemin doğası gereği yığın kullanma zorunluluğu vardı; bunda da kuyruk kullanma zorunluluğu vardır.”
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)

01234567ugranildi[]0T1F2F3F4F5F6F7Fkuyruk0▲ on▲ arka

0 yazılır, işaretlenir ve kuyruğa eklenir.

01234567ugranildi[]0T1F2F3F4F5F6F7Fkuyrukboş (on = arka = NULL)baslik[0]12

Kuyruk boş değil: öndeki 0 alınır; şimdi 0’ın listesi taranacak.

01234567ugranildi[]0T1T2F3F4F5F6F7Fkuyruk1▲ on▲ arkabaslik[0]1w2

w → 1: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir.

01234567ugranildi[]0T1T2T3F4F5F6F7Fkuyruk12▲ on▲ arkabaslik[0]12w

w → 2: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir.

01234567ugranildi[]0T1T2T3F4F5F6F7Fkuyruk2▲ on▲ arkabaslik[1]034

Kuyruk boş değil: öndeki 1 alınır; şimdi 1’in listesi taranacak.

01234567ugranildi[]0T1T2T3T4F5F6F7Fkuyruk23▲ on▲ arkabaslik[1]03w4

w → 3: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir. (0 zaten işaretliydi.)

01234567ugranildi[]0T1T2T3T4T5F6F7Fkuyruk234▲ on▲ arkabaslik[1]034w

w → 4: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir.

01234567ugranildi[]0T1T2T3T4T5F6F7Fkuyruk34▲ on▲ arkabaslik[2]056

Kuyruk boş değil: öndeki 2 alınır; şimdi 2’nin listesi taranacak.

01234567ugranildi[]0T1T2T3T4T5T6F7Fkuyruk345▲ on▲ arkabaslik[2]05w6

w → 5: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir. (0 zaten işaretliydi.)

01234567ugranildi[]0T1T2T3T4T5T6T7Fkuyruk3456▲ on▲ arkabaslik[2]056w

w → 6: ziyaret edilmemiş → yazılır, kuyruğun arkasına eklenir, işaretlenir.

01234567ugranildi[]0T1T2T3T4T5T6T7Fkuyruk456▲ on▲ arkabaslik[3]17

Kuyruk boş değil: öndeki 3 alınır; şimdi 3’ün listesi taranacak.

01234567ugranildi[]0T1T2T3T4T5T6T7Tkuyruk4567▲ on▲ arkabaslik[3]17w

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)

01234567ugranildi[]0T1T2T3T4T5T6T7Tkuyruk567▲ on▲ arkabaslik[4]17

Kuyruk boş değil: öndeki 4 alınır; şimdi 4’ün listesi taranacak.

01234567ugranildi[]0T1T2T3T4T5T6T7Tkuyruk67▲ on▲ arkabaslik[5]27

Kuyruk boş değil: öndeki 5 alınır; şimdi 5’in listesi taranacak.

01234567ugranildi[]0T1T2T3T4T5T6T7Tkuyruk7▲ on▲ arkabaslik[6]27

Kuyruk boş değil: öndeki 6 alınır; şimdi 6’nın listesi taranacak.

01234567ugranildi[]0T1T2T3T4T5T6T7Tkuyrukboş (on = arka = NULL)baslik[7]3456

Kuyruk boş değil: öndeki 7 alınır; şimdi 7’nin listesi taranacak.

01234567ugranildi[]0T1T2T3T4T5T6T7Tkuyrukboş (on = arka = NULL)

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

Analiz

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

Ne verir?

Başlangıca olan kenar sayısı (düzey) sırasıyla ziyaret; ağırlıksız çizgede en kısa yol ağacı.

İlerisi

Ö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 mi BFS mi?

DFS: yığın/özyineleme, derin gider (bileşen, döngü, topolojik düşünme). BFS: kuyruk, katman katman (en az kenarlı yol).

Hocadan: “Oyun problemlerinde de “Hamleler şunlar olabilir.” deniyorsa genişliğine açılıyor demektir.”

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.

0123456789

Çıktı: 0 1 3 2 / 4 5 6 / 7 8 9

012345678910

Çıktı: 0 / 1 2 4 3 / 5 6 7 8 / 9 10

2. satırın çıktısı 1 2 4 3’tür; 5 sonraki bileşendedir.

Bağlantılı bileşenler · Kodu adım adım çalıştır

Kod İzleme: bitisik_bilesenler(10)

0123456789ugranildi[]0T1T2T3T4F5F6F7F8F9Fbileşen sayısı: 1

i = 0: işaretsiz → yeni bileşen; dfs(0) gezer: 0 1 3 2.

0123456789ugranildi[]0T1T2T3T4F5F6F7F8F9Fbileşen sayısı: 1

dfs döndü; yeni satıra geçilir.

0123456789ugranildi[]0T1T2T3T4F5F6F7F8F9Fbileşen sayısı: 1

i = 1: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).

0123456789ugranildi[]0T1T2T3T4F5F6F7F8F9Fbileşen sayısı: 1

i = 2: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).

0123456789ugranildi[]0T1T2T3T4F5F6F7F8F9Fbileşen sayısı: 1

i = 3: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).

0123456789ugranildi[]0T1T2T3T4T5T6T7F8F9Fbileşen sayısı: 2

i = 4: işaretsiz → yeni bileşen; dfs(4) gezer: 4 5 6.

0123456789ugranildi[]0T1T2T3T4T5T6T7F8F9Fbileşen sayısı: 2

dfs döndü; yeni satıra geçilir.

0123456789ugranildi[]0T1T2T3T4T5T6T7F8F9Fbileşen sayısı: 2

i = 5: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).

0123456789ugranildi[]0T1T2T3T4T5T6T7F8F9Fbileşen sayısı: 2

i = 6: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).

0123456789ugranildi[]0T1T2T3T4T5T6T7T8T9Tbileşen sayısı: 3

i = 7: işaretsiz → yeni bileşen; dfs(7) gezer: 7 8 9.

0123456789ugranildi[]0T1T2T3T4T5T6T7T8T9Tbileşen sayısı: 3

dfs döndü; yeni satıra geçilir.

0123456789ugranildi[]0T1T2T3T4T5T6T7T8T9Tbileşen sayısı: 3

i = 8: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).

0123456789ugranildi[]0T1T2T3T4T5T6T7T8T9Tbileşen sayısı: 3

i = 9: zaten işaretli (bir önceki DFS’de gezildi) → if’e girilmez, O(1).

0123456789ugranildi[]0T1T2T3T4T5T6T7T8T9Tbileşen sayısı: 3

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?

Hocadan: “Olur mu? Olur mu? Olur mu? O kadar çalışır mı? Hayır! … “Hepsini çarparım, sonucu söylerim.” Değil!”
Neden O(n + e)?

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

Matrisle olsaydı?

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.
0123401234K5: 10 kenarn − 1 = 4 kenar, bağlantılı
Hocadan: “Tic tac toe oyunu… “Rakibin hamlesini nasıl bozarım?” Okununca bu kavramlar daha iyi oturur, tahmin ediyorum.”

DFS ile döngü · Kodu adım adım çalıştır

Kod İzleme: yönsüz çizgede döngü var mı?

0123ugranildi[]0T1F2F3F

dongu_var(0, -1): 0 işaretlenir.

0123ugranildi[]0T1T2F3F

dongu_var(1, 0): 1 işaretlenir.

0123ugranildi[]0T1T2F3F

w → 0: işaretli ama 1’in atası; geldiğimiz kenar, döngü sayılmaz.

0123ugranildi[]0T1T2T3F

dongu_var(2, 1): 2 işaretlenir.

0123ugranildi[]0T1T2T3F

w → 1: işaretli ama 2’nin atası; geldiğimiz kenar, döngü sayılmaz.

0123ugranildi[]0T1T2T3T

dongu_var(3, 2): 3 işaretlenir.

0123ugranildi[]0T1T2T3T

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ü

Yönsüz

DFS’de ziyaretli ama ata olmayan bir komşu = geri kenar = döngü. O(n + e).

Yönlü

“Ş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.

Neden önemli?

Önceliği olan işlerde döngü, projenin hiç bitmemesi demektir; MST’de döngü, fazladan kenar demektir.

Matrisle

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.
0123456-döngü012üçgen

BFS: 0 boyandı (renk 0). Komşu, kendisinin tersi renge boyanır.

0123456-döngü012üçgen

BFS: 1 boyandı (renk 1). Komşu, kendisinin tersi renge boyanır.

0123456-döngü012üçgen

BFS: 5 boyandı (renk 1). Komşu, kendisinin tersi renge boyanır.

0123456-döngü012üçgen

BFS: 2 boyandı (renk 0). Komşu, kendisinin tersi renge boyanır.

0123456-döngü012üçgen

BFS: 4 boyandı (renk 0). Komşu, kendisinin tersi renge boyanır.

0123456-döngü012üçgen

BFS: 3 boyandı (renk 1). Komşu, kendisinin tersi renge boyanır.

0123456-döngü: iki parçalı ✓012üçgen

Üçgen: 0 boyandı.

0123456-döngü: iki parçalı ✓012üçgen

Üçgen: 1 boyandı.

0123456-döngü: iki parçalı ✓012üçgen

Üçgen: 2 boyandı.

0123456-döngü: iki parçalı ✓012üçgen

Aynı renkli iki komşu → iki parçalı değil (tek uzunluklu döngü var).

Özet

Karmaşıklık Özeti

İşlemMatrisListeNot
BellekO(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 / BFSO(n²)O(n + e)her düğüm bir kez
Bağlantılı bileşenlerO(n²)O(n + e)çarpım değil!
Döngü / iki parçalılıkO(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

1Yönlü çizgede <u,v> varken <v,u>'yu da var saymak; matrisi simetrik sanmak.
2BFS’de düğümü kuyruğa eklerken değil, çıkarırken işaretlemek → aynı düğüm kuyruğa birden çok kez girer.
3ugranildi[]’yi her gezinmeden önce FALSE yapmayı unutmak (global dizi!).
4Dış for × DFS’yi çarpıp bileşenler için O(n(n + e)) demek.
5Liste kurarken malloc sonucunu kontrol etmemek; programın sonunda listeleri free etmemek.
6Kenarı sona ekleyip her seferinde listeyi baştan yürümek; O(1)’lik başa eklemeyi kaçırmak.

Özet

Alıştırmalar

  1. Derece dizisi (3, 3, 2, 2, 1) olan basit yönsüz bir çizge var mıdır? Kaç kenarı vardır?
  2. G4 için dizi ile sıralı gösterimde 5 numaralı düğümün komşularını hangi indisler tutar?
  3. 8 düğümlü örnek çizgede DFS ve BFS’yi 7’den başlatın (listeler sıralı).
  4. BFS kodunda ugranildi[w->dugum] = TRUE satırı kuyruk_al’dan sonraya taşınırsa ne olur?
  5. Komşuluk matrisiyle verilen çizgenin bileşen sayısını bulan yordamı yazın; karmaşıklığı nedir?
  6. Yönlü çizgede in-degree’yi O(1)’de verebilmek için hangi yapıyı, nasıl güncellersiniz?
Cevaplar / ipuçları
  1. Toplam 11 tek sayı → 2e olamaz (Teorem 1). Böyle bir çizge yoktur.
  2. dugum[5] = 18, dugum[6] = 20 → indis 18, 19: komşular 4 ve 6.
  3. DFS(7): 7 3 1 0 2 5 6 4 · BFS(7): 7 3 4 5 6 1 2 0.
  4. 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.
  5. Her i için işaretli değilse satırı tarayan DFS: O(n²).
  6. Her düğüm için giris[] sayacı (ya da ters liste); kenar eklerken/silerken güncellenir.

Özet

Bölüm Özeti

Kavramlar

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.

Gösterimler

Matris O(n²); liste O(n + e); sıralı dizi; ortogonal (yönlü); çok bağlı (her kenar tek öğe).

Gezinmeler

DFS = özyineleme/yığın; BFS = kuyruk. İkisi de listeyle O(n + e), matrisle O(n²).

Uygulamalar

Bağlantılı bileşenler O(n + e); döngü tespiti; iki parçalılık testi; kapsayan ağaçlara hazırlık.

Yöntem

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

Son

Bölüm 15: Çizgeler
Sonraki bölüm: Bölüm 16: Kümeler ve Union-Find · Ders sayfası
1 / 1 C ile Veri Yapıları