Bölüm 18: En Kısa Yol ve Aktivite Ağları

C ile Veri Yapıları ve Algoritmalar
Kaba kuvvetDijkstraÖncüller dizisiBellman-FordFloyd-WarshallTopolojik sıraKritik yol
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Modellemek

Şehir/demiryolu ve ağ gecikmesi problemlerini ağırlıklı yönlü çizge olarak kurmak.

Dijkstra

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.

Genişletmek

Negatif kenarda Bellman-Ford’u, tüm çiftler için Floyd-Warshall’ı uygulamak.

Topolojik sıra

AoV ağlarında öncül ilişkisine uyan doğrusal sırayı sayaç + yığın ile bulmak, döngüyü yakalamak.

Zaman hesabı

AoE ağlarında en erken/en geç başlama zamanlarını ileri ve geri çözümlemeyle hesaplamak.

Kritik yol

Bolluğu (slack) bulup kritik aktiviteleri ve kritik yolu belirlemek.

En Kısa Yol

En Kısa Yol Problemi (Shortest Path)

501045151020152035303014235

Horowitz–Sahni örneği

KaynakHedefMaliyet
05Yol yok!
0210
0325
0145
0445
  • 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

012301234

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

Tam çizgede yol sayısı: 1 + (n−2) + (n−2)(n−3) + … + (n−2)! ≈ 1 + O(n) + O(n²) + … ≈ O(nⁿ) (kaba ama doğru modelleme). Tek bir hedef için bile!
Hocadan: “Görüldüğü gibi kaba kuvvet yöntemiyle tek bir kaynaktan tek bir hedefe en kısa yolu bulurken bile karmaşıklık O(nⁿ) oldu… Elbette mantık doğru, ama böyle çözmeyeceğiz.”

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).
uw1w2w3S’ye yeni katıldı

u’nun komşularına u üzerinden yeni yollar

Bu, Edsger Dijkstra’nın (1959) algoritmasıdır. Kenar ağırlıkları negatif olmamalıdır (bkz. Bellman-Ford).

Dijkstra

Adım Adım: v0’dan Tüm Düğümlere

101010103020210v0v1v2v4v5v3v6gidildiuzaklıkv0T0v1F10v2F∞v3F30v4F∞v5F∞v6F∞

S = {v0}. Komşuların uzaklıkları doğrudan kenar maliyetleri: v1 = 10, v3 = 30; diğerleri ∞.

101010103020210v0v1v2v4v5v3v6gidildiuzaklıkv0T0v1T10v2F20v3F30v4F∞v5F∞v6F∞

En küçük uzaklıklı v1 (10) S’ye katılır. Güncellenen: v2 = 20.

101010103020210v0v1v2v4v5v3v6gidildiuzaklıkv0T0v1T10v2T20v3F30v4F30v5F∞v6F∞

En küçük uzaklıklı v2 (20) S’ye katılır. Güncellenen: v4 = 30.

101010103020210v0v1v2v4v5v3v6gidildiuzaklıkv0T0v1T10v2T20v3T30v4F30v5F50v6F32

En küçük uzaklıklı v3 (30) S’ye katılır. Güncellenen: v5 = 50, v6 = 32.

101010103020210v0v1v2v4v5v3v6gidildiuzaklıkv0T0v1T10v2T20v3T30v4T30v5F40v6F32

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

101010103020210v0v1v2v4v5v3v6gidildiuzaklıkv0T0v1T10v2T20v3T30v4T30v5F40v6T32

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.

101010103020210v0v1v2v4v5v3v6gidildiuzaklıkv0T0v1T10v2T20v3T30v4T30v5T40v6T32

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_MAX iç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: minimumBul O(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, …)

501045151020152035303014235uzaklik001502103∞4455∞gidildiTFFFFFonceki000000

A1 sonrası: uzaklik = 0’ın satırı (1000 = ∞); onceki[] = 0. Kaynak işaretlenir.

501045151020152035303014235uzaklik001502103∞4455∞gidildiTFTFFFonceki000000

minimumBul: işaretsizler arasında en küçük uzaklık 2 (10). Kesinleşti.

501045151020152035303014235uzaklik001502103254455∞gidildiTFTFFFonceki000200

