Bölüm 4.3: Minimum Kapsayan Ağaç Algoritmaları
Giriş
Öğrenme Çıktıları
Kapsayan ağacı ve minimum kapsayan ağacı (MST) tanımlamak; V − 1 kenar, kesme ve döngü özelliklerini açıklamak.
Kenarları sıralayıp döngü oluşturmayanları seçmek; döngü denetimini ayrık kümelerle (bul / birleştir) yapmak.
Ağacı bir düğümden büyütmek; anahtar ve ata dizilerini öncelik kuyruğuyla adım adım izlemek.
Bileşen tabanlı ve silme tabanlı yaklaşımları uygulamak; dört algoritmanın karmaşıklığını karşılaştırıp uygun olanı seçmek.
MST Kavramı
Kapsayan Ağaç ve Minimum Kapsayan Ağaç
- G = (V, E): bağlı, yönsüz ve kenarları ağırlıklı bir çizge.
- Kapsayan ağaç (spanning tree): tüm düğümleri içeren, bağlı ve döngüsüz alt çizge. Her zaman tam V − 1 kenarı vardır.
- Minimum kapsayan ağaç (Minimum Spanning Tree, MST): kenar ağırlıkları toplamı en küçük olan kapsayan ağaç.
- Ağaca bir kenar eklemek döngü oluşturur; bir kenar silmek ağacı ikiye böler.
MST Kavramı
Örnek: "MST" Çizgesi
- 15 düğüm, 20 kenar. Kalın 14 kenar (= V − 1) minimum kapsayan ağacı oluşturur: toplam ağırlık 141.
- İnce kenarlar dışarıda kalır: her biri, kalın kenarlarla bir döngü kapatır ve o döngünün en ağır kenarıdır (ör. 15, 16, 18, 25, 40, 50).
- Ağırlığı yüksek (20, 24, 25) kenarlar da seçilebilir: bir düğüme ulaşmanın tek yolu onlarsa (köprü) MST'de yer almak zorundadır.
MST Kavramı
Kesme Özelliği ve Döngü Özelliği
{a, b} | {c, d, e}: geçen kenarlar ae = 1, be = 4, bc = 5 → ae güvenli.
b–c–e döngüsü: 4, 5, 6 → ec = 6 asla seçilmez.
Kruskal ve Prim kesme özelliğini, ters silme döngü özelliğini kullanır. Tüm ağırlıklar farklıysa MST tektir.
MST Kavramı
Açgözlü MST Algoritmaları
Dört algoritmanın hepsi açgözlüdür: her adımda yerel olarak en iyi kenarı seçer (ya da en kötüsünü atar) ve bu seçim hiçbir zaman geri alınmaz.
| Algoritma | Fikir | Yardımcı yapı | Zaman |
|---|---|---|---|
| Kruskal (1956) | Kenarları küçükten büyüğe sırala, döngü oluşturmayanları ekle | union-find | O(E log E) |
| Prim (1957) | Bir düğümden başla, ağacı en ucuz komşu kenarla büyüt | öncelik kuyruğu | O((V + E) log V) |
| Borůvka (1926) | Her bileşen kendi en ucuz kenarını aynı anda ekler | union-find | O(E log V) |
| Ters silme | Kenarları büyükten küçüğe dene, bağlılığı bozmayanı sil | BFS/DFS ya da union-find | O(E · (V + E)) |
MST Kavramı
Nerede Kullanılır?
Kruskal
Kruskal Algoritması
- Çizgedeki tüm düğümleri birbirine bağlayan, toplam ağırlığı en küçük ağacı oluşturur; açgözlü (greedy) bir yaklaşımdır.
- Joseph Kruskal tarafından geliştirilmiştir (1956).
- Başlangıçta her düğüm ayrı bir ağaçtır (orman); adım adım bu ağaçlar birleştirilir.
- Kenarlar küçükten büyüğe sıralanır; döngü oluşturmayan kenar seçilir, döngü oluşturan atlanır.
Güçlü yönler
- Seyrek çizgelerde (E ≈ V) çok uygun.
- Basit: kenar listesi + union-find yeterli.
- Bağlı olmayan çizgede minimum kapsayan orman verir.
Zayıf yönler
- Kenar sıralama maliyeti: O(E log E).
- Yoğun çizgede (E ≈ V²) Prim daha hızlı olabilir.
- Döngü denetimi için ek yapı (union-find) gerekir.
Kruskal
Kruskal: Adımlar ve Sözde Kod
- Çizgedeki tüm kenarlar ağırlıklarına göre sıralanır.
- Sıralı kenarlar arasından sıradaki en küçük ağırlıklı kenar seçilir.
- Kenar farklı ağaçlara ait iki düğümü birleştiriyorsa (döngü oluşturmuyorsa) ağaca eklenir ve iki ağaç birleştirilir.
- Aynı ağacın iki düğümünü bağlıyorsa döngü oluşturur: atlanır.
- V − 1 kenar seçilene (ya da kenarlar bitene) kadar 2–4 tekrarlanır.
KRUSKAL(G): MST = [ ] ayrikKume.oluştur(V) kenarlar.sırala(artana göre) her bir (u, v, ağırlık) için kenarlar içinde: eğer ayrikKume.bul(u) ≠ ayrikKume.bul(v): MST.ekle((u, v, ağırlık)) ayrikKume.birleştir(u, v) döndür MST
"Aynı ağaçta mı?" sorusu union-find (ayrık kümeler) yapısıyla neredeyse sabit zamanda yanıtlanır.
Kruskal
Kruskal Adım Adım
| MST | — |
|---|---|
| w(T) | 0 |
| Ağaçlar | {a} {b} {c} {d} {e} |
Başlangıç: her düğüm ayrı bir ağaçtır (5 ağaçlı orman). Kenar tablosu henüz sırasız.
| MST | — |
|---|---|
| w(T) | 0 |
| Ağaçlar | {a} {b} {c} {d} {e} |
Adım 1: kenarlar artan ağırlığa göre sıralanır: ae, cd, ab, be, bc, ec, ed.
| MST | ae |
|---|---|
| w(T) | 1 |
| Ağaçlar | {a, e} {b} {c} {d} |
ae = 1: a ve e farklı ağaçlarda → kenar eklenir, iki ağaç birleşir.
| MST | ae, cd |
|---|---|
| w(T) | 3 |
| Ağaçlar | {a, e} {c, d} {b} |
cd = 2: c ve d farklı ağaçlarda → kenar eklenir, iki ağaç birleşir.
| MST | ae, cd, ab |
|---|---|
| w(T) | 6 |
| Ağaçlar | {a, b, e} {c, d} |
ab = 3: a ve b farklı ağaçlarda → kenar eklenir, iki ağaç birleşir.
| MST | ae, cd, ab |
|---|---|
| w(T) | 6 |
| Ağaçlar | {a, b, e} {c, d} |
be = 4: b ve e zaten aynı ağaçta → eklenirse döngü oluşur, atlanır.
| MST | ae, cd, ab, bc |
|---|---|
| w(T) | 11 |
| Ağaçlar | {a, b, c, d, e} |
bc = 5: b ve c farklı ağaçlarda → kenar eklenir, iki ağaç birleşir.
| MST | ae, cd, ab, bc |
|---|---|
| w(T) | 11 |
| Ağaçlar | tek ağaç |
V − 1 = 4 kenar seçildi: ağaç kuruldu, w(T) = 11. ec ve ed'ye bakmaya gerek yoktur.
Kruskal
Union-Find (Ayrık Kümeler, Disjoint Set)
- Kesişmeyen kümeleri tutar. Her küme bir ağaçtır; kökü kümenin temsilcisidir.
ata[i]: i'nin ebeveyni, kökteata[r] = r. - oluştur(V): her düğüm kendi kümesi →
ata[i] = i. - bul(x) (find): köke kadar çık, kökü döndür.
- birleştir(x, y) (union): bir kökü diğerinin altına bağla.
- Kruskal'da:
bul(u) == bul(v)ise u ve v aynı ağaçtadır → kenar döngü kapatır.
int bul(int[] ata, int i) { if (ata[i] != i) ata[i] = bul(ata, ata[i]); // yol sıkıştırma return ata[i]; } void birlestir(int[] ata, int x, int y) { ata[bul(ata, x)] = bul(ata, y); }
Kruskal
Union-Find ile Döngü Denetimi
oluştur(5): her düğüm kendi kümesinin kökü, ata[i] = i. Kenarlar: ac 1, de 2, ab 3, bc 4, bd 5, cd 6, ce 7.
ac = 1: bul(a) = a, bul(c) = c → farklı → kenar MST'ye; ata[a] = c.
de = 2: bul(d) = d, bul(e) = e → farklı → kenar MST'ye; ata[d] = e.
ab = 3: bul(a) = c, bul(b) = b → farklı → kenar MST'ye; ata[c] = b.
bc = 4: bul(b) = b (yol b), bul(c) = b → aynı kök: döngü, kenar atlanır.
bd = 5: bul(b) = b, bul(d) = e → farklı → kenar MST'ye; ata[b] = e.
MST tamam (4 kenar, w = 11). Ama orman uzun bir zincir oldu: bul(a) a→c→b→e yolunu izler (3 adım).
Yol sıkıştırma: bul(a) dönerken yoldaki her düğümü doğrudan köke bağlar (ata[a] = ata[c] = e). Sonraki aramalar 1 adım.
Kruskal · Kodu adım adım çalıştır
Kruskal: Kodu Adım Adım İzle
Kenarlar sıralanır: (1,3)=2, (2,5)=2, (1,2)=3, (2,3)=4, (3,4)=4, (1,4)=6.
ata[i] = i: her düğüm ayrı bir ağaç (5 ağaç).
e = (1,3), ağırlık 2: bul(1) = 1, bul(3) = 3.
x ≠ y → (1,3) MST'ye eklenir, ata[1] = 3. v1 ve v3 birleşir.
e = (2,5), ağırlık 2: bul(2) = 2, bul(5) = 5.
x ≠ y → (2,5) MST'ye eklenir, ata[2] = 5. v2 ve v5 birleşir.
e = (1,2), ağırlık 3: bul(1) = 3, bul(2) = 5.
x ≠ y → (1,2) MST'ye eklenir, ata[3] = 5. Önceki iki ağaç birleşir.
e = (2,3), ağırlık 4: bul(2) = 5, bul(3) = 5.
x = y = 5: (2,3) iki ayrı ağacı birleştirmiyor → döngü, atlanır.
e = (3,4), ağırlık 4: bul(3) = 5, bul(4) = 4.
x ≠ y → (3,4) MST'ye eklenir, ata[5] = 4. v4 ağaca katılır; w(T) = 11.
agac.size() = 4 = V − 1: döngü biter. (1,4)=6 hiç incelenmez. w(T) = 11.
void kruskal(int V, List<Kenar> ke) { Collections.sort(ke); // artan int[] ata = new int[V + 1]; for (int i = 1; i <= V; i++) ata[i] = i; List<Kenar> agac = new ArrayList<>(); int k = 0; while (agac.size() < V - 1) { Kenar e = ke.get(k++); int x = bul(ata, e.u); int y = bul(ata, e.v); if (x != y) { // ayrı ağaç agac.add(e); ata[x] = y; // birleştir } } }
Kruskal
Kruskal Örneği: Dokuz Düğüm
Kenarlar sıralı: 1, 1, 2, 3, 4, 5, 5, 6, 7, 9, 10, 11, 12, 15, 20. Henüz hiçbir kenar seçilmedi.
EF = 1 eklendi (iki farklı küme). Toplam ağırlık 1.
CF = 1 eklendi (iki farklı küme). Toplam ağırlık 2.
DE = 2 eklendi (iki farklı küme). Toplam ağırlık 4.
FH = 3 eklendi (iki farklı küme). Toplam ağırlık 7.
AB = 4 eklendi (iki farklı küme). Toplam ağırlık 11.
EH = 5: E ve H zaten aynı kümede (E–F–H yolu) → döngü, eklenmez. Toplam 11 kalır.
GI = 5 eklendi (iki farklı küme). Toplam ağırlık 16.
DG = 6 eklendi (iki farklı küme). Toplam ağırlık 22.
AC = 7 eklendi (iki farklı küme). Toplam ağırlık 29. 8 = V − 1 kenar: MST = 29, çizge bağlı.
Kruskal
Kruskal: Karmaşıklık Analizi
| İşlem | Kaç kez | Maliyet |
|---|---|---|
| Kenarları ağırlığa göre sıralama | 1 | O(E log E) |
oluştur(V) | 1 | O(V) |
bul (her kenar için 2) | ≤ 2E | O(E · α(V)) — yol sıkıştırma + rütbe O(E log V) — yalnızca rütbe |
birleştir | V − 1 |
- Toplam: O(E log E); sıralama baskındır. E ≤ V² olduğundan log E ≤ 2 log V, yani O(E log E) = O(E log V).
- Kenarlar zaten sıralıysa (ya da ağırlıklar küçük tamsayıysa, sayma sıralaması) süre neredeyse doğrusal: O(E · α(V)).
- Bellek: kenar listesi +
atadizisi → O(V + E).
Prim
Prim Algoritması
- Tüm düğümleri bağlayan en küçük ağırlıklı ağacı oluşturur; açgözlü bir algoritmadır.
- Robert C. Prim (1957); daha önce V. Jarník (1930) bulmuş, E. W. Dijkstra (1959) yeniden keşfetmiştir.
- Bir başlangıç düğümü seçilir; her adımda ağaçta olmayan düğümler arasından, ağaca en ucuz kenarla bağlanan düğüm ağaca eklenir.
- Her an tek bir ağaç vardır (Kruskal'daki ormandan farklı olarak).
Güçlü yönler
- Yoğun çizgelerde uygun: matrisle O(V²).
- Kenar sıralaması gerekmez.
- Komşuluk listesi/matrisi ile doğal çalışır.
Zayıf yönler
- Performans veri yapısına bağlı (dizi, ikili yığın, Fibonacci yığını).
- Bağlı olmayan çizgede yalnızca başlangıcın bileşenini kapsar.
Prim
Prim: Adımlar, Anahtar ve Ata
- Başlangıç düğümü seçilir ve bu düğüme ait tüm kenarlar öncelik kuyruğuna eklenir.
- Kuyruktan, ağaçta olmayan bir düğüme giden en küçük ağırlıklı kenar seçilir.
- Seçilen kenar ile bağlantılı yeni düğüm ağaca eklenir.
- Yeni düğüme bağlı kenarlar kuyruğa eklenir; tüm düğümler ağaca girene kadar 2–4 tekrarlanır.
Kuyruktan çıkan düğüm için, ağaçta olmayan her komşu v'de: ağırlık(u, v) < anahtar[v] ise anahtar ve ata güncellenir.
Prim
Prim: Sözde Kod
PRIM(G, kaynak): MST = [ ] mesafeler = [ ] ata = [ ] ziyaretEdildi = [ ] her bir v için V içinde: mesafeler[v] = ∞ ziyaretEdildi[v] = 0 mesafeler[kaynak] = 0 öncelikKuyruğu.ekle(kaynak, 0)
döngü öncelikKuyruğu boş değil iken: u = öncelikKuyruğu.çıkar() eğer ziyaretEdildi[u] == 0: ziyaretEdildi[u] = 1 her bir (u, v, ağırlık) için G[u] içinde: eğer ziyaretEdildi[v] == 0 ve ağırlık < mesafeler[v]: mesafeler[v] = ağırlık ata[v] = u öncelikKuyruğu.ekle(v, ağırlık) her bir v için V içinde (kaynak hariç): MST.ekle((ata[v], v, mesafeler[v])) döndür MST
Bu "tembel" (lazy) sürüm bir düğümü kuyruğa birden çok kez ekleyebilir; ziyaretEdildi denetimi eski kayıtları atar. Kuyruk en fazla E kayıt tutar → O(E log E).
Prim
Prim Adım Adım: Öncelik Kuyruğu
| key | u | π |
|---|---|---|
| 0 | 1 | / |
| ∞ | 2 | / |
| ∞ | 3 | / |
| ∞ | 4 | / |
| ∞ | 5 | / |
w(T) = 0
İlklendirme: kuyruk Q tüm düğümlerle başlatılır; key(1) = 0, diğerleri ∞, π = /.
| key | u | π |
|---|---|---|
| 2 | 3 | 1 |
| 3 | 2 | 1 |
| 6 | 4 | 1 |
| ∞ | 5 | / |
w(T) = 0
Kuyruktan düğüm 1 çıkar. Komşular güncellenir: key(3) = 2 (1,3), key(2) = 3 (1,2), key(4) = 6 (1,4).
| key | u | π |
|---|---|---|
| 3 | 2 | 1 |
| 4 | 4 | 3 |
| ∞ | 5 | / |
w(T) = 2
Düğüm 3 çıkar, T'ye (1,3) eklenir. key(4) = 4 (3,4) olur; (2,3) = 4 > key(2) = 3 → değişmez.
| key | u | π |
|---|---|---|
| 2 | 5 | 2 |
| 4 | 4 | 3 |
w(T) = 5
Düğüm 2 çıkar, T'ye (1,2) eklenir. key(5) = 2 (2,5).
| key | u | π |
|---|---|---|
| 4 | 4 | 3 |
w(T) = 7
Düğüm 5 çıkar, T'ye (2,5) eklenir. Ağaçta olmayan komşusu yok: Q'da güncelleme yok.
| key | u | π |
|---|---|---|
| Q = ∅ | ||
w(T) = 11
Düğüm 4 çıkar, T'ye (3,4) eklenir. Q'da güncelleme yok.
| key | u | π |
|---|---|---|
| Q = ∅ | ||
w(T) = 11
Son durum: Q = ∅. MST = (1,3), (1,2), (2,5), (3,4), w(T) = 11 — Kruskal ile aynı ağaç.
Kesikli çizgi: π ile gösterilen, henüz kesinleşmemiş en ucuz bağlantı. Q tablosu key değerine göre sıralıdır.
Prim · Kodu adım adım çalıştır
Prim: Kodu Adım Adım İzle (Komşuluk Matrisi)
A–F çizgesi, kaynak B (indis 1). Tüm anahtarlar ∞; anahtar[B] = 0, ata[B] = −1.
enKucukAnahtar → u = B (anahtar 0): kaynak ağaca girer.
v = A: G[B][A] = 1 < anahtar[A] = ∞ → anahtar[A] = 1, ata[A] = B.
v = C: G[B][C] = 3 < anahtar[C] = ∞ → anahtar[C] = 3, ata[C] = B.
v = D: G[B][D] = 2 < anahtar[D] = ∞ → anahtar[D] = 2, ata[D] = B.
v = E: G[B][E] = 6 < anahtar[E] = ∞ → anahtar[E] = 6, ata[E] = B.
v = F: G[B][F] = 5 < anahtar[F] = ∞ → anahtar[F] = 5, ata[F] = B.
enKucukAnahtar → u = A (anahtar 1); (B,A) ağaca girer. w(T) = 1. AD = 3 > anahtar[D] = 2: güncelleme yok.
enKucukAnahtar → u = D (anahtar 2); (B,D) ağaca girer. w(T) = 3.
v = E: G[D][E] = 3 < anahtar[E] = 6 → anahtar[E] = 3, ata[E] = D.
enKucukAnahtar → u = C (anahtar 3); (B,C) ağaca girer. w(T) = 6. C ve E eşit (3): küçük indisli C.
v = F: G[C][F] = 4 < anahtar[F] = 5 → anahtar[F] = 4, ata[F] = C.
enKucukAnahtar → u = E (anahtar 3); (D,E) ağaca girer. w(T) = 9.
v = F: G[E][F] = 2 < anahtar[F] = 4 → anahtar[F] = 2, ata[F] = E.
Döngü V − 1 = 5 kez döndü; son düğüm F'nin ata'sı E. MST = (ata[v], v): AB, BC, BD, DE, EF → w(T) = 11.
void prim(int[][] G, int kaynak) { int V = G.length; int[] anahtar = new int[V]; int[] ata = new int[V]; boolean[] agacta = new boolean[V]; Arrays.fill(anahtar, Integer.MAX_VALUE); anahtar[kaynak] = 0; ata[kaynak] = -1; for (int s = 0; s < V - 1; s++) { int u = enKucukAnahtar(anahtar, agacta); agacta[u] = true; for (int v = 0; v < V; v++) if (G[u][v] != 0 && !agacta[v] && G[u][v] < anahtar[v]) { anahtar[v] = G[u][v]; ata[v] = u; } } }
Prim
Prim Örneği: Dokuz Düğüm (A'dan Başla)
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ |
| ata | − | A | A | − | − | − | − | − | − |
Başlangıç A: komşuları B (4) ve C (7) aday olur.
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | 9 | ∞ | 20 | ∞ | ∞ | ∞ |
| ata | − | A | A | B | − | B | − | − | − |
B ağaca girer: (A,B) = 4, toplam 4. Güncellenen: D → 9, F → 20.
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | 9 | ∞ | 1 | ∞ | ∞ | ∞ |
| ata | − | A | A | B | − | C | − | − | − |
C ağaca girer: (A,C) = 7, toplam 11. Güncellenen: F → 1.
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | 9 | 1 | 1 | ∞ | 3 | ∞ |
| ata | − | A | A | B | F | C | − | F | − |
F ağaca girer: (C,F) = 1, toplam 12. Güncellenen: E → 1, H → 3.
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | 2 | 1 | 1 | 10 | 3 | 15 |
| ata | − | A | A | E | F | C | E | F | E |
E ağaca girer: (F,E) = 1, toplam 13. Güncellenen: D → 2, G → 10, I → 15.
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | 2 | 1 | 1 | 6 | 3 | 15 |
| ata | − | A | A | E | F | C | D | F | E |
D ağaca girer: (E,D) = 2, toplam 15. Güncellenen: G → 6.
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | 2 | 1 | 1 | 6 | 3 | 12 |
| ata | − | A | A | E | F | C | D | F | H |
H ağaca girer: (F,H) = 3, toplam 18. Güncellenen: I → 12. EH = 5 artık kullanılamaz: E ve H ikisi de ağaçta (döngü).
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | 2 | 1 | 1 | 6 | 3 | 5 |
| ata | − | A | A | E | F | C | D | F | G |
G ağaca girer: (D,G) = 6, toplam 24. Güncellenen: I → 5.
| A | B | C | D | E | F | G | H | I | |
|---|---|---|---|---|---|---|---|---|---|
| anahtar | 0 | 4 | 7 | 2 | 1 | 1 | 6 | 3 | 5 |
| ata | − | A | A | E | F | C | D | F | G |
I ağaca girer: (G,I) = 5, toplam 29. Tüm düğümler ağaçta: MST = 29 (Kruskal ile aynı).
Prim
Prim: Karmaşıklık Analizi
| Uygulama | en küçüğü çıkar | anahtar azalt | Toplam |
|---|---|---|---|
| Komşuluk matrisi + dizi taraması (yukarıdaki kod) | V × O(V) | O(1) | O(V²) |
| Komşuluk listesi + ikili yığın (binary heap) | V × O(log V) | E × O(log V) | O((V + E) log V) |
| Tembel sürüm (sözde kod): kuyrukta kenar kopyaları | ≤ E ekleme/çıkarma, her biri O(log E) | O(E log E) | |
| Fibonacci yığını | O(log V) amortize | O(1) amortize | O(E + V log V) |
Prim
Prim ile Dijkstra Arasındaki Fark
İki algoritmanın kodu neredeyse aynıdır (Bölüm 4.2): bir kaynaktan başlar, öncelik kuyruğundan en küçük anahtarlı düğümü çıkarır ve komşuları gevşetir. Tek fark anahtarın anlamıdır.
| Prim (MST) | Dijkstra (en kısa yol) | |
|---|---|---|
| anahtar[v] | v'yi ağaca bağlayan en ucuz tek kenar | kaynaktan v'ye toplam yol uzunluğu |
| Güncelleme | w(u,v) < anahtar[v] | uzaklik[u] + w(u,v) < uzaklik[v] |
| Sonuç | toplam ağırlığı en küçük ağaç | en kısa yollar ağacı (SPT) |
| Kaynak | sonucu etkilemez (aynı ağırlık) | sonucu belirler |
| Negatif ağırlık | sorun değil | çalışmaz |
Beş düğümlü örnekte 1'den en kısa yollar ağacı (1,4) = 6 kenarını kullanabilir; MST onun yerine (3,4) = 4'ü seçer.
Borůvka
Borůvka (Sollin) Algoritması
- Minimum kapsayan ağacı bulan en eski algoritma: Otakar Borůvka, 1926 (Moravya elektrik ağı). Diğer adı Sollin algoritması.
- Başlangıçta her düğüm bağımsız bir bileşendir.
- Her turda her bileşen kendisini dışarıya bağlayan en ucuz kenarı seçer; seçilenlerin hepsi birden eklenir.
- Bileşenler union-find ile birleştirilir; tek bileşen kalana kadar devam edilir. Bağlı çizgede |V| − 1 kenar üretir.
Güçlü yönler
- Bileşenlerin seçimleri birbirinden bağımsız: paralel çalışmaya çok uygun.
- Tur sayısı en fazla ⌈log₂ V⌉.
Zayıf yönler
- Kruskal/Prim'den daha karmaşık kodlanır.
- Eşit ağırlıklarda tutarlı bir eşitlik bozucu (ör. kenar indisi) şart; yoksa döngü oluşabilir.
Borůvka
Borůvka: Adımlar ve Sözde Kod
- Başlangıçta her düğümü tek başına bir bileşen (küme) olarak ele al.
- Her bileşen için, onu başka bir bileşene bağlayan en küçük ağırlıklı kenarı bul.
- Bulunan kenarların hepsini ekle; bağladıkları bileşenleri birleştir (aynı kenar iki kez eklenmez).
- Tek bileşen kalana kadar 2 ve 3. adımları tekrarla.
Not: Borůvka kenarları sıralamaz; her turda tüm kenarları bir kez tarayıp bileşen başına en küçüğü tutar.
BORUVKA(G): MST = [ ] bileşenSayısı = |V| döngü bileşenSayısı > 1 iken: enUcuz = [ ] // bileşen başına her bir e = (u, v, ağırlık) için E içinde: cu = ayrikKume.bul(u) cv = ayrikKume.bul(v) eğer cu ≠ cv: enUcuz[cu] = min(enUcuz[cu], e) enUcuz[cv] = min(enUcuz[cv], e) her bir (u, v, ağırlık) için enUcuz içinde: eğer ayrikKume.bul(u) ≠ ayrikKume.bul(v): MST.ekle((u, v, ağırlık)) ayrikKume.birleştir(u, v) bileşenSayısı -= 1 döndür MST
Borůvka
Borůvka Adım Adım: Altı Düğüm
Başlangıç: her düğüm ayrı bir bileşen (6 bileşen).
| Bileşen | En ucuz |
|---|---|
| {A} | AB 1 |
| {B} | AB 1 |
| {C} | BC 5 |
| {D} | AD 4 |
| {E} | EF 2 |
| {F} | EF 2 |
Tur 1: her bileşen kendi en ucuz dış kenarını seçer (mavi). AB ve EF iki bileşen tarafından seçilir ama birer kez eklenir.
Tur 1 sonu: 4 farklı kenar eklendi (AB, BC, AD, EF), 2 bileşen kaldı: {A, B, C, D} {E, F}. Toplam 12.
| Bileşen | En ucuz |
|---|---|
| {A,B,C,D} | BE 3 |
| {E,F} | BE 3 |
Tur 2: her bileşen kendi en ucuz dış kenarını seçer (mavi). İki bileşen de aynı kenarı seçer.
Tur 2 sonu: 1 farklı kenar eklendi (BE), 1 bileşen kaldı: {A, B, C, D, E, F}. Toplam 15. MST = 15 (AB, BC, AD, EF, BE).
Kruskal kenarları 1, 2, 3, 4, 5 sırasıyla tek tek ekler; Borůvka'da ise tur 1'de dört kenar aynı anda eklenir. Sonuç ağacı aynıdır.
Borůvka
Borůvka Örneği: On İki Düğüm
Başlangıç: her düğüm ayrı bir bileşen (12 bileşen).
| Bileşen | En ucuz |
|---|---|
| {A} | AC 6 |
| {B} | BD 1 |
| {C} | AC 6 |
| {D} | BD 1 |
| {E} | EF 2 |
| {F} | EF 2 |
| {G} | GI 5 |
| {H} | GH 15 |
| {I} | GI 5 |
| {J} | JL 4 |
| {K} | GK 10 |
| {L} | JL 4 |
Tur 1: her bileşenin en ucuz dış kenarı (mavi).
Tur 1 sonu: 7 farklı kenar eklendi (AC, BD, EF, GI, GH, JL, GK), 5 bileşen kaldı: {G, H, I, K} {A, C} {B, D} {E, F} {J, L}. Toplam 43.
| Bileşen | En ucuz |
|---|---|
| {A,C} | BC 7 |
| {B,D} | DF 3 |
| {E,F} | DF 3 |
| {G,H,I,K} | KL 12 |
| {J,L} | KL 12 |
Tur 2: her bileşenin en ucuz dış kenarı (mavi).
Tur 2 sonu: 3 farklı kenar eklendi (BC, DF, KL), 2 bileşen kaldı: {A, B, C, D, E, F} {G, H, I, J, K, L}. Toplam 65.
| Bileşen | En ucuz |
|---|---|
| {A,B,C,D,E,F} | EJ 18 |
| {G,H,I,J,K,L} | EJ 18 |
Tur 3: her bileşenin en ucuz dış kenarı (mavi).
Tur 3 sonu: 1 farklı kenar eklendi (EJ), 1 bileşen kaldı: {A, B, C, D, E, F, G, H, I, J, K, L}. MST = 83, yalnızca 3 turda.
Borůvka
Borůvka: Java Kodu ve Karmaşıklık
while (bilesenSayisi > 1) { Kenar[] enUcuz = new Kenar[V]; for (Kenar e : kenarlar) { // 1) en ucuzlar int k1 = bul(ata, e.u), k2 = bul(ata, e.v); if (k1 == k2) continue; // aynı bileşen if (enUcuz[k1] == null || e.w < enUcuz[k1].w) enUcuz[k1] = e; if (enUcuz[k2] == null || e.w < enUcuz[k2].w) enUcuz[k2] = e; } for (Kenar e : enUcuz) { // 2) hepsini ekle if (e == null) continue; int k1 = bul(ata, e.u), k2 = bul(ata, e.v); if (k1 == k2) continue; // zaten eklendi agac.add(e); ata[k1] = k2; // birleştir bilesenSayisi--; } }
- Her turda her bileşen en az bir başka bileşenle birleşir → bileşen sayısı en az yarıya iner.
- Tur sayısı ≤ ⌈log₂ V⌉; her tur tüm kenarları bir kez tarar: O(E).
- Toplam: O(E log V). Sıralama yapılmaz; "ağırlık sıralaması" gerekmez.
e.w <karşılaştırması eşitlikte ilk (küçük indisli) kenarı tutar: bu tutarlı eşitlik bozucu döngüyü önler.
Ters Silme
Ters Silme (Reverse-Delete) Algoritması
- Minimum kapsayan ağacı bulan açgözlü bir algoritmadır (Kruskal, 1956 makalesinde önerdi).
- Kenarları ağırlıklarına göre azalan şekilde sıralar.
- En ağır kenardan başlayarak, çizgenin bağlılığını bozmayan her kenarı siler.
- Döngü özelliğine dayanır: silinen kenar, bir döngünün en ağır kenarıdır.
Ters Silme
Ters Silme: Adımlar, Sözde Kod, Karmaşıklık
- Tüm kenarları ağırlığına göre azalan şekilde sırala.
- Sıradaki en ağır kenarı sil.
- Çizge artık bağlı değilse silinen kenarı geri ekle.
- Tüm kenarlar işlenene kadar 2. ve 3. adımları tekrarla.
TERS_SİLME(G): MST = tüm kenarlar kenarlar.sırala(azalana göre) her bir (u, v, ağırlık) için kenarlar içinde: MST.sil((u, v, ağırlık)) ayrikKume.sıfırla() her bir (x, y) için MST içinde: ayrikKume.birleştir(x, y) kök = ayrikKume.bul(V[0]) eğer bir v için ayrikKume.bul(v) ≠ kök: MST.ekle((u, v, ağırlık)) // geri koy döndür MST
Bağlılık testi tüm düğümlere bakar; kökten kopuk en az bir düğüm varsa silinen kenar bir kez geri konur.
Ters Silme
Ters Silme Adım Adım: Yedi Düğüm
| Kenar | w | Durum |
|---|---|---|
| 3–4 | 15 | |
| 5–6 | 11 | |
| 1–3 | 9 | |
| 4–6 | 9 | |
| 1–2 | 8 | |
| 4–5 | 8 | |
| 1–4 | 7 | |
| 0–1 | 7 | |
| 3–5 | 6 | |
| 2–4 | 5 | |
| 0–3 | 5 |
Kenarlar azalan sırada: 15, 11, 9, 9, 8, 8, 7, 7, 6, 5, 5. Toplam ağırlık 90.
| Kenar | w | Durum |
|---|---|---|
| 3–4 | 15 | silindi |
| 5–6 | 11 | |
| 1–3 | 9 | |
| 4–6 | 9 | |
| 1–2 | 8 | |
| 4–5 | 8 | |
| 1–4 | 7 | |
| 0–1 | 7 | |
| 3–5 | 6 | |
| 2–4 | 5 | |
| 0–3 | 5 |
4–3 = 15 silinir: çizge hâlâ bağlı (kenar bir döngünün en ağırıdır).
| Kenar | w | Durum |
|---|---|---|
| 3–4 | 15 | silindi |
| 5–6 | 11 | silindi |
| 1–3 | 9 | |
| 4–6 | 9 | |
| 1–2 | 8 | |
| 4–5 | 8 | |
| 1–4 | 7 | |
| 0–1 | 7 | |
| 3–5 | 6 | |
| 2–4 | 5 | |
| 0–3 | 5 |
6–5 = 11 silinir: çizge hâlâ bağlı (kenar bir döngünün en ağırıdır).
| Kenar | w | Durum |
|---|---|---|
| 3–4 | 15 | silindi |
| 5–6 | 11 | silindi |
| 1–3 | 9 | silindi |
| 4–6 | 9 | |
| 1–2 | 8 | |
| 4–5 | 8 | |
| 1–4 | 7 | |
| 0–1 | 7 | |
| 3–5 | 6 | |
| 2–4 | 5 | |
| 0–3 | 5 |
1–3 = 9 silinir: çizge hâlâ bağlı (kenar bir döngünün en ağırıdır).
| Kenar | w | Durum |
|---|---|---|
| 3–4 | 15 | silindi |
| 5–6 | 11 | silindi |
| 1–3 | 9 | silindi |
| 4–6 | 9 | kaldı |
| 1–2 | 8 | |
| 4–5 | 8 | |
| 1–4 | 7 | |
| 0–1 | 7 | |
| 3–5 | 6 | |
| 2–4 | 5 | |
| 0–3 | 5 |
4–6 = 9 silinirse düğüm 6 yalnız kalır → bağlılık bozulur, kenar geri eklenir.
| Kenar | w | Durum |
|---|---|---|
| 3–4 | 15 | silindi |
| 5–6 | 11 | silindi |
| 1–3 | 9 | silindi |
| 4–6 | 9 | kaldı |
| 1–2 | 8 | silindi |
| 4–5 | 8 | |
| 1–4 | 7 | |
| 0–1 | 7 | |
| 3–5 | 6 | |
| 2–4 | 5 | |
| 0–3 | 5 |
2–1 = 8 silinir: çizge hâlâ bağlı (kenar bir döngünün en ağırıdır).
| Kenar | w | Durum |
|---|---|---|
| 3–4 | 15 | silindi |
| 5–6 | 11 | silindi |
| 1–3 | 9 | silindi |
| 4–6 | 9 | kaldı |
| 1–2 | 8 | silindi |
| 4–5 | 8 | silindi |
| 1–4 | 7 | |
| 0–1 | 7 | |
| 3–5 | 6 | |
| 2–4 | 5 | |
| 0–3 | 5 |
4–5 = 8 silinir: çizge hâlâ bağlı (kenar bir döngünün en ağırıdır).
| Kenar | w | Durum |
|---|---|---|
| 3–4 | 15 | silindi |
| 5–6 | 11 | silindi |
| 1–3 | 9 | silindi |
| 4–6 | 9 | kaldı |
| 1–2 | 8 | silindi |
| 4–5 | 8 | silindi |
| 1–4 | 7 | kaldı |
| 0–1 | 7 | kaldı |
| 3–5 | 6 | kaldı |
| 2–4 | 5 | kaldı |
| 0–3 | 5 | kaldı |
Kalan 6 kenar (7, 7, 6, 5, 5 ve 9) birer köprüdür: hiçbiri silinemez. 6 = V − 1 kenar, MST = 39.
Ters Silme · Kodu adım adım çalıştır
Ters Silme: Kodu Adım Adım İzle
Kenarlar azalan sıralanır: DE 15, FG 11, BD 9, EG 9, BC 8, EF 8, AB 7, BE 7, DF 6, AD 5, CE 5.
En yüksek ağırlıklı DE incelenir: DE = 15 silindi, bagliMi = true → silinmiş kalır.
FG çıkarılırsa bağlılık bozuluyor mu? FG = 11 silindi, bagliMi = true → silinmiş kalır.
Bir sonraki en ağır kenar BD: BD = 9 silindi, bagliMi = true → silinmiş kalır.
EG = 9 silinirse G yalnız kalır (FG zaten silindi) → bagliMi = false, EG geri konur; i = 1.
BC = 8 silindi, bagliMi = true → silinmiş kalır.
EF = 8 silindi, bagliMi = true → silinmiş kalır.
AB = 7 silinirse {A, D, F} kopar → geri konur, i = 2.
BE = 7 silinirse {C, E, G} kopar → geri konur, i = 3.
DF = 6 silinirse {F} kopar → geri konur, i = 4.
AD = 5 silinirse {D, F} kopar → geri konur, i = 5.
CE = 5 silinirse {C} kopar → geri konur, i = 6.
i = boyut = 6: döngü biter. Silinecek başka kenar yok; kalan ağaç AB, AD, BE, CE, DF, EG → w = 39.
void tersSil(int V, List<Kenar> ke) { ke.sort((a, b) -> b.w - a.w); // azalan int i = 0; while (i < ke.size()) { Kenar e = ke.remove(i); // geçici sil if (!bagliMi(V, ke)) { ke.add(i, e); // geri koy i++; } } }
Karşılaştırma
Dört Algoritmanın Karşılaştırması
| Kruskal | Prim | Borůvka | Ters silme | |
|---|---|---|---|---|
| Bakış | kenar odaklı | düğüm odaklı | bileşen odaklı | kenar odaklı (silme) |
| Ara durum | orman | tek ağaç | orman | bağlı çizge |
| Dayandığı özellik | kesme | kesme | kesme | döngü |
| Veri yapısı | sıralı kenarlar + union-find | öncelik kuyruğu / dizi | union-find | BFS/DFS |
| Zaman | O(E log E) | O(V²) ya da O((V + E) log V) | O(E log V) | O(E · (V + E)) |
| En uygun | seyrek çizge | yoğun çizge | paralel / dağıtık | öğretim, teori |
| Bağlı olmayan çizge | orman verir | bir bileşen | orman verir | bileşenleri korur |
Özet
Özet
| Konu | Hatırlanacaklar |
|---|---|
| MST | bağlı, yönsüz, ağırlıklı çizgede toplam ağırlığı en küçük kapsayan ağaç; V − 1 kenar |
| Özellikler | kesmenin en hafif kenarı güvenli; döngünün en ağır kenarı dışarıda; farklı ağırlıklar → tek MST |
| Kruskal | kenarları sırala; bul(u) ≠ bul(v) ise ekle ve birleştir — O(E log E) |
| Union-Find | ata[] ormanı; yol sıkıştırma + rütbe → işlem başına O(α(V)) |
| Prim | tek ağacı büyüt; anahtar = ağaca en ucuz kenar — O(V²) / O((V + E) log V) |
| Borůvka | her bileşen en ucuz dış kenarını aynı anda ekler; ≤ log₂ V tur — O(E log V) |
| Ters silme | azalan sırada sil, bağlılık bozulursa geri koy — O(E · (V + E)) |
Özet
Etkileşimli Simülatörler
Özet
Sık Yapılan Hatalar
anahtar[u] + w yazmak Dijkstra'dır. Prim'de anahtar yalnızca tek kenarın ağırlığıdır.while (agac.size() < V − 1) kenarlar bitince dizi sınırını aşar.Özet
Örnek Problemler
Çizge: AB = 2, AC = 3, BC = 1, BD = 4, CD = 5, CE = 6, DE = 7 (yönsüz).
- Kruskal'ın incelediği kenarları sırasıyla yazın; hangileri reddedilir? w(T) = ?
- Prim'i A'dan çalıştırın: düğümler hangi sırayla ağaca girer? İlk adımdan sonra anahtar dizisi nedir?
- Borůvka kaç turda biter? Tur 1'de hangi kenarlar seçilir?
- Ters silme hangi kenarları siler?
- 100 düğümlü bağlı bir çizgenin MST'sinde kaç kenar vardır?
- Tüm ağırlıklara 5 eklenirse MST değişir mi? Toplam ne olur?
Cevapları göster
- BC 1 ✓, AB 2 ✓, AC 3 ✗ (döngü), BD 4 ✓, CD 5 ✗, CE 6 ✓ → 4 kenar, w(T) = 13. DE incelenmez.
- A, B (2), C (1, B üzerinden), D (4), E (6). A'dan sonra: anahtar[B] = 2, [C] = 3, [D] = [E] = ∞.
- 1 tur: A→AB, B→BC, C→BC, D→BD, E→CE; dört farklı kenar tek bileşen yapar.
- DE 7 silinir, CE 6 kalır (E kopar), CD 5 silinir, BD 4 kalır, AC 3 silinir, AB ve BC kalır.
- V − 1 = 99.
- Değişmez: kenarların sırası korunur. Toplam 13 + 4 · 5 = 33 olur.
Özet
Alıştırmalar
- Tüm ağırlıklar farklıysa MST'nin tek olduğunu kesme özelliğiyle gösterin.
- En büyük kapsayan ağacı bulmak için Kruskal'ı nasıl değiştirirsiniz?
- Dokuz düğümlü örneğin MST'sini kullanarak düğümleri 3 kümeye ayırın.
- 0..5,
ata[bul(x)] = bul(y): birleştir (0,1), (2,3), (1,3), (4,5), (3,5). Sonata?bul(0)neyi değiştirir? - V = 1000: Prim O(V²) ve O(E log V) sürümlerini E = 5 000 ve 500 000 için karşılaştırın.
- Kruskal'ı bağlı olmayan çizgede orman döndürecek biçimde düzeltin.
Cevaplar / ipuçları
- Her MST kenarı e, T − e'nin tanımladığı kesmenin tek en hafif kenarıdır; o kenar her MST'de olmak zorunda → MST'ler aynı.
- Kenarları azalan sıralayın (ya da −w kullanın).
- MST'den en ağır 2 kenarı (AC 7, DG 6) silin: {C, D, E, F, H} {A, B} {G, I}.
- ata = [1, 3, 3, 5, 5, 5].
bul(0): 0→1→3→5 yolu sıkışır → ata = [5, 5, 3, 5, 5, 5]. - V² = 10⁶; E log V ≈ 5 · 10⁴ (yığın kazanır) ve ≈ 5 · 10⁶ (matris kazanır).
- Koşul:
k < ke.size() && agac.size() < V − 1; sondaagac.size() < V − 1ise çizge bağlı değildir.
Özet
Kaynaklar
- Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, MIT Press, 2022 (Bölüm 19: ayrık kümeler, Bölüm 21: MST).
- Sedgewick ve Wayne, Algorithms, 4. baskı, Addison-Wesley, 2011 (Bölüm 4.3: Minimum Spanning Trees).
- J. B. Kruskal, "On the Shortest Spanning Subtree of a Graph and the Traveling Salesman Problem", Proc. AMS 7(1), 1956.
- R. C. Prim, "Shortest Connection Networks and Some Generalizations", Bell System Technical Journal 36(6), 1957.
- O. Borůvka, "O jistém problému minimálním", Práce Moravské přírodovědecké společnosti 3, 1926.
- R. E. Tarjan, "Efficiency of a Good But Not Linear Set Union Algorithm", Journal of the ACM 22(2), 1975.