Ders 18 / 25
Minimum Kapsayan Ağaç
Kesme özelliği, Prim ve Kruskal algoritmaları, birleşim-bulma yapısı, iki yaklaşımın karşılaştırması ve kümeleme uygulaması.
İçindekiler
Buraya kadarki sorular iki düğüm arasındaki yolla ilgiliydi. Bu ders soruyu değiştirir: tüm düğümleri birbirine bağlayan en ucuz kenar kümesi hangisidir?
Soru, ağ tasarımının temel biçimidir: her noktayı bağlamak zorunludur, toplam kablo, boru veya hat maliyeti en aza indirilmelidir.
Problem
Ağırlıklı, yönsüz ve bağlı bir çizgede kapsayan ağaç, tüm düğümleri içeren ve çevrim barındırmayan bir alt çizgedir. Böyle bir alt çizgede tam olarak kenar bulunur.
Minimum kapsayan ağaç (MST), kenar ağırlıkları toplamı en küçük olan kapsayan ağaçtır. Ağırlıklar birbirinden farklıysa MST tektir; eşit ağırlıklar varsa birden çok MST olabilir ve hepsinin toplam ağırlığı aynıdır.
Bu ders boyunca kullanılan çizge yönsüzdür:
kenarlar: list[tuple[int, str, str]] = [ (4, "A", "B"), (2, "A", "C"), (5, "B", "C"), (10, "B", "D"), (3, "C", "E"), (4, "E", "D"), (11, "D", "F"), ] dugumler = ["A", "B", "C", "D", "E", "F"]
Kesme Özelliği
Her iki algoritma da açgözlüdür ve doğrulukları tek bir gözleme dayanır.
Kesme (cut), düğümlerin boş olmayan iki parçaya ayrılmasıdır. Bir kenar, uçları farklı parçalardaysa kesmeyi geçer.
Kesme özelliği: Herhangi bir kesmeyi geçen en hafif kenar, bir minimum kapsayan ağaçta bulunur.
Gerekçe bir değiş tokuş akıl yürütmesidir. En hafif geçen kenar olsun ve onu içermeyen bir MST düşünülsün. Bu ağaca eklenirse bir çevrim oluşur; çevrim, kesmeyi en az bir kez daha geçmek zorundadır. O geçen kenar olsun. çıkarılıp eklendiğinde yine bir kapsayan ağaç elde edilir ve ağırlığı artmamıştır, çünkü ’tir. Dolayısıyla ’yi içeren bir MST de vardır.
Bu özellik, “şu an güvenli olan en ucuz kenarı al” biçimindeki her açgözlü stratejinin doğruluğunu verir. İki algoritma yalnızca hangi kesmeye baktıklarıyla ayrılır.
Prim Algoritması
Tek bir düğümden başlanır ve ağaç her adımda büyütülür. Bakılan kesme, ağaçtaki düğümler ile dışarıdakiler arasındaki kesmedir; seçilen kenar bu kesmeyi geçen en hafif kenardır.
import heapq from collections import defaultdict def komsuluk(kenarlar: list[tuple[int, str, str]]) -> dict[str, list[tuple[int, str]]]: k: dict[str, list[tuple[int, str]]] = defaultdict(list) for agirlik, v, w in kenarlar: k[v].append((agirlik, w)) k[w].append((agirlik, v)) return k def prim(kenarlar: list[tuple[int, str, str]], baslangic: str) -> tuple[list[tuple[int, str, str]], int]: komsu = komsuluk(kenarlar) icinde = {baslangic} kuyruk = [(a, baslangic, w) for a, w in komsu[baslangic]] heapq.heapify(kuyruk) agac: list[tuple[int, str, str]] = [] while kuyruk: agirlik, v, w = heapq.heappop(kuyruk) if w in icinde: # kenarın iki ucu da ağaçta: çevrim continue icinde.add(w) agac.append((agirlik, v, w)) for a, komsusu in komsu[w]: if komsusu not in icinde: heapq.heappush(kuyruk, (a, w, komsusu)) return agac, sum(a for a, _, _ in agac) print(prim(kenarlar, "A")) # ([(2, 'A', 'C'), (3, 'C', 'E'), (4, 'A', 'B'), (4, 'E', 'D'), (11, 'D', 'F')], 24)
Yapı, Dijkstra’ya çok benzer; tek fark önceliğin kaynaktan uzaklık değil ağaca uzaklık olmasıdır. Maliyet de aynıdır: ikili yığınla .
Kruskal Algoritması
Kruskal ağaç büyütmez; kenarları ağırlığa göre sıralar ve sırayla ekler. Bir kenar, iki ucu zaten aynı bileşendeyse çevrim yaratacağı için atlanır.
Bu denetim için birleşim-bulma (union-find) yapısı kullanılır: her düğümün hangi bileşende olduğunu neredeyse sabit maliyetle söyleyen bir yapı.
class BirlesimBulma: def __init__(self, dugumler: list[str]) -> None: self.ebeveyn = {d: d for d in dugumler} self.derece = {d: 0 for d in dugumler} def bul(self, d: str) -> str: while self.ebeveyn[d] != d: self.ebeveyn[d] = self.ebeveyn[self.ebeveyn[d]] # yol sıkıştırma d = self.ebeveyn[d] return d def birlestir(self, a: str, b: str) -> bool: ka, kb = self.bul(a), self.bul(b) if ka == kb: return False # zaten aynı bileşen if self.derece[ka] < self.derece[kb]: # dereceye göre birleştirme ka, kb = kb, ka self.ebeveyn[kb] = ka if self.derece[ka] == self.derece[kb]: self.derece[ka] += 1 return True def kruskal(kenarlar: list[tuple[int, str, str]], dugumler: list[str]) -> tuple[list[tuple[int, str, str]], int]: kume = BirlesimBulma(dugumler) agac: list[tuple[int, str, str]] = [] for agirlik, v, w in sorted(kenarlar): if kume.birlestir(v, w): agac.append((agirlik, v, w)) if len(agac) == len(dugumler) - 1: # ağaç tamamlandı break return agac, sum(a for a, _, _ in agac) print(kruskal(kenarlar, dugumler)) # ([(2, 'A', 'C'), (3, 'C', 'E'), (4, 'A', 'B'), (4, 'E', 'D'), (11, 'D', 'F')], 24)
İki algoritma bu çizgede aynı ağacı bulur. Genel olarak yalnızca toplam ağırlığın aynı olması güvencedir; eşit ağırlıklı kenarlar varsa farklı ama eşit ağırlıkta ağaçlar üretebilirler.
Buradaki kesme, “eklenmeye çalışılan kenarın bir ucunun bileşeni” ile geri kalan düğümlerdir; kenar, o kesmeyi geçen en hafif kenardır çünkü daha hafif olanlar zaten denenmiştir.
Yol sıkıştırma ve dereceye göre birleştirme birlikte kullanıldığında, birleşim-bulma işlemlerinin amortize maliyeti neredeyse sabittir — büyümesi ters Ackermann işleviyle sınırlıdır ve pratikte dörtten küçüktür.
Karşılaştırma
| Ölçüt | Prim | Kruskal |
|---|---|---|
| Yaklaşım | Ağacı büyüt | Kenarları sırayla ekle |
| Yardımcı yapı | Öncelik kuyruğu | Birleşim-bulma |
| Maliyet | ||
| Yoğun çizge | Uygun (dizi değişkesiyle ) | Sıralama baskın |
| Bağlı olmayan çizge | Yalnızca bir bileşeni bulur | Kapsayan orman üretir |
Kruskal’ın maliyetine sıralama egemendir; kenarlar zaten sıralıysa maliyet neredeyse doğrusala iner. Prim, kenar sayısı düğüm sayısının karesine yaklaştığında öne çıkar.
Bir ayrım pratikte belirleyicidir: çizge bağlı değilse Kruskal her bileşen için bir ağaç üretir ve sonuç kapsayan ormandır; Prim ise yalnızca başlangıç düğümünün bileşenini kapsar.
Kümeleme Uygulaması
Kruskal’ın ara adımları tek başına anlamlıdır: algoritma kenar eklediğinde tane bileşen kalmıştır ve bu bileşenler, birbirine en uzak duran kümedir.
Bu, tek bağlantılı kümeleme yöntemidir: noktalar arası uzaklıklar kenar ağırlığı sayılır, MST kurulur ve en ağır kenar atılır. Sonuç, kümeler arası en küçük uzaklığı en büyükleyen bölünmedir.
Aynı yapı, ağ tasarımı ve yaklaşık çözümlerde de kullanılır: gezgin satıcı probleminin metrik durumunda MST, eniyi turun uzunluğu için bir alt sınır verir ve iki katını aşmayan bir tur üretmeye yarar.
Özet
- Kapsayan ağaç tüm düğümleri çevrimsiz bağlar ve kenar içerir; minimum kapsayan ağaç bunların en hafifidir.
- Kesme özelliği, herhangi bir kesmeyi geçen en hafif kenarın bir MST’de bulunduğunu söyler ve açgözlü seçimleri gerekçelendirir.
- Prim ağacı büyütür ve öncelik kuyruğu kullanır; maliyeti ’dir.
- Kruskal kenarları sıralar ve birleşim-bulma ile çevrim denetler; maliyeti ’dir.
- Yol sıkıştırma ve dereceye göre birleştirme, birleşim-bulma işlemlerini amortize olarak neredeyse sabit yapar.
- Kruskal’ın ara durumları kümeleme üretir; MST ayrıca yaklaşık çözümlerde alt sınır sağlar.
Sonraki Adım
Şimdiye kadar kenarlar maliyet taşıyordu. Sonraki ders kenarlara farklı bir anlam yükler: kapasite. Bir ağdan kaynaktan hedefe en çok ne kadar akıtılabilir sorusu, artık çizge üzerinde tanımlı bir eniyileme problemidir ve yanıtı beklenmedik bir eşitlikle — en büyük akış ile en küçük kesme arasındaki eşitlikle — gelir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.