w = 3: 10 + 15 = 25 < eski değer → uzaklik[3] = 25, onceki[3] = 2.

501045151020152035303014235uzaklik001502103254455∞gidildiTFTTFFonceki000200

minimumBul: işaretsizler arasında en küçük uzaklık 3 (25). Kesinleşti.

501045151020152035303014235uzaklik001452103254455∞gidildiTFTTFFonceki030200

w = 1: 25 + 20 = 45 < eski değer → uzaklik[1] = 45, onceki[1] = 3.

501045151020152035303014235uzaklik001452103254455∞gidildiTTTTFFonceki030200

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.

501045151020152035303014235uzaklik001452103254455∞gidildiTTTTFFonceki030200

1 üzerinden hiçbir uzaklık kısalmadı (ör. 45 + 10 = 55 > 45).

501045151020152035303014235uzaklik001452103254455∞gidildiTTTTTFonceki030200

minimumBul: işaretsizler arasında en küçük uzaklık 4 (45). Kesinleşti.

501045151020152035303014235uzaklik001452103254455∞gidildiTTTTTFonceki030200

4 üzerinden hiçbir uzaklık kısalmadı (4’ten tek çıkış <4,3>, 3 zaten işaretli).

501045151020152035303014235uzaklik001452103254455∞gidildiTTTTTFonceki030200

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

Kitaptaki algoritma

n + n[n + n] = n + 2n² = O(n²) — sırasıyla A1, A2, A3, A4’ten.

Min öncelikli kuyruk

Uzaklıklar min-heap’te: A3’teki n yerine log n. Komşuluk listesiyle A4’teki n yerine derece(v).

İyileştirilmiş toplam

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

Üç algoritma

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.

Hocadan: “Minimum öncelikli kuyruğu öğrendikten sonra dizide en küçük öğeyi aramak ne kadar hantal kalıyor değil mi? Ne kadar abes! Ne kadar ayıp!”

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

501045151020152035303014235onceki[]0013203240501 ← 3 ← 2 ← 0 (tersten izlenir)

h = 1 ≠ 0: önce öncülün yolu yazılmalı → yol_yaz(…, 3).

501045151020152035303014235onceki[]0013203240501 ← 3 ← 2 ← 0 (tersten izlenir)

h = 3 ≠ 0: önce öncülün yolu yazılmalı → yol_yaz(…, 2).

501045151020152035303014235onceki[]0013203240501 ← 3 ← 2 ← 0 (tersten izlenir)

h = 2 ≠ 0: önce öncülün yolu yazılmalı → yol_yaz(…, 0).

501045151020152035303014235onceki[]0013203240501 ← 3 ← 2 ← 0 (tersten izlenir)

h = s = 0: kaynağa ulaşıldı → 0 yazılır, dönülür.

501045151020152035303014235onceki[]0013203240501 ← 3 ← 2 ← 0 (tersten izlenir)

yol_yaz(2)’e dönüldü: “ -> 2” yazılır.

501045151020152035303014235onceki[]0013203240501 ← 3 ← 2 ← 0 (tersten izlenir)

yol_yaz(3)’e dönüldü: “ -> 3” yazılır.

501045151020152035303014235onceki[]0013203240501 ← 3 ← 2 ← 0 (tersten izlenir)

yol_yaz(1)’e dönüldü: “ -> 1” yazılır.

501045151020152035303014235onceki[]0013203240501 ← 3 ← 2 ← 0 (tersten izlenir)

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

Hocadan: “Şimdi maliyeti bulduk da yolu nasıl bulacağız? Maliyet bulundu, sıra yolda! … Hangi yoldan gidileceği belli olmadığından ileri doğru (forward) çözülemez. Tek bir diziyle çözülebilir: öncüller dizisi… Tersten kullanılacak.”
Neden tek dizi yeter?

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

Güncelleme

Gevşetme başarılıysa onceki[w] = u. Maliyet değiştikçe öncül de değişir.

Tersten kullanmak

onceki[] ile hedeften kaynağa yürünür; özyineleme ya da yığın ile ters çevrilip yazılır.

Erişilemeyen düğüm

uzaklik[h] ≥ SONSUZ ise “yol yok” yazılmalı; onceki[] anlamsızdır.

