Bölüm 18: En Kısa Yol ve Aktivite Ağları
Giriş
Öğrenme Çıktıları
Şehir/demiryolu ve ağ gecikmesi problemlerini ağırlıklı yönlü çizge olarak kurmak.
Tek kaynaktan tüm hedeflere en kısa yolu açgözlü yöntemle bulmak; O(n²) kodu izlemek ve yolu öncüller dizisiyle yazdırmak.
Negatif kenarda Bellman-Ford’u, tüm çiftler için Floyd-Warshall’ı uygulamak.
AoV ağlarında öncül ilişkisine uyan doğrusal sırayı sayaç + yığın ile bulmak, döngüyü yakalamak.
AoE ağlarında en erken/en geç başlama zamanlarını ileri ve geri çözümlemeyle hesaplamak.
Bolluğu (slack) bulup kritik aktiviteleri ve kritik yolu belirlemek.
En Kısa Yol
En Kısa Yol Problemi (Shortest Path)
Horowitz–Sahni örneği
| Kaynak | Hedef | Maliyet |
|---|---|---|
| 0 | 5 | Yol yok! |
| 0 | 2 | 10 |
| 0 | 3 | 25 |
| 0 | 1 | 45 |
| 0 | 4 | 45 |
- Düğümler şehir, kenarlar iki şehir arasındaki demiryolu uzaklığı: bir şehirden diğerine en kısa yol hangisi?
- Ya da kenarlar bir ağda paket gecikmesi (trafik sıkışıklığı): bu verilere göre bir router nasıl yazılmalı?
- İki soru: 1) x’ten y’ye yol var mı? 2) Birden çok yol varsa en kısası hangisi?
- Tüm düğümlere birer kez uğrayıp başa dönmek (gezgin satıcı) farklı ve çok daha zor bir problemdir.
En Kısa Yol
Tek Kaynaktan Tek Hedefe: Kaba Kuvvet
K4 düzlemsel; K5 düzlemsel değil
K4 (0 → 3), n = 4 için 5 yol: 0-1-2-3, 0-1-3, 0-2-1-3, 0-2-3, 0-3
K5 (0 → 4), n = 5 için 1 + 3 + 6 + 6 = 16 yol: 0-1-2-3-4, 0-1-2-4, 0-1-3-2-4, 0-1-3-4, 0-1-4, 0-2-1-3-4, 0-2-1-4, 0-2-3-1-4, 0-2-3-4, 0-2-4, 0-3-1-2-4, 0-3-1-4, 0-3-2-1-4, 0-3-2-4, 0-3-4, 0-4
Dijkstra
Etkin Çözüme Doğru: Açgözlü (Greedy) Fikir
- S: en kısa yolu kesinleşmiş düğümler (başta yalnız kaynak 0).
- w ∉ S için
uzaklik[w]: 0’dan başlayıp yalnızca S’deki düğümlerden geçerek w’de biten en kısa yolun uzunluğu. uzaklik[u]en küçük olan u seçilir ve S’ye eklenir.- u’nun eklenmesi, u üzerinden geçen yeni yollar açar:
uzaklik[u] + maliyet(<u,w>) < uzaklik[w]ise uzaklik[w] günlenir (u–w arasında ara düğüm yok).
u’nun komşularına u üzerinden yeni yollar
Dijkstra
Adım Adım: v0’dan Tüm Düğümlere
S = {v0}. Komşuların uzaklıkları doğrudan kenar maliyetleri: v1 = 10, v3 = 30; diğerleri ∞.
En küçük uzaklıklı v1 (10) S’ye katılır. Güncellenen: v2 = 20.
En küçük uzaklıklı v2 (20) S’ye katılır. Güncellenen: v4 = 30.
En küçük uzaklıklı v3 (30) S’ye katılır. Güncellenen: v5 = 50, v6 = 32.
En küçük uzaklıklı v4 (30) S’ye katılır. Güncellenen: v5 = 40. v5’e v4 üzerinden daha kısa yol bulundu (50 → 40)!
En küçük uzaklıklı v6 (32) S’ye katılır. Güncelleme yok. Sıra numaraya göre değil uzaklığa göre belirlenir; 32 < 40 olduğu için önce v6 seçilir.
En küçük uzaklıklı v5 (40) S’ye katılır. Güncelleme yok.
Dijkstra
C Kodu: Maliyet Matrisi ve minimumBul
#include <limits.h> #define MAX_DUGUM 6 #define SONSUZ 1000 /* baglanti yok */ int maliyet[][MAX_DUGUM] = { { 0, 50, 10, 1000, 45, 1000}, {1000, 0, 15, 1000, 10, 1000}, { 20, 1000, 0, 15, 1000, 1000}, {1000, 20, 1000, 0, 35, 1000}, {1000, 1000, 1000, 30, 0, 1000}, /* eksik satir */ {1000, 1000, 1000, 3, 1000, 0}}; /* 100 -> 1000 */ int minimumBul(int uzaklik[], int n, short gidildi[]) { int i, min = INT_MAX, pos = -1; for (i = 0; i < n; i++) if (uzaklik[i] < min && !gidildi[i]) { min = uzaklik[i]; pos = i; } return pos; }
Ayrıntılar:
!gidildi[w]yazılmalı;!gidildi(w)diziyi fonksiyon gibi çağırır.INT_MAXiçin<limits.h>; FALSE/TRUE tanımları.minimumBul−1 dönebilir (işaretsiz düğüm kalmamışsa); çağıran denetlemeli. Erişilemeyen düğüm de seçilebilir:SONSUZ(1000) <INT_MAX.
Dijkstra
EnKisaYolBul(): Ana Yordam
void EnKisaYolBul(int v, int maliyet[][MAX_DUGUM], int uzaklik[], int onceki[], int n, short gidildi[]) { int i, u, w; for (i = 0; i < n; i++) { /* A1 */ gidildi[i] = FALSE; uzaklik[i] = maliyet[v][i]; onceki[i] = v; /* yol icin tutulur */ } gidildi[v] = TRUE; uzaklik[v] = 0; for (i = 2; i < n; i++) { /* n-2 tur */ u = minimumBul(uzaklik, n, gidildi); if (u < 0) break; /* kalan yok */ gidildi[u] = TRUE; for (w = 0; w < n; w++) if (!gidildi[w] && uzaklik[u] + maliyet[u][w] < uzaklik[w]) { uzaklik[w] = uzaklik[u] + maliyet[u][w]; onceki[w] = u; } } }
- A1: başlangıç değerleri — uzaklıklar doğrudan kenar maliyetleri.
- A2: dış döngü n − 2 tur: son kalan düğümün uzaklığı zaten kesindir.
- A3:
minimumBulO(n). - A4: tüm w’ler için gevşetme (relaxation) O(n).
onceki[]: yolu geri izlemek için tutulur.
EnKisaYolBul · Kodu adım adım çalıştır
Kod İzleme: EnKisaYolBul(0, …)
A1 sonrası: uzaklik = 0’ın satırı (1000 = ∞); onceki[] = 0. Kaynak işaretlenir.
minimumBul: işaretsizler arasında en küçük uzaklık 2 (10). Kesinleşti.
w = 3: 10 + 15 = 25 < eski değer → uzaklik[3] = 25, onceki[3] = 2.
minimumBul: işaretsizler arasında en küçük uzaklık 3 (25). Kesinleşti.
w = 1: 25 + 20 = 45 < eski değer → uzaklik[1] = 45, onceki[1] = 3.
minimumBul: işaretsizler arasında en küçük uzaklık 1 (45) — 1 ile 4 eşit (45); < olduğu için küçük indis 1 seçilir. Kesinleşti.
1 üzerinden hiçbir uzaklık kısalmadı (ör. 45 + 10 = 55 > 45).
minimumBul: işaretsizler arasında en küçük uzaklık 4 (45). Kesinleşti.
4 üzerinden hiçbir uzaklık kısalmadı (4’ten tek çıkış <4,3>, 3 zaten işaretli).
n − 2 = 4 tur bitti. Sonuç: 2 → 10, 3 → 25, 1 → 45, 4 → 45; 5’e yol yok (1000 kaldı).
gidildi[v] = TRUE; uzaklik[v] = 0; for (i = 2; i < n; i++) { /* n-2 tur */ u = minimumBul(uzaklik, n, gidildi); if (u < 0) break; /* kalan yok */ gidildi[u] = TRUE; for (w = 0; w < n; w++) if (!gidildi[w] && uzaklik[u] + maliyet[u][w] < uzaklik[w]) { uzaklik[w] = uzaklik[u] + maliyet[u][w]; onceki[w] = u; } }
Dijkstra
Analiz ve Yapıları İyileştirmek
n + n[n + n] = n + 2n² = O(n²) — sırasıyla A1, A2, A3, A4’ten.
Uzaklıklar min-heap’te: A3’teki n yerine log n. Komşuluk listesiyle A4’teki n yerine derece(v).
O(n lg n) sanmak sık yapılan bir hatadır; her gevşetme de heap’i güncellediği için doğrusu O((n + e) log n) (Fibonacci heap: O(e + n log n)).
Kaba kuvvet O(nⁿ) · matris O(n²) · heap + liste O((n + e) log n). Yoğun çizgede O(n²) sürüm hâlâ iyidir.
“Adam matristeki en büyük öğeyi bulana kadar, ben v0’dan tüm düğümlere olan en kısa yolları buluyorum.”
Öncüller dizisi · Kodu adım adım çalıştır
Kod İzleme: yol_yaz(onceki, 0, 1)
h = 1 ≠ 0: önce öncülün yolu yazılmalı → yol_yaz(…, 3).
h = 3 ≠ 0: önce öncülün yolu yazılmalı → yol_yaz(…, 2).
h = 2 ≠ 0: önce öncülün yolu yazılmalı → yol_yaz(…, 0).
h = s = 0: kaynağa ulaşıldı → 0 yazılır, dönülür.
yol_yaz(2)’e dönüldü: “ -> 2” yazılır.
yol_yaz(3)’e dönüldü: “ -> 3” yazılır.
yol_yaz(1)’e dönüldü: “ -> 1” yazılır.
Yol: 0 -> 2 -> 3 -> 1, maliyet 10 + 15 + 20 = 45. Önce hedeften geriye yürüdük, yazarken çağrı yığını sırayı düzeltti.
void yol_yaz(int onceki[], int s, int h) { if (h == s) { printf("%d", s); return; } yol_yaz(onceki, s, onceki[h]); /* once oncul */ printf(" -> %d", h); }
Dijkstra
Yolu Bulmak: Öncüller Dizisi
Her düğümün en kısa yolu, öncülünün en kısa yolu + bir kenardır (optimal alt yapı). Yollar bir ağaç oluşturur: en kısa yol ağacı.
Gevşetme başarılıysa onceki[w] = u. Maliyet değiştikçe öncül de değişir.
onceki[] ile hedeften kaynağa yürünür; özyineleme ya da yığın ile ters çevrilip yazılır.
uzaklik[h] ≥ SONSUZ ise “yol yok” yazılmalı; onceki[] anlamsızdır.
Dijkstra
Yazılım Mühendisliği Kavramları
AgirlikBul(…); diye çağırmış. Bu ne? “Hocam ne var ki orda?” Bu ne döner? “int döner.” Hani nereye dönüyor? Dönen değer havada duruyor. “Ah, ben onu unutmuşum.”” Çözümler modüler, basit, okunabilir, tekrar kullanılabilir, bakımı kolay olmalıdır. El altında bir Yazılım Mühendisliği kitabı bulunmalı:
Bu bölümdeki kodlar da bu yüzden küçük yordamlara bölündü: minimumBul, yol_yaz, kume_bul…
Negatif Kenar ve Tüm Çiftler
Negatif Kenarda Dijkstra Neden Yanılır? (ek konu)
| Düğüm | Dijkstra | Doğru |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 4 | 2 |
| 2 | 5 | 5 |
| 3 | 6 | 4 |
Dijkstra 1’i 4 ile kesinleştirir (ondan küçük aday yok). Sonra 2 → 1 kenarı (−3) 1’e 2 maliyetle ulaşır; ama 1 artık işaretlidir. 3 de yanlış kalır.
Dijkstra, seçilen düğüme daha sonra daha ucuz bir yoldan gelinemeyeceğini varsayar; bu yalnızca w ≥ 0 iken doğrudur.
Bellman-Ford: tüm kenarları n − 1 tur gevşet. Negatif döngü varsa en kısa yol tanımsızdır; n. turda hâlâ gevşeme olursa bunu yakalar.
Bellman-Ford (ek konu) · Kodu adım adım çalıştır
Kod İzleme: Bellman-Ford (kaynak 0)
d[] = ∞, kaynak d[0] = 0.
Tur 1, 0→1 (4): d[0] + 4 = 4 daha küçük → d[1] = 4.
Tur 1, 0→2 (5): d[0] + 5 = 5 daha küçük → d[2] = 5.
Tur 1, 1→3 (2): d[1] + 2 = 6 daha küçük → d[3] = 6.
Tur 1, 2→1 (-3): d[2] + (−3) = 2 daha küçük → d[1] = 2.
Tur 2, 0→1 (4): 0 + 4 = 4 ≥ d[1] = 2, değişmez.
Tur 2, 0→2 (5): 0 + 5 = 5 ≥ d[2] = 5, değişmez.
Tur 2, 1→3 (2): d[1] + 2 = 4 daha küçük → d[3] = 4.
Tur 2, 2→1 (-3): 5 + (−3) = 2 ≥ d[1] = 2, değişmez.
Tur 3: hiçbir kenar gevşemez (d zaten kesin). Genelde n − 1 tur gerekir: en kısa yol en çok n − 1 kenarlıdır.
n. turda da gevşeme yok → negatif döngü yok, return 0. Sonuç d = [0, 2, 5, 4]. Maliyet O(n·e).
for (i = 0; i < n; i++) d[i] = SONSUZ; d[s] = 0; for (t = 1; t <= n - 1; t++) /* n-1 tur */ for (j = 0; j < e; j++) { u = K[j].u; v = K[j].v; if (d[u] != SONSUZ && d[u] + K[j].w < d[v]) d[v] = d[u] + K[j].w; } for (j = 0; j < e; j++) /* n. tur */ if (d[K[j].u] != SONSUZ && d[K[j].u] + K[j].w < d[K[j].v]) return -1; return 0; /* -1: negatif dongu var */
Tüm çiftler (ek konu) · Kodu adım adım çalıştır
Kod İzleme: Floyd-Warshall
A⁻¹ = maliyet matrisi (Horowitz örneği; 2 → 1 doğrudan kenar yok).
k = 0: artık yollar 0 düğümünden (ve daha küçük numaralılardan) geçebilir.
A[2][0] + A[0][1] = 3 + 4 = 7 < ∞ → A[2][1] = 7.
k = 1: artık yollar 1 düğümünden (ve daha küçük numaralılardan) geçebilir.
A[0][1] + A[1][2] = 4 + 2 = 6 < 11 → A[0][2] = 6.
k = 2: artık yollar 2 düğümünden (ve daha küçük numaralılardan) geçebilir.
A[1][2] + A[2][0] = 2 + 3 = 5 < 6 → A[1][0] = 5.
Bitti: FA = tüm çiftler arası en kısa uzaklıklar. Üç iç içe döngü → O(n³); negatif kenar olabilir, negatif döngü olamaz.
for (i = 0; i < n; i++) for (j = 0; j < n; j++) A[i][j] = maliyet[i][j]; for (k = 0; k < n; k++) /* ara dugum k */ for (i = 0; i < n; i++) for (j = 0; j < n; j++) if (A[i][k] + A[k][j] < A[i][j]) A[i][j] = A[i][k] + A[k][j];
Negatif Kenar ve Tüm Çiftler
En Kısa Yol: Hangi Algoritma, Ne Zaman?
| Problem | Algoritma | Zaman | Koşul |
|---|---|---|---|
| Ağırlıksız, tek kaynak | BFS | O(n + e) | tüm kenarlar eşit |
| Tek kaynak, w ≥ 0 | Dijkstra (matris) | O(n²) | yoğun çizge |
| Tek kaynak, w ≥ 0 | Dijkstra (heap + liste) | O((n + e) log n) | seyrek çizge |
| Tek kaynak, negatif kenar | Bellman-Ford | O(n · e) | negatif döngüyü yakalar |
| DAG, tek kaynak | Topolojik sırada gevşetme | O(n + e) | döngü yok (aktivite ağları!) |
| Tüm çiftler | Floyd-Warshall | O(n³) | negatif döngü yok |
| Tüm çiftler, seyrek | n × Dijkstra | O(n (n + e) log n) | w ≥ 0 |
Aktivite Ağları
Aktivite Ağları: Proje = Aktiviteler
| Kod | Ders | Ön gereksinim |
|---|---|---|
| C1 | Programming I | – |
| C2 | Discrete Math. | – |
| C3 | Data Structures | C1, C2 |
| C4 | Calculus I | – |
| C5 | Calculus II | C4 |
| C6 | Linear Algebra | C5 |
| C7 | Analysis of Algorithms | C3, C6 |
| C8 | Assembly Lang. | C3 |
| C9 | Operating Systems | C7, C8 |
| C10 | Programming Langs. | C7 |
| C11 | Compiler Design | C10 |
| C12 | Artificial Intelligence | C7 |
| C13 | Computational Theory | C7 |
| C14 | Parallel Algorithms | C13 |
| C15 | Numerical Analysis | – |
Proje, alt işler (aktiviteler) bitince biter: mezuniyet = dersler. İnşaatta: temel atılır, 21 gün sonra 2. kat… Proje ne zaman biter? Hangi işler kritik? Hangilerinde bolluk var?
Aktivite Ağları
Düğümde Aktivite Ağları (AoV) ve Öncül İlişkisi
- AoV (activity on vertex/node): aktiviteler düğüm, öncül ilişkileri (precedence relation) kenar: i dersi j’nin öncülüyse <i, j>.
- C3 ve C6, C7’nin yakın öncülleridir (immediate predecessors).
- C9, C10, C12, C13, C7’nin yakın ardıllarıdır (immediate successors).
- C14, C3’ün bir ardılıdır ama yakın ardılı değildir.
- Öncül ilişkisi geçişlidir ve döngüsüz olmalıdır: A, B’nin; B de A’nın öncülü olamaz.
Aktivite Ağları
Tanım: Topolojik Sıra (Topological Order)
Soğuk Savaş yıllarında füze (Polaris) üretimini hızlandırmak için geliştirilen planlama çalışmalarına (PERT, 1958) dayanır. Derslerin iki geçerli topolojik sırası (programla sınandı):
C1, C2, C4, C5, C3, C6, C8, C7, C10, C13, C12, C14, C15, C11, C9
C4, C5, C2, C1, C6, C3, C8, C15, C7, C9, C10, C11, C12, C13, C14
Her kenar <i, j> için i’nin sıradaki yeri j’ninkinden küçük mü? 14 kenarın hepsi sağlanıyor.
Ders planı, derleme sırası (make), görev zamanlama, hesap tablosu hücreleri, en erken zamanlar.
Aktivite Ağları
Topolojik Sıra Bulmak: Öncülsüz Düğümü Sil
a) v0’ın öncülü yok → ilk seçilecek düğüm.
b) v0 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir. Ör. v3 seçilirse <v3,v4> ve <v3,v5> silinir.
c) v3 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir.
d) v2 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir.
e) v5 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir.
f) v1 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir.
g) Olası bir topolojik sıra: v0, v3, v2, v5, v1, v4.
Her seferinde öncülü olmayan düğümlerden biri alınır, yazılır, çıkan kenarları silinir; sona kadar böyle devam edilir. Öncülsüz düğüm kalmadıysa ama düğüm varsa: döngü.
Aktivite Ağları
Veri Yapısı: Başlıkta Öncül Sayacı
typedef struct oge *oge_gost; typedef struct oge { int dugum; oge_gost bag; } oge; typedef struct { int sayac; /* <i,j> icin: basi j olan kenar sayisi */ oge_gost bag; } basoge; basoge baslik[MAX_DUGUM];
- Düğümler 0..n−1 numaralı.
sayac: düğümün öncül (giriş kenarı) sayısı; 0 ise yazılabilir.- Kenarlar okunurken
baslik[j].sayac++.
v0..v5 örneğinin sayaçlı listesi
Aktivite Ağları
Topolojik Sıra Bulma Yordamı
void TopolojikSira(basoge baslik[], int n) { int i, j, k, ust = -1; oge_gost p; for (i = 0; i < n; i++) /* sayac 0: yigina */ if (!baslik[i].sayac) { baslik[i].sayac = ust; ust = i; } for (i = 0; i < n; i++) { if (ust == -1) { printf("dongu"); break; } j = ust; ust = baslik[ust].sayac; printf("%d ", j); for (p = baslik[j].bag; p; p = p->bag) { k = p->dugum; if (--baslik[k].sayac == 0) { baslik[k].sayac = ust; ust = k; } } } }
- Öncülü kalmayan düğümler
sayacalanları üzerinden peş peşe bağlanır: sayac artık “yığında altımdaki düğüm”. Kümelerdeki ebeveyn dizisine benzer. sayac--; if (!sayac)ikilisi burada--sayac == 0olarak yazıldı (aynı anlam).
TopolojikSira · Kodu adım adım çalıştır
Kod İzleme: TopolojikSira(baslik, 6)
v0’ın sayacı 0 → yığına: sayac = ust (−1: yığının dibi), ust = 0.
İlk döngü bitti: yalnız v0 öncülsüz. Diğer sayaçlar öncül sayısını tutuyor.
Yığından al: j = 0, ust = sayac[0] = -1. v0 yazılır.
<0,1>: v1’in sayacı 0’a düştü → yığına: sayac[1] = -1, ust = 1.
<0,2>: v2’nin sayacı 0’a düştü → yığına: sayac[2] = 1, ust = 2.
<0,3>: v3’ün sayacı 0’a düştü → yığına: sayac[3] = 2, ust = 3.
Yığından al: j = 3, ust = sayac[3] = 2. v3 yazılır.
<3,5>: v5’in öncül sayısı 1 azaldı → 1; henüz yazılamaz.
<3,4>: v4’ün öncül sayısı 1 azaldı → 2; henüz yazılamaz.
Yığından al: j = 2, ust = sayac[2] = 1. v2 yazılır.
<2,4>: v4’ün öncül sayısı 1 azaldı → 1; henüz yazılamaz.
for (i = 0; i < n; i++) /* sayac 0: yigina */ if (!baslik[i].sayac) { baslik[i].sayac = ust; ust = i; } for (i = 0; i < n; i++) { if (ust == -1) { printf("dongu"); break; } j = ust; ust = baslik[ust].sayac; printf("%d ", j); for (p = baslik[j].bag; p; p = p->bag) { k = p->dugum; if (--baslik[k].sayac == 0) { baslik[k].sayac = ust; ust = k; } } }
TopolojikSira · Kodu adım adım çalıştır
Kod İzleme: TopolojikSira() (devam)
<2,5>: v5’in sayacı 0’a düştü → yığına: sayac[5] = 1, ust = 5.
Yığından al: j = 5, ust = sayac[5] = 1. v5 yazılır.
Yığından al: j = 1, ust = sayac[1] = -1. v1 yazılır.
<1,4>: v4’ün sayacı 0’a düştü → yığına: sayac[4] = -1, ust = 4.
Yığından al: j = 4, ust = sayac[4] = -1. v4 yazılır.
Tüm düğümler yazıldı: 0 3 2 5 1 4. Döngü olsaydı bir turda ust == −1 görülür, “döngü var” yazılırdı.
for (i = 0; i < n; i++) /* sayac 0: yigina */ if (!baslik[i].sayac) { baslik[i].sayac = ust; ust = i; } for (i = 0; i < n; i++) { if (ust == -1) { printf("dongu"); break; } j = ust; ust = baslik[ust].sayac; printf("%d ", j); for (p = baslik[j].bag; p; p = p->bag) { k = p->dugum; if (--baslik[k].sayac == 0) { baslik[k].sayac = ust; ust = k; } } }
Aktivite Ağları
Döngü Tespiti ve Analiz
Döngü, A ve B’nin karşılıklı olarak birbirinin öncülü olmasıdır — bu bir hatadır. “Döngü varsa proje hiç bitmeyecektir.” Bir turda yığın boşsa ama düğüm kalmışsa döngü vardır.
İlk for O(n), ikinci for O(n); iç for dj kez döner (çıkış derecesi). Toplam O(Σdi + n) = O(n + e).
Yığına eklenme sırası komşuluk listesindeki sıraya bağlıdır; kuyruk kullanılsaydı (Kahn) başka ama yine geçerli bir sıra çıkardı.
e, çizgenin bağlanışına göre O(n) ile O(n²) arasında değişir.
Aktivite Ağları
Kenarda Aktivite Ağları (AoE)
v0 başlangıç, v8 bitiş: 11 aktivite (kenar), 9 olay (düğüm)
AoV’nin tersine yönlü kenarlar aktiviteleri (süreleriyle), düğümler olayları temsil eder.
Projenin tamamlanması için ulaşılması gereken ara aşama: “bitmesi gereken iş bir düğümle ifade ediliyor”.
a0, a1, a2 paralel başlar; a3 ve a4 bitince a6, a7 başlar; a10 için a7 ve a8 bitmiş olmalıdır.
Aktivite Ağları
Graph Drawing: AoV’den AoE’ye, Yapay Aktivite
AoV’de 4 aktivitesi 2 ve 3’ü bekler; AoE’de bu, maliyeti 0 olan bir yapay kenarla gösterilir.
Maliyeti 0 olan kenar. Ör. yukarıdaki projede a6 ve a7’nin a3, a4 ve a5 bitince başlaması istenseydi <v5, v4> yapay aktivitesi gerekirdi.
Çizgeyi en az sayıda yapay aktiviteyle çizme problemi: NP-zor. Üzerinde tez düzeyinde çalışmalar yapılmış bir araştırma konusudur.
PERT (Program Evaluation and Review Technique, 1958), CPM (Critical Path Method, 1950’lerin sonu), RAMPS (Resource Allocation and Multi-Project Scheduling).
Erken/Geç Zamanlar
Kritik Yol (Critical Path)
Kritik yollar: v0-v1-v4-v6-v8 ve v0-v1-v4-v7-v8 (uzunluk 18)
- Aktivite süreleri toplandığında en uzun v0 → v8 yolu: kritik yol. Birden fazla olabilir.
- Burada 6 + 1 + 9 + 2 = 18 ve 6 + 1 + 7 + 4 = 18 → proje en erken 18 günde biter.
- Bir aktivite, ön koşulları en kısa sürede biterse ne zaman başlar? Proje uzamasın diye en geç ne zaman başlamalı?
- Kritik işlerin ustalarına: “Sakın 4 günde bitirin, 5 güne sarkmasın. 3 günde biterse memnun olurum.” — yöneylem problemi.
Erken/Geç Zamanlar
En Erken Başlama Zamanları (Earliest Times)
- ai = <k, m> ise
EnErken(i) = EnErkenBas[k]: aktivite, kuyruğundaki olay gerçekleşince başlar. - Ör. a1: k = v0 → 0; a5: k = v3 → 5 (a2); a8: k = v5 → 5 + 2 = 7.
- v4’e iki yol var: a3 en erken 6 + 1 = 7’de, a4 4 + 1 = 5’te biter. İkisi de bitmeli → en uzunu (7) beklenir.
EnErkenBas[m] = max { EnErkenBas[k] + süre(<k,m>) }, k ∈ P(m) (yakın öncüller).- Başta dizi 0: projenin başlangıcı 0. günü (ör. “8 Mayıs”); 10 konsaydı hepsi 10 kayardı.
- Hesap topolojik sıraya göre yapılmalıdır: öncüllerin değerleri önce bilinmeli.
/* TopolojikSira icinde sayac azaltilmadan once: */ if (EnErkenBas[k] < EnErkenBas[j] + p->sure) EnErkenBas[k] = EnErkenBas[j] + p->sure;
Liste öğesine sure alanı eklenir; daha uzun bir yol bulununca değer günlenir.
İleri çözümleme · Kodu adım adım çalıştır
Kod İzleme: EnErkenBas[] (topolojik sırada)
Başlangıç: EnErkenBas[] = 0; ilk döngü öncülsüz v0’ı yığına koydu.
Yığından v0 alınır ve yazılır (EnErkenBas[0] = 0 artık kesin).
<0,1> (süre 6): 0 + 6 = 6 > 0 → EnErkenBas[1] = 6; v1 yığına girdi.
<0,2> (süre 4): 0 + 4 = 4 > 0 → EnErkenBas[2] = 4; v2 yığına girdi.
<0,3> (süre 5): 0 + 5 = 5 > 0 → EnErkenBas[3] = 5; v3 yığına girdi.
Yığından v3 alınır ve yazılır (EnErkenBas[3] = 5 artık kesin).
<3,5> (süre 2): 5 + 2 = 7 > 0 → EnErkenBas[5] = 7; v5 yığına girdi.
Yığından v5 alınır ve yazılır (EnErkenBas[5] = 7 artık kesin).
<5,7> (süre 4): 7 + 4 = 11 > 0 → EnErkenBas[7] = 11.
Yığından v2 alınır ve yazılır (EnErkenBas[2] = 4 artık kesin).
<2,4> (süre 1): 4 + 1 = 5 > 0 → EnErkenBas[4] = 5.
j = ust; ust = baslik[ust].sayac; /* yigindan al */ printf("%d ", j); for (p = baslik[j].bag; p; p = p->bag) { k = p->dugum; if (EnErkenBas[k] < EnErkenBas[j] + p->sure) EnErkenBas[k] = EnErkenBas[j] + p->sure; if (--baslik[k].sayac == 0) { baslik[k].sayac = ust; ust = k; /* yigina */ } }
İleri çözümleme · Kodu adım adım çalıştır
Kod İzleme: EnErkenBas[] (devam)
Yığından v1 alınır ve yazılır (EnErkenBas[1] = 6 artık kesin).
<1,4> (süre 1): 6 + 1 = 7 > 5 → EnErkenBas[4] = 7; v4 yığına girdi.
Yığından v4 alınır ve yazılır (EnErkenBas[4] = 7 artık kesin).
<4,6> (süre 9): 7 + 9 = 16 > 0 → EnErkenBas[6] = 16; v6 yığına girdi.
<4,7> (süre 7): 7 + 7 = 14 > 11 → EnErkenBas[7] = 14; v7 yığına girdi.
Yığından v7 alınır ve yazılır (EnErkenBas[7] = 14 artık kesin).
<7,8> (süre 4): 14 + 4 = 18 > 0 → EnErkenBas[8] = 18.
Yığından v6 alınır ve yazılır (EnErkenBas[6] = 16 artık kesin).
<6,8> (süre 2): 16 + 2 = 18 ≤ 18, değişmez; v8 yığına girdi.
Yığından v8 alınır ve yazılır (EnErkenBas[8] = 18 artık kesin).
j = ust; ust = baslik[ust].sayac; /* yigindan al */ printf("%d ", j); for (p = baslik[j].bag; p; p = p->bag) { k = p->dugum; if (EnErkenBas[k] < EnErkenBas[j] + p->sure) EnErkenBas[k] = EnErkenBas[j] + p->sure; if (--baslik[k].sayac == 0) { baslik[k].sayac = ust; ust = k; /* yigina */ } }
Erken/Geç Zamanlar
En Erken Başlama Zamanı Hesaplama Tablosu
| EnErkenBas | [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] | [8] | Yığın |
|---|---|---|---|---|---|---|---|---|---|---|
| Başlangıç | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Çıktı v0 | 0 | 6 | 4 | 5 | 0 | 0 | 0 | 0 | 0 | 3, 2, 1 |
| Çıktı v3 | 0 | 6 | 4 | 5 | 0 | 7 | 0 | 0 | 0 | 5, 2, 1 |
| Çıktı v5 | 0 | 6 | 4 | 5 | 0 | 7 | 0 | 11 | 0 | 2, 1 |
| Çıktı v2 | 0 | 6 | 4 | 5 | 5 | 7 | 0 | 11 | 0 | 1 |
| Çıktı v1 | 0 | 6 | 4 | 5 | 7 | 7 | 0 | 11 | 0 | 4 |
| Çıktı v4 | 0 | 6 | 4 | 5 | 7 | 7 | 16 | 14 | 0 | 7, 6 |
| Çıktı v7 | 0 | 6 | 4 | 5 | 7 | 7 | 16 | 14 | 18 | 6 |
| Çıktı v6 | 0 | 6 | 4 | 5 | 7 | 7 | 16 | 14 | 18 | 8 |
| Çıktı v8 | 0 | 6 | 4 | 5 | 7 | 7 | 16 | 14 | 18 | – |
Erken/Geç Zamanlar
En Geç Başlama Zamanları (Latest Times)
- Geri doğru çözümleme: tüm oklar ters çevrilir; bu çizgenin topolojik sırasında çalışılır (v8 ilk, v0 son). Yapı: komşuluk listesinin tersi, O(n + e)’de kurulur.
- Son düğüm:
EnGecBas[n−1] = EnErkenBas[n−1]= 18. Başta dizi 18 ile doldurulur. - ai = <k, m>:
EnGec(i) = EnGecBas[m] − süre. v7 en geç 18 − 4 = 14, v6 en geç 18 − 2 = 16. - Birden çok ardıl varsa en küçüğü:
EnGecBas[k] = min { EnGecBas[m] − süre }. Ör. v0: v1 için 6 − 6 = 0, v2 için 6 − 4 = 2, v3 için 8 − 5 = 3 → 0. Büyüğünü (3) alsaydık v1 gecikir, proje 21’e sarkardı.
if (EnGecBas[k] > EnGecBas[j] - p->sure)
EnGecBas[k] = EnGecBas[j] - p->sure;Geri çözümleme · Kodu adım adım çalıştır
Kod İzleme: EnGecBas[] (ters çizgede)
Başlangıç: EnGecBas[] = 18; ters çizgede öncülsüz tek düğüm v8 yığında.
v8 alınır (EnGecBas[8] = 18 kesin). Öncüller: EnGecBas[6] = 16, EnGecBas[7] = 14. Yığına: v6, v7.
v7 alınır (EnGecBas[7] = 14 kesin). Öncüller: EnGecBas[4] = 7, EnGecBas[5] = 10. Yığına: v5.
v5 alınır (EnGecBas[5] = 10 kesin). Öncüller: EnGecBas[3] = 8. Yığına: v3.
v3 alınır (EnGecBas[3] = 8 kesin). Öncüller: EnGecBas[0] = 3.
v6 alınır (EnGecBas[6] = 16 kesin). [4] değişmez. Yığına: v4.
v4 alınır (EnGecBas[4] = 7 kesin). Öncüller: EnGecBas[1] = 6, EnGecBas[2] = 6. Yığına: v1, v2.
v2 alınır (EnGecBas[2] = 6 kesin). Öncüller: EnGecBas[0] = 2 (değişti).
v1 alınır (EnGecBas[1] = 6 kesin). Öncüller: EnGecBas[0] = 0 (değişti). Yığına: v0.
v0 alınır (EnGecBas[0] = 0 kesin). Öncülü yok (başlangıç olayı).
j = ust; ust = baslik[ust].sayac; /* ters liste */ printf("%d ", j); for (p = baslik[j].bag; p; p = p->bag) { k = p->dugum; /* k: oncul */ if (EnGecBas[k] > EnGecBas[j] - p->sure) EnGecBas[k] = EnGecBas[j] - p->sure; if (--baslik[k].sayac == 0) { baslik[k].sayac = ust; ust = k; } }
Erken/Geç Zamanlar
En Geç Başlama Zamanı Hesaplama Tablosu
| EnGecBas | [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] | [8] | Yığın |
|---|---|---|---|---|---|---|---|---|---|---|
| Başlangıç | 18 | 18 | 18 | 18 | 18 | 18 | 18 | 18 | 18 | 8 |
| Çıktı v8 | 18 | 18 | 18 | 18 | 18 | 18 | 16 | 14 | 18 | 7, 6 |
| Çıktı v7 | 18 | 18 | 18 | 18 | 7 | 10 | 16 | 14 | 18 | 5, 6 |
| Çıktı v5 | 18 | 18 | 18 | 8 | 7 | 10 | 16 | 14 | 18 | 3, 6 |
| Çıktı v3 | 3 | 18 | 18 | 8 | 7 | 10 | 16 | 14 | 18 | 6 |
| Çıktı v6 | 3 | 18 | 18 | 8 | 7 | 10 | 16 | 14 | 18 | 4 |
| Çıktı v4 | 3 | 6 | 6 | 8 | 7 | 10 | 16 | 14 | 18 | 2, 1 |
| Çıktı v2 | 2 | 6 | 6 | 8 | 7 | 10 | 16 | 14 | 18 | 1 |
| Çıktı v1 | 0 | 6 | 6 | 8 | 7 | 10 | 16 | 14 | 18 | 0 |
| Çıktı v0 | 0 | 6 | 6 | 8 | 7 | 10 | 16 | 14 | 18 | – |
Erken/Geç Zamanlar
Bolluklar ve Kritik Aktiviteler
| Akt. | Kenar | Süre | En erken | En geç | Bolluk |
|---|---|---|---|---|---|
| a0 | (0,1) | 6 | 0 | 0 | 0: kritik |
| a1 | (0,2) | 4 | 0 | 2 | 2 |
| a2 | (0,3) | 5 | 0 | 3 | 3 |
| a3 | (1,4) | 1 | 6 | 6 | 0: kritik |
| a4 | (2,4) | 1 | 4 | 6 | 2 |
| a5 | (3,5) | 2 | 5 | 8 | 3 |
| a6 | (4,6) | 9 | 7 | 7 | 0: kritik |
| a7 | (4,7) | 7 | 7 | 7 | 0: kritik |
| a8 | (5,7) | 4 | 7 | 10 | 3 |
| a9 | (6,8) | 2 | 16 | 16 | 0: kritik |
| a10 | (7,8) | 4 | 14 | 14 | 0: kritik |
kalın kırmızı kenar: bolluk 0 (kritik)
- Bolluk = en geç − en erken (ai = <k,m>: EnGecBas[m] − süre − EnErkenBas[k]). Ör. a4: en erken 4, en geç 6 → 2 gün gecikebilir.
- Bolluğu 0 olan kritik aktivite: en erken vaktinde başlamak zorunda. Project yazılımları bolluk dağılımını grafikle gösterir.
- Bu toplam bolluktur (total float): a2-a5-a8 yolundaki 3 günlük bolluk paylaşılır; a2 veya a5 kullanırsa a8 kritik olur.
Erken/Geç Zamanlar
Kritik Yolu Program Bulsun (ek konu)
/* EnErkenBas[] ve EnGecBas[] hesaplandiktan sonra */ for (i = 0; i < n; i++) for (p = baslik[i].bag; p; p = p->bag) { int erken = EnErkenBas[i]; int gec = EnGecBas[p->dugum] - p->sure; printf("<%d,%d> erken=%d gec=%d bolluk=%d%s\n", i, p->dugum, erken, gec, gec - erken, gec == erken ? " KRITIK" : ""); }
- Kritik aktiviteler v0’dan v8’e uzanan bir (ya da birkaç) yol oluşturur: kritik yol.
- Tüm hesap: ileri çözümleme O(n + e) + transpoz liste O(n + e) + geri çözümleme O(n + e) = O(n + e).
- DAG’de “en uzun yol” topolojik sırada kolaydır; genel çizgede en uzun basit yol NP-zordur.
- Serbest bolluk (free float): ardılların en erken zamanını geciktirmeden kullanılabilen bolluk; a2 ve a5 için 0, a8 için 3.
Erken/Geç Zamanlar
Sınav Sorusu: En Erken / En Geç Zamanları İşletin
- Topolojik sırayı ve her adımda yığını yazın.
- EnErkenBas[] ve EnGecBas[] tablolarını doldurun.
- Her aktivitenin bolluğunu bulun; kritik yolu belirtin.
Çözüm
EnErkenBas = [0, 3, 2, 7, 5, 10] · EnGecBas = [0, 4, 2, 8, 5, 10]. Proje en erken 10 günde biter.
Bolluklar: a0=1, a1=0, a2=1, a3=4, a4=0, a5=1, a6=0. Kritik aktiviteler: a1, a4, a6 → kritik yol v0-v2-v4-v5.
Laboratuvar · C kodu adım adım
Laboratuvar: Dijkstra
Özet
Karmaşıklık Özeti
| Algoritma | Yapı | Zaman |
|---|---|---|
| Kaba kuvvet (tek hedef) | tüm yollar | O(nⁿ) |
| Dijkstra (temel) | maliyet matrisi + diziler | O(n²) |
| Dijkstra (iyileştirilmiş) | min-heap + komşuluk listesi | O((n + e) log n) |
| Yolu yazdırma | öncüller dizisi | O(yol uzunluğu) |
| Bellman-Ford | kenar listesi | O(n · e) |
| Floyd-Warshall | n × n matris | O(n³) |
| Topolojik sıra | sayaçlı liste + gömülü yığın | O(n + e) |
| En erken / en geç zaman, bolluk | topolojik sıra + ters liste | O(n + e) |
Özet
Sık Yapılan Hatalar
uzaklik[u] + maliyet toplamında taşma yaşamak.Özet
Alıştırmalar
- Yönlü örnek çizgede kaynak 2 olsaydı EnKisaYolBul hangi uzaklıkları bulurdu?
- EnKisaYolBul’un dış döngüsü neden n − 2 tur döner? n − 1 tur dönse ne olurdu?
- Adım adım örnekteki (v0…v6) çizgede v3–v6 kenarı 2 yerine 15 olsaydı S’ye katılma sırası ne olurdu?
- Topolojik sıra yordamında yığın yerine kuyruk kullanılsaydı v0..v5 örneğinde çıktı ne olurdu?
- AoE örneğinde a8’in süresi 4 yerine 8 olsaydı proje süresi ve kritik yol ne olurdu?
- Floyd-Warshall örneğinde A[1][0] neden k = 2 turunda değişti?
Cevaplar / ipuçları
- d(2→0)=20, d(2→3)=15, d(2→1)=35 (3 üzerinden), d(2→4)=45 (1 üzerinden), 5’e yol yok.
- Son kalan düğüm seçildiğinde güncellenecek işaretsiz düğüm kalmaz; bir tur daha zararsız ama gereksiz.
- v1 (10), v2 (20), v3 ve v4 (ikisi 30; eşitlikte küçük indis önce: v3, v4), sonra v5 (40), v6 (45).
- Kuyrukla (Kahn): 0 1 2 3 5 4 (v3’ün listesinde önce v5, sonra v4 var).
- v7’ye v5 üzerinden 7 + 8 = 15 > 14 → EnErken[7] = 15, proje 19; kritik yol v0-v3-v5-v7-v8 (5 + 2 + 8 + 4).
- k = 2: 1 → 2 → 0 yolu 2 + 3 = 5 < 6.
Özet
Bölüm Özeti
Kaba kuvvet O(nⁿ); Dijkstra açgözlüdür, w ≥ 0 ister: O(n²) ya da O((n + e) log n).
Öncüller dizisi tek başına yeter; tersten (özyineleme/yığın) yazdırılır.
Negatif kenar: Bellman-Ford O(ne). Tüm çiftler: Floyd-Warshall O(n³).
Sayaç + gömülü yığın; O(n + e); döngüyü de yakalar.
İleri: max ile en erken; geri (ters çizge): min ile en geç. Bolluk = geç − erken.
Bolluğu 0 olan aktiviteler; projenin süresini belirler.
Özet
Kaynaklar
- E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, 2. baskı, §6.4 Shortest Paths, §6.5 Activity Networks.
- T. H. Cormen ve ark., Introduction to Algorithms, 4. baskı, Bölüm 20.4 (Topological Sort), 22 (Single-Source Shortest Paths), 23 (All-Pairs).
- R. Sedgewick, Algorithms in C, Part 5, Bölüm 21 (Shortest Paths).