Dijkstra

Yazılım Mühendisliği Kavramları

Hocadan: “Vizedeki soruyu tek bir özyinelemeli yordamla çözmeye çalışanlar olmuş… Bazıları da yordamı 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ı:

Reliability
Usability
Maintainability
Portability
Integrity
Flexibility
Testability
Efficiency
Interoperability
Understandability
Modularity
Traceability
Modifiability
Correctness
Expandability

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)

452-30123
DüğümDijkstraDoğru
000
142
255
364

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.

Varsayım

Dijkstra, seçilen düğüme daha sonra daha ucuz bir yoldan gelinemeyeceğini varsayar; bu yalnızca w ≥ 0 iken doğrudur.

Çözüm

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)

452-30123d[]001∞2∞3∞K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

d[] = ∞, kaynak d[0] = 0.

452-30123d[]00142∞3∞K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

Tur 1, 0→1 (4): d[0] + 4 = 4 daha küçük → d[1] = 4.

452-30123d[]0014253∞K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

Tur 1, 0→2 (5): d[0] + 5 = 5 daha küçük → d[2] = 5.

452-30123d[]00142536K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

Tur 1, 1→3 (2): d[1] + 2 = 6 daha küçük → d[3] = 6.

452-30123d[]00122536K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

Tur 1, 2→1 (-3): d[2] + (−3) = 2 daha küçük → d[1] = 2.

452-30123d[]00122536K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

Tur 2, 0→1 (4): 0 + 4 = 4 ≥ d[1] = 2, değişmez.

452-30123d[]00122536K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

Tur 2, 0→2 (5): 0 + 5 = 5 ≥ d[2] = 5, değişmez.

452-30123d[]00122534K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

Tur 2, 1→3 (2): d[1] + 2 = 4 daha küçük → d[3] = 4.

452-30123d[]00122534K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

Tur 2, 2→1 (-3): 5 + (−3) = 2 ≥ d[1] = 2, değişmez.

452-30123d[]00122534K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

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.

452-30123d[]00122534K[] (kenar listesi, bu sırayla)0→1 (4)0→2 (5)1→3 (2)2→1 (-3)

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

411623012A (k = −1)01200411160223∞0

A⁻¹ = maliyet matrisi (Horowitz örneği; 2 → 1 doğrudan kenar yok).

411623012A (k = 0)01200411160223∞0

k = 0: artık yollar 0 düğümünden (ve daha küçük numaralılardan) geçebilir.

411623012A (k = 0)0120041116022370

A[2][0] + A[0][1] = 3 + 4 = 7 < ∞ → A[2][1] = 7.

411623012A (k = 1)0120041116022370

k = 1: artık yollar 1 düğümünden (ve daha küçük numaralılardan) geçebilir.

411623012A (k = 1)012004616022370

A[0][1] + A[1][2] = 4 + 2 = 6 < 11 → A[0][2] = 6.

411623012A (k = 2)012004616022370

k = 2: artık yollar 2 düğümünden (ve daha küçük numaralılardan) geçebilir.

411623012A (k = 2)012004615022370

A[1][2] + A[2][0] = 2 + 3 = 5 < 6 → A[1][0] = 5.

411623012A (k = 2), sonuç012004615022370

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?

ProblemAlgoritmaZamanKoşul
Ağırlıksız, tek kaynakBFSO(n + e)tüm kenarlar eşit
Tek kaynak, w ≥ 0Dijkstra (matris)O(n²)yoğun çizge
Tek kaynak, w ≥ 0Dijkstra (heap + liste)O((n + e) log n)seyrek çizge
Tek kaynak, negatif kenarBellman-FordO(n · e)negatif döngüyü yakalar
DAG, tek kaynakTopolojik sırada gevşetmeO(n + e)döngü yok (aktivite ağları!)
Tüm çiftlerFloyd-WarshallO(n³)negatif döngü yok
Tüm çiftler, seyrekn × DijkstraO(n (n + e) log n)w ≥ 0

Aktivite Ağları

Aktivite Ağları: Proje = Aktiviteler

KodDersÖn gereksinim
C1Programming I–
C2Discrete Math.–
C3Data StructuresC1, C2
C4Calculus I–
C5Calculus IIC4
C6Linear AlgebraC5
C7Analysis of AlgorithmsC3, C6
C8Assembly Lang.C3
C9Operating SystemsC7, C8
C10Programming Langs.C7
C11Compiler DesignC10
C12Artificial IntelligenceC7
C13Computational TheoryC7
C14Parallel AlgorithmsC13
C15Numerical Analysis–
C1C2C3C8C9C7C10C11C12C13C14C4C5C6C15

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.
C1C2C3C8C9C7C10C11C12C13C14C4C5C6C15
Hocadan: “Satır arası: Yöneylem, sayısal analiz gibi derslerin kalkmış olması öğrencilerin matematiksel olarak zayıf kalmasına yol açıyor.”

Aktivite Ağları

Tanım: Topolojik Sıra (Topological Order)

Düğümlerin öyle bir doğrusal sırası ki, i, j’nin öncülüyse sırada i, j’den önce gelir. Topolojik sıra birden çok olabilir; çizgede döngü varsa hiç yoktur.

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

Sağlama

Her kenar <i, j> için i’nin sıradaki yeri j’ninkinden küçük mü? 14 kenarın hepsi sağlanıyor.

Kullanım

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

v0v1v2v3v4v5sıra: —öncülü kalmayan: v0

a) v0’ın öncülü yok → ilk seçilecek düğüm.

v0v1v2v3v4v5sıra: v0öncülü kalmayan: v1, v2, v3

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.

v0v1v2v3v4v5sıra: v0, v3öncülü kalmayan: v1, v2

c) v3 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir.

v0v1v2v3v4v5sıra: v0, v3, v2öncülü kalmayan: v1, v5

d) v2 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir.

v0v1v2v3v4v5sıra: v0, v3, v2, v5öncülü kalmayan: v1

e) v5 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir.

v0v1v2v3v4v5sıra: v0, v3, v2, v5, v1öncülü kalmayan: v4

f) v1 yazıldı, çıkan kenarları silindi. Öncülü kalmayanlardan (turuncu) herhangi biri seçilebilir.

v0v1v2v3v4v5sıra: v0, v3, v2, v5, v1, v4öncülü kalmayan: —

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++.
v00123v114v2145v3154v43v52sayacbag

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; }
        }
    }
}
Hocadan: “Ortada yığın falan yok, ama “yığına ekle”, “yığından al” diye açıklama satırları yazdık. Açıklama satırları işte bu yüzden önemlidir. Yığın kullanıyor da nasıl kullanıyor?”
  • Öncülü kalmayan düğümler sayac alanları ü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 == 0 olarak yazıldı (aynı anlam).

TopolojikSira · Kodu adım adım çalıştır

Kod İzleme: TopolojikSira(baslik, 6)

012345baslik[].sayacv0-1v11v21v31v43v52ust = 0yığın (üst solda): v0

v0’ın sayacı 0 → yığına: sayac = ust (−1: yığının dibi), ust = 0.

012345baslik[].sayacv0-1v11v21v31v43v52ust = 0yığın (üst solda): v0

İlk döngü bitti: yalnız v0 öncülsüz. Diğer sayaçlar öncül sayısını tutuyor.

012345baslik[].sayacv0-1v11v21v31v43v52ust = -1yığın (üst solda): boş

Yığından al: j = 0, ust = sayac[0] = -1. v0 yazılır.

012345baslik[].sayacv0-1v1-1v21v31v43v52ust = 1yığın (üst solda): v1

<0,1>: v1’in sayacı 0’a düştü → yığına: sayac[1] = -1, ust = 1.

012345baslik[].sayacv0-1v1-1v21v31v43v52ust = 2yığın (üst solda): v2, v1

<0,2>: v2’nin sayacı 0’a düştü → yığına: sayac[2] = 1, ust = 2.

012345baslik[].sayacv0-1v1-1v21v32v43v52ust = 3yığın (üst solda): v3, v2, v1

<0,3>: v3’ün sayacı 0’a düştü → yığına: sayac[3] = 2, ust = 3.

012345baslik[].sayacv0-1v1-1v21v32v43v52ust = 2yığın (üst solda): v2, v1

Yığından al: j = 3, ust = sayac[3] = 2. v3 yazılır.

012345baslik[].sayacv0-1v1-1v21v32v43v51ust = 2yığın (üst solda): v2, v1

<3,5>: v5’in öncül sayısı 1 azaldı → 1; henüz yazılamaz.

012345baslik[].sayacv0-1v1-1v21v32v42v51ust = 2yığın (üst solda): v2, v1

<3,4>: v4’ün öncül sayısı 1 azaldı → 2; henüz yazılamaz.

012345baslik[].sayacv0-1v1-1v21v32v42v51ust = 1yığın (üst solda): v1

Yığından al: j = 2, ust = sayac[2] = 1. v2 yazılır.

012345baslik[].sayacv0-1v1-1v21v32v41v51ust = 1yığın (üst solda): v1

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

012345baslik[].sayacv0-1v1-1v21v32v41v51ust = 5yığın (üst solda): v5, v1

<2,5>: v5’in sayacı 0’a düştü → yığına: sayac[5] = 1, ust = 5.

012345baslik[].sayacv0-1v1-1v21v32v41v51ust = 1yığın (üst solda): v1

Yığından al: j = 5, ust = sayac[5] = 1. v5 yazılır.

012345baslik[].sayacv0-1v1-1v21v32v41v51ust = -1yığın (üst solda): boş

Yığından al: j = 1, ust = sayac[1] = -1. v1 yazılır.

012345baslik[].sayacv0-1v1-1v21v32v4-1v51ust = 4yığın (üst solda): v4

<1,4>: v4’ün sayacı 0’a düştü → yığına: sayac[4] = -1, ust = 4.

012345baslik[].sayacv0-1v1-1v21v32v4-1v51ust = -1yığın (üst solda): boş

Yığından al: j = 4, ust = sayac[4] = -1. v4 yazılır.

012345baslik[].sayacv0-1v1-1v21v32v4-1v51ust = -1yığın (üst solda): boş

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üyü de bulur

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.

Analiz

İlk for O(n), ikinci for O(n); iç for dj kez döner (çıkış derecesi). Toplam O(Σdi + n) = O(n + e).

Sıra neye bağlı?

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

Çizgenin yapısı

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)

a0=6a1=4a2=5a3=1a4=1a5=2a6=9a7=7a8=4a9=2a10=4v0v1v2v3v4v5v6v7v8

v0 başlangıç, v8 bitiş: 11 aktivite (kenar), 9 olay (düğüm)

AoE (activity on edge/arc)

AoV’nin tersine yönlü kenarlar aktiviteleri (süreleriyle), düğümler olayları temsil eder.

Olay (event)

Projenin tamamlanması için ulaşılması gereken ara aşama: “bitmesi gereken iş bir düğümle ifade ediliyor”.

Paralellik

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

12341234yapay kenar (dummy arc), süre 0AoV: 4, hem 2’yi hem 3’ü bekler

AoV’de 4 aktivitesi 2 ve 3’ü bekler; AoE’de bu, maliyeti 0 olan bir yapay kenarla gösterilir.

Yapay aktivite (dummy activity)

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.

Minimum arc problem

Çizgeyi en az sayıda yapay aktiviteyle çizme problemi: NP-zor. Üzerinde tez düzeyinde çalışmalar yapılmış bir araştırma konusudur.

Yöntemler

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)

a0=6a1=4a2=5a3=1a4=1a5=2a6=9a7=7a8=4a9=2a10=4v0v1v2v3v4v5v6v7v8

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.
Hocadan: “2. vizede en erken ve en geç başlama zamanı hesaplamayı sordum, yine sorarım. Bunu veririm, “İşletin.” derim. Anlaşıldığı zannedilir, ama %80’i işletemez.”

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;
a_ikm

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)

64511297424012345678EnErkenBas[]001020304050607080yığın (üst solda): 0

Başlangıç: EnErkenBas[] = 0; ilk döngü öncülsüz v0’ı yığına koydu.

64511297424012345678EnErkenBas[]001020304050607080yığın (üst solda): boş

Yığından v0 alınır ve yazılır (EnErkenBas[0] = 0 artık kesin).

64511297424012345678EnErkenBas[]001620304050607080yığın (üst solda): 1

<0,1> (süre 6): 0 + 6 = 6 > 0 → EnErkenBas[1] = 6; v1 yığına girdi.

64511297424012345678EnErkenBas[]001624304050607080yığın (üst solda): 2, 1

<0,2> (süre 4): 0 + 4 = 4 > 0 → EnErkenBas[2] = 4; v2 yığına girdi.

64511297424012345678EnErkenBas[]001624354050607080yığın (üst solda): 3, 2, 1

<0,3> (süre 5): 0 + 5 = 5 > 0 → EnErkenBas[3] = 5; v3 yığına girdi.

64511297424012345678EnErkenBas[]001624354050607080yığın (üst solda): 2, 1

Yığından v3 alınır ve yazılır (EnErkenBas[3] = 5 artık kesin).

64511297424012345678EnErkenBas[]001624354057607080yığın (üst solda): 5, 2, 1

<3,5> (süre 2): 5 + 2 = 7 > 0 → EnErkenBas[5] = 7; v5 yığına girdi.

64511297424012345678EnErkenBas[]001624354057607080yığın (üst solda): 2, 1

Yığından v5 alınır ve yazılır (EnErkenBas[5] = 7 artık kesin).

64511297424012345678EnErkenBas[]0016243540576071180yığın (üst solda): 2, 1

<5,7> (süre 4): 7 + 4 = 11 > 0 → EnErkenBas[7] = 11.

64511297424012345678EnErkenBas[]0016243540576071180yığın (üst solda): 1

Yığından v2 alınır ve yazılır (EnErkenBas[2] = 4 artık kesin).

64511297424012345678EnErkenBas[]0016243545576071180yığın (üst solda): 1

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

64511297424012345678EnErkenBas[]0016243545576071180yığın (üst solda): boş

Yığından v1 alınır ve yazılır (EnErkenBas[1] = 6 artık kesin).

64511297424012345678EnErkenBas[]0016243547576071180yığın (üst solda): 4

<1,4> (süre 1): 6 + 1 = 7 > 5 → EnErkenBas[4] = 7; v4 yığına girdi.

64511297424012345678EnErkenBas[]0016243547576071180yığın (üst solda): boş

Yığından v4 alınır ve yazılır (EnErkenBas[4] = 7 artık kesin).

64511297424012345678EnErkenBas[]00162435475761671180yığın (üst solda): 6

<4,6> (süre 9): 7 + 9 = 16 > 0 → EnErkenBas[6] = 16; v6 yığına girdi.

64511297424012345678EnErkenBas[]00162435475761671480yığın (üst solda): 7, 6

<4,7> (süre 7): 7 + 7 = 14 > 11 → EnErkenBas[7] = 14; v7 yığına girdi.

64511297424012345678EnErkenBas[]00162435475761671480yığın (üst solda): 6

Yığından v7 alınır ve yazılır (EnErkenBas[7] = 14 artık kesin).

64511297424012345678EnErkenBas[]001624354757616714818yığın (üst solda): 6

<7,8> (süre 4): 14 + 4 = 18 > 0 → EnErkenBas[8] = 18.

64511297424012345678EnErkenBas[]001624354757616714818yığın (üst solda): boş

Yığından v6 alınır ve yazılır (EnErkenBas[6] = 16 artık kesin).

64511297424012345678EnErkenBas[]001624354757616714818yığın (üst solda): 8

<6,8> (süre 2): 16 + 2 = 18 ≤ 18, değişmez; v8 yığına girdi.

64511297424012345678EnErkenBas[]001624354757616714818yığın (üst solda): boş

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ıç0000000000
Çıktı v00645000003, 2, 1
Çıktı v30645070005, 2, 1
Çıktı v506450701102, 1
Çıktı v206455701101
Çıktı v106457701104
Çıktı v4064577161407, 6
Çıktı v70645771614186
Çıktı v60645771614188
Çıktı v8064577161418–
Hangi veriler kalıcı? “Ancak çıktıya yazılan düğümlerin en erken başlama zamanları kesinleşmiştir, o andan itibaren bir daha değişmez.” Ör. v5 işlenince [7] = 11 yazıldı; v4’ten gelen daha uzun yol onu 14 yaptı. Proje en erken 18 günde biter.

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;
a9 = 2 olduğundan v6 en geç 16. gündür (tablo da 16 der) ve v4 için iki yol da 7 verir; burada min kuralı fark yaratmaz. Min kuralını göstermek için v0 örneği daha uygundur.

Geri çözümleme · Kodu adım adım çalıştır

Kod İzleme: EnGecBas[] (ters çizgede)

64511297424012345678EnGecBas[]018118218318418518618718818yığın (üst solda): 8

Başlangıç: EnGecBas[] = 18; ters çizgede öncülsüz tek düğüm v8 yığında.

64511297424012345678EnGecBas[]018118218318418518616714818yığın (üst solda): 7, 6

v8 alınır (EnGecBas[8] = 18 kesin). Öncüller: EnGecBas[6] = 16, EnGecBas[7] = 14. Yığına: v6, v7.

64511297424012345678EnGecBas[]01811821831847510616714818yığın (üst solda): 5, 6

v7 alınır (EnGecBas[7] = 14 kesin). Öncüller: EnGecBas[4] = 7, EnGecBas[5] = 10. Yığına: v5.

64511297424012345678EnGecBas[]0181182183847510616714818yığın (üst solda): 3, 6

v5 alınır (EnGecBas[5] = 10 kesin). Öncüller: EnGecBas[3] = 8. Yığına: v3.

64511297424012345678EnGecBas[]031182183847510616714818yığın (üst solda): 6

v3 alınır (EnGecBas[3] = 8 kesin). Öncüller: EnGecBas[0] = 3.

64511297424012345678EnGecBas[]031182183847510616714818yığın (üst solda): 4

v6 alınır (EnGecBas[6] = 16 kesin). [4] değişmez. Yığına: v4.

64511297424012345678EnGecBas[]0316263847510616714818yığın (üst solda): 2, 1

v4 alınır (EnGecBas[4] = 7 kesin). Öncüller: EnGecBas[1] = 6, EnGecBas[2] = 6. Yığına: v1, v2.

64511297424012345678EnGecBas[]0216263847510616714818yığın (üst solda): 1

v2 alınır (EnGecBas[2] = 6 kesin). Öncüller: EnGecBas[0] = 2 (değişti).

64511297424012345678EnGecBas[]0016263847510616714818yığın (üst solda): 0

v1 alınır (EnGecBas[1] = 6 kesin). Öncüller: EnGecBas[0] = 0 (değişti). Yığına: v0.

64511297424012345678EnGecBas[]0016263847510616714818yığın (üst solda): boş

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ıç1818181818181818188
Çıktı v81818181818181614187, 6
Çıktı v7181818187101614185, 6
Çıktı v518181887101614183, 6
Çıktı v33181887101614186
Çıktı v63181887101614184
Çıktı v436687101614182, 1
Çıktı v226687101614181
Çıktı v106687101614180
Çıktı v00668710161418–
[0] üç kez günlendi: v3’ten 8 − 5 = 3, v2’den 6 − 4 = 2, v1’den 6 − 6 = 0.

Erken/Geç Zamanlar

Bolluklar ve Kritik Aktiviteler

Akt.KenarSüreEn erkenEn geçBolluk
a0(0,1)6000: kritik
a1(0,2)4022
a2(0,3)5033
a3(1,4)1660: kritik
a4(2,4)1462
a5(3,5)2583
a6(4,6)9770: kritik
a7(4,7)7770: kritik
a8(5,7)47103
a9(6,8)216160: kritik
a10(7,8)414140: kritik
a0=6a1=4a2=5a3=1a4=1a5=2a6=9a7=7a8=4a9=2a10=4v0v1v2v3v4v5v6v7v8

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

a0=3a1=2a2=4a3=2a4=3a5=2a6=5v0v1v2v3v4v5
  1. Topolojik sırayı ve her adımda yığını yazın.
  2. EnErkenBas[] ve EnGecBas[] tablolarını doldurun.
  3. Her aktivitenin bolluğunu bulun; kritik yolu belirtin.
Hocadan: “‘Hocam nerden nereye kadar çıkacak?’ İşlediğimiz bütün konulardan gelebilir. Veri Yapıları 1’den de gelebilir.”
Çö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

AlgoritmaYapıZaman
Kaba kuvvet (tek hedef)tüm yollarO(nⁿ)
Dijkstra (temel)maliyet matrisi + dizilerO(n²)
Dijkstra (iyileştirilmiş)min-heap + komşuluk listesiO((n + e) log n)
Yolu yazdırmaöncüller dizisiO(yol uzunluğu)
Bellman-Fordkenar listesiO(n · e)
Floyd-Warshalln × n matrisO(n³)
Topolojik sırasayaçlı liste + gömülü yığınO(n + e)
En erken / en geç zaman, bolluktopolojik sıra + ters listeO(n + e)

Özet

Sık Yapılan Hatalar

1Negatif kenarlı çizgede Dijkstra kullanmak.
2∞ için INT_MAX seçip uzaklik[u] + maliyet toplamında taşma yaşamak.
3En erken zamanda min, en geç zamanda max almak (tersi doğru!).
4En geç zamanı ardıllar kesinleşmeden hesaplamak: sıra ters çizgenin topolojik sırası (ya da ileri sıranın tersi) olmalıdır.
5Topolojik sıraya rastgele bir düğümden başlamak; öncülleri işlenmemiş düğümün değerini kesin sanmak.
6Yolu ileri doğru kurmaya çalışmak; öncüller dizisini tersten kullanmamak.

Özet

Alıştırmalar

  1. Yönlü örnek çizgede kaynak 2 olsaydı EnKisaYolBul hangi uzaklıkları bulurdu?
  2. EnKisaYolBul’un dış döngüsü neden n − 2 tur döner? n − 1 tur dönse ne olurdu?
  3. Adım adım örnekteki (v0…v6) çizgede v3–v6 kenarı 2 yerine 15 olsaydı S’ye katılma sırası ne olurdu?
  4. Topolojik sıra yordamında yığın yerine kuyruk kullanılsaydı v0..v5 örneğinde çıktı ne olurdu?
  5. AoE örneğinde a8’in süresi 4 yerine 8 olsaydı proje süresi ve kritik yol ne olurdu?
  6. Floyd-Warshall örneğinde A[1][0] neden k = 2 turunda değişti?
Cevaplar / ipuçları
  1. d(2→0)=20, d(2→3)=15, d(2→1)=35 (3 üzerinden), d(2→4)=45 (1 üzerinden), 5’e yol yok.
  2. Son kalan düğüm seçildiğinde güncellenecek işaretsiz düğüm kalmaz; bir tur daha zararsız ama gereksiz.
  3. v1 (10), v2 (20), v3 ve v4 (ikisi 30; eşitlikte küçük indis önce: v3, v4), sonra v5 (40), v6 (45).
  4. Kuyrukla (Kahn): 0 1 2 3 5 4 (v3’ün listesinde önce v5, sonra v4 var).
  5. v7’ye v5 üzerinden 7 + 8 = 15 > 14 → EnErken[7] = 15, proje 19; kritik yol v0-v3-v5-v7-v8 (5 + 2 + 8 + 4).
  6. k = 2: 1 → 2 → 0 yolu 2 + 3 = 5 < 6.

Özet

Bölüm Özeti

En kısa yol

Kaba kuvvet O(nⁿ); Dijkstra açgözlüdür, w ≥ 0 ister: O(n²) ya da O((n + e) log n).

Yol

Öncüller dizisi tek başına yeter; tersten (özyineleme/yığın) yazdırılır.

Genellemeler

Negatif kenar: Bellman-Ford O(ne). Tüm çiftler: Floyd-Warshall O(n³).

AoV ve topolojik sıra

Sayaç + gömülü yığın; O(n + e); döngüyü de yakalar.

AoE ve zamanlar

İleri: max ile en erken; geri (ters çizge): min ile en geç. Bolluk = geç − erken.

Kritik yol

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

Son

Bölüm 18: En Kısa Yol ve Aktivite Ağları
Sonraki bölüm: Bölüm 19: Temel Sıralama Algoritmaları · Ders sayfası
1 / 1 C ile Veri Yapıları