Bölüm 16: Kümeler ve Union-Find
Giriş
Öğrenme Çıktıları
İkili ayrık kümeleri çocuktan ataya bağlı ağaçlarla göstermek.
Tek bir ata dizisiyle Kume_Bul ve Birlesim yordamlarını yazmak.
Hangi işlem dizisinin ağacı dejenere ettiğini ve O(n²) maliyeti göstermek.
Ağırlıklı kural ve azaltma (collapsing) kuralını uygulamak, lemmaları yorumlamak.
Tarjan–van Leeuwen sonucunu ve ters Ackermann fonksiyonunu açıklamak.
Bağlantılı bileşen sayma ve Kruskal algoritmasında union-find'ı uygulamak.
Ayrık Kümeler
Kümelerde birleşim (union) ve bulma (find)
Evren {0, 1, …, n−1}; kümeler ikili ayrık (pairwise disjoint): S₁ ∩ S₂ = S₂ ∩ S₃ = S₁ ∩ S₃ = ∅. n = 10 için:
Her küme bir ağaçtır; bağlar çocuklardan atalara doğrudur (oklar yukarı).
Ayrık Kümeler
İki işlem ve birleşimin iki biçimi
Si ∪ Sj = {x : x ∈ Si ∨ x ∈ Sj}. Birleşme sonrası kümeler bağımsız olarak bulunmaz: S₁ ∪ S₂ = {0, 6, 7, 8, 1, 4, 9}.
i öğesini içeren kümeyi bulur (kümenin adı = ağacın kökü).
Kökler 0, 4, 2. Birleşim için köklerden birinin ata bilgisi diğer kökü gösterecek şekilde günlenir.
S₁ ∪ S₂: 4'ün atası 0 yapılır; S₂ ağacı 0'ın alt ağacı olur.
S₂ ∪ S₁: bu kez 0'ın atası 4 yapılır. İkisi de doğrudur; hangisinin seçileceği maliyeti etkiler.
Ayrık Kümeler
Kümenin adı: kök düğüm
| Küme adı | bağ (kök) |
|---|---|
| S₁ | → 0 |
| S₂ | → 4 |
| S₃ | → 2 |
Adlar metin olarak gerekiyorsa ayrı bir tabloda kökü gösteren bir bağ tutulur.
Birleşim ve Küme_bul algoritmalarını incelerken her küme adını ağacın kökü ile ilişkilendireceğiz: “S₁ kümesi” yerine “0 nolu küme” diyeceğiz.
Böylece Küme_bul(8) == Küme_bul(6) sorusu “8 ile 6 aynı kümede mi?” demektir.
Dizi ile Gerçekleme
Nasıl gerçekleyelim?
| Yöntem | Birleşim | Küme bul | Sorun |
|---|---|---|---|
| Bağlı liste | O(1) | O(n) | arama yavaş |
| Sıralı tutmak | — | — | sıralamak O(n lg n) |
| ata dizisi (ağaç) | O(1) | ağaç derinliği | derinliği küçük tutmalı |
Dizi ile Gerçekleme
ata dizisi: her indis atasına bir bağ
- 1 ve 9'un atası 4; 3 ve 5'in atası 2; 6, 7 ve 8'in atası 0.
- 0, 2 ve 4 kümelerin kökleridir:
ata[kök] = −1(negatif = kök).
#define N 10 int ata[N] = { -1, 4, -1, 2, -1, 2, 0, 0, 0, 4 };
Dizi ile Gerçekleme
İlk sürüm: Kume_Bul_1 ve Birlesim_1
int Kume_Bul_1(int i) { for (; ata[i] >= 0; i = ata[i]) ; /* negatife dek ilerle */ return i; } void Birlesim_1(int i, int j) { ata[i] = j; /* i kümesini j'ye bağla */ }
Ata bilgisi negatif bir değer olana (köke) dek ilerler; kökü döndürür.
i ve j kümeleri (kökleri) aktarıldıktan sonra biri diğerinin alt ağacı olacak şekilde günlenir: O(1).
i ve j kök olmalıdır; değilse önce Kume_Bul_1 ile kökler bulunur.
Dizi ile gerçekleme · kodu adım adım çalıştır
Kod izleme: Kume_Bul_1(9), Birlesim_1(4, 0)
Kume_Bul_1(9): 9 hangi kümede?
ata[9] = 4 ≥ 0 → i = ata[i] = 4.
ata[4] = −1 < 0: kök. return 4 → 9, 4 nolu kümede.
Birlesim_1(4, 0): iki kök veriliyor.
ata[4] = 0: 4'ün ağacı 0'ın altına girdi. Tek atama: O(1).
Yeniden Kume_Bul_1(9): yol bir düğüm uzadı.
ata[9] = 4 ≥ 0 → i = 4.
ata[4] = 0 ≥ 0 → i = 0.
return 0: artık 9, 0 nolu kümede. Maliyet = düğümün derinliği.
int Kume_Bul_1(int i) { for (; ata[i] >= 0; i = ata[i]) ; /* negatife dek ilerle */ return i; } void Birlesim_1(int i, int j) { ata[i] = j; /* i kümesini j'ye bağla */ }
Analiz · kodu adım adım çalıştır
Kod izleme: Birlesim_1(0,1) … Birlesim_1(3,4), Kume_Bul_1(0)
n = 5 tek öğeli küme: Si = {i}, tümünde ata[i] = −1.
Birlesim_1(0, 1): ata[0] = 1. Her çağrı O(1).
Birlesim_1(1, 2): ata[1] = 2. Her çağrı O(1).
Birlesim_1(2, 3): ata[2] = 3. Her çağrı O(1).
Birlesim_1(3, 4): ata[3] = 4. Her çağrı O(1).
Ağaç bir zincire dönüştü; derinlik n. Şimdi Kume_Bul_1(0).
i = ata[0] = 1 (1. ilerleme).
i = ata[1] = 2 (2. ilerleme).
i = ata[2] = 3 (3. ilerleme).
i = ata[3] = 4 (4. ilerleme).
return 4 — 4 = n−1 ilerleme. Kume_Bul_1(1), (2)… sırayla n−2, n−3, … ilerler: toplam Σ = O(n²).
int Kume_Bul_1(int i) { for (; ata[i] >= 0; i = ata[i]) ; /* negatife dek ilerle */ return i; } void Birlesim_1(int i, int j) { ata[i] = j; /* i kümesini j'ye bağla */ }
Analiz
Kume_Bul_1 ve Birlesim_1'in analizi
/* n tek öğeli kümeyle başla */
Birlesim_1(0, 1);
Birlesim_1(1, 2);
...
Birlesim_1(n-2, n-1);
Kume_Bul_1(0);
Kume_Bul_1(1);
...
Kume_Bul_1(n-1);- n−1 birleşim: her biri sabit → toplam O(n), sorun yok.
- Ağaç zincire döner; derinlik n olur.
- (n−i). düzeydeki i için Kume_Bul_1 O(i) sürer: 0 için n−1, 1 için n−2… ilerleme.
- Tek bir Kume_Bul_1 O(n)'e, n tanesi toplamda Σ i = O(n²)'ye çıkar.
Analiz
Bir parantez: en kötü durumu test etmek
Worst case analizinde kod, çakılacağı ya da işletiminin en uzun süreceği verilerle test edilmelidir. Bunu saptamak zordur.
Zincir üreten birleşim sırası, Kume_Bul_1 için tam da böyle bir girdidir: rastgele testler onu kolayca kaçırır.
Ağırlıklı Kural
Ağırlıklı kural (weighting rule)
Tanım: i ağacındaki düğüm sayısı j ağacındakinden azsa j, i'nin atası; aksi hâlde i, j'nin atası yapılır. (Düğüm sayısı çok olan ata olur.)
Ağırlıklı Kural
Birlesim_2: kökte eksi düğüm sayısı
void Birlesim_2(int i, int j) { /* ağırlıklı kural; i ve j kök olmalı */ int p = ata[i] + ata[j]; /* -(toplam düğüm) */ if (ata[i] > ata[j]) { /* i'de daha az düğüm */ ata[i] = j; /* i'yi j'ye bağla */ ata[j] = p; /* j'nin sayısını günle */ } else { /* j'de az ya da eşit */ ata[j] = i; ata[i] = p; } }
Kökteki değer −(düğüm sayısı): tek öğeli küme −1, 8 öğeli küme −8.
>?Değerler negatif: ata[i] > ata[j], i'nin daha az düğümü olduğu anlamına gelir (−2 > −4).
Hâlâ O(1); ek dizi gerekmez.
Ağırlıklı kural · kodu adım adım çalıştır
Kod izleme: Birlesim_2 (0,1) (2,3) (4,5) (6,7)
Birlesim_2(0, 1): 0 kümesinde 1, 1 kümesinde 1 düğüm.
p = -1 + (-1) = -2: birleşik kümede 2 düğüm.
-1 > -1 yanlış (j'de az ya da eşit) → else: j, i'ye bağlanacak.
ata[1] = 0, ata[0] = -2. En derin düğüm 2. düzeyde.
Birlesim_2(2, 3): 2 kümesinde 1, 3 kümesinde 1 düğüm.
p = -1 + (-1) = -2: birleşik kümede 2 düğüm.
-1 > -1 yanlış (j'de az ya da eşit) → else: j, i'ye bağlanacak.
ata[3] = 2, ata[2] = -2. En derin düğüm 2. düzeyde.
Birlesim_2(4, 5): 4 kümesinde 1, 5 kümesinde 1 düğüm.
p = -1 + (-1) = -2: birleşik kümede 2 düğüm.
-1 > -1 yanlış (j'de az ya da eşit) → else: j, i'ye bağlanacak.
ata[5] = 4, ata[4] = -2. En derin düğüm 2. düzeyde.
Birlesim_2(6, 7): 6 kümesinde 1, 7 kümesinde 1 düğüm.
p = -1 + (-1) = -2: birleşik kümede 2 düğüm.
-1 > -1 yanlış (j'de az ya da eşit) → else: j, i'ye bağlanacak.
ata[7] = 6, ata[6] = -2. En derin düğüm 2. düzeyde.
void Birlesim_2(int i, int j) { /* ağırlıklı kural; i ve j kök olmalı */ int p = ata[i] + ata[j]; /* -(toplam düğüm) */ if (ata[i] > ata[j]) { /* i'de daha az düğüm */ ata[i] = j; /* i'yi j'ye bağla */ ata[j] = p; /* j'nin sayısını günle */ } else { /* j'de az ya da eşit */ ata[j] = i; ata[i] = p; } }
Ağırlıklı kural · kodu adım adım çalıştır
Kod izleme: Birlesim_2 (0,2) (4,6) (0,4)
Birlesim_2(0, 2): 0 kümesinde 2, 2 kümesinde 2 düğüm.
p = -2 + (-2) = -4: birleşik kümede 4 düğüm.
-2 > -2 yanlış (j'de az ya da eşit) → else: j, i'ye bağlanacak.
ata[2] = 0, ata[0] = -4. En derin düğüm 3. düzeyde.
Birlesim_2(4, 6): 4 kümesinde 2, 6 kümesinde 2 düğüm.
p = -2 + (-2) = -4: birleşik kümede 4 düğüm.
-2 > -2 yanlış (j'de az ya da eşit) → else: j, i'ye bağlanacak.
ata[6] = 4, ata[4] = -4. En derin düğüm 3. düzeyde.
Birlesim_2(0, 4): 0 kümesinde 4, 4 kümesinde 4 düğüm.
p = -4 + (-4) = -8: birleşik kümede 8 düğüm.
-4 > -4 yanlış (j'de az ya da eşit) → else: j, i'ye bağlanacak.
ata[4] = 0, ata[0] = -8. En derin düğüm 4. düzeyde.
void Birlesim_2(int i, int j) { /* ağırlıklı kural; i ve j kök olmalı */ int p = ata[i] + ata[j]; /* -(toplam düğüm) */ if (ata[i] > ata[j]) { /* i'de daha az düğüm */ ata[i] = j; /* i'yi j'ye bağla */ ata[j] = p; /* j'nin sayısını günle */ } else { /* j'de az ya da eşit */ ata[j] = i; ata[i] = p; } }
Ağırlıklı Kural
Soru: Ağacı ne kadar dejenere eder, bozar?
Birlesim_2 izlemelerindeki gibi). Her birleşimde küçük ağacın tüm düğümlerinin derinliği 1 artar, ama ağaç en az iki katına çıkar.Ağırlıklı Kural
Lemma: ağırlıklı birleşimle derinlik logaritmiktir
Birlesim_2 ile çeşitli kümelerin birleşimi sonucu elde edilmiş m düğümlü bir ağaç ise T'nin derinliği (düzey sayısı) ⌊lg m⌋ + 1'den büyük olamaz.- İspat fikri (tümevarım): bir düğümün derinliği yalnızca onun ağacı daha küçük ya da eşit bir ağaca bağlanınca 1 artar.
- O zaman düğümün yer aldığı ağaç en az iki katına çıkar.
- m düğüme en çok lg m kez iki katına çıkılabilir → derinlik ≤ ⌊lg m⌋ + 1.
Azaltma Kuralı
Azaltma kuralı (collapsing rule)
Son durumdaki yapıda Küme_bul(7) köke 3 ilerlemede ulaşır. Program Küme_bul(7)'yi tekrar tekrar işletirse maliyet katlanır. Düğüm sayısı az olanı çok olana eklesek bile derinlik artabiliyor; derinliği denetleyemediğimize göre bir adım ileri gideriz:
Azaltma Kuralı
Kume_bul_2: önce kök bulunur, sonra yol köke bağlanır
int Kume_bul_2(int i) { int r, s; for (r = i; ata[r] >= 0; r = ata[r]) ; /* kökü bul */ while (i != r) { /* yolu azalt */ s = ata[i]; /* i'nin atasını sakla */ ata[i] = r; /* i'yi doğrudan köke bağla */ i = s; /* atasına geç */ } return r; }
r kökü bulur (ata bilgisi negatif).
Yol üzerindeki her düğüm doğrudan r'ye bağlanır. Köke zaten bağlı bir düğüm için de (gereksiz ama zararsız) bir kez çalışır.
Aynı yoldaki sonraki aramalar bir çırpıda köke ulaşır.
Azaltma kuralı · kodu adım adım çalıştır
Kod izleme: Kume_bul_2(7)
Kume_bul_2(7).
ata[7] = 6 ≥ 0 → r = 6.
ata[6] = 4 ≥ 0 → r = 4.
ata[4] = 0 ≥ 0 → r = 0.
ata[0] = -8 < 0: kök r = 0.
i = 7 ≠ r: s = ata[7] = 6 saklanır.
ata[7] = 0: 7 doğrudan köke bağlandı; i = s = 6.
i = 6 ≠ r: s = ata[6] = 4 saklanır.
ata[6] = 0: 6 doğrudan köke bağlandı; i = s = 4.
i = 4 ≠ r: s = ata[4] = 0 saklanır.
ata[4] = 0 (zaten köke bağlıydı: fazladan bir tur); i = 0.
i == r: döngü biter, return 0. ata = [-8, 0, 0, 2, 0, 4, 0, 0].
int Kume_bul_2(int i) { int r, s; for (r = i; ata[r] >= 0; r = ata[r]) ; /* kökü bul */ while (i != r) { /* yolu azalt */ s = ata[i]; /* i'nin atasını sakla */ ata[i] = r; /* i'yi doğrudan köke bağla */ i = s; /* atasına geç */ } return r; }
Azaltma kuralı · kodu adım adım çalıştır
Kod izleme: yine Kume_bul_2(7), sonra Kume_bul_2(5)
Kume_bul_2(7).
ata[7] = 0 ≥ 0 → r = 0.
ata[0] = -8 < 0: kök r = 0.
i = 7 ≠ r: s = ata[7] = 0 saklanır.
ata[7] = 0 (zaten köke bağlıydı: fazladan bir tur); i = 0.
i == r: döngü biter, return 0. ata = [-8, 0, 0, 2, 0, 4, 0, 0].
Kume_bul_2(5).
ata[5] = 4 ≥ 0 → r = 4.
ata[4] = 0 ≥ 0 → r = 0.
ata[0] = -8 < 0: kök r = 0.
i = 5 ≠ r: s = ata[5] = 4 saklanır.
ata[5] = 0: 5 doğrudan köke bağlandı; i = s = 4.
i = 4 ≠ r: s = ata[4] = 0 saklanır.
ata[4] = 0 (zaten köke bağlıydı: fazladan bir tur); i = 0.
i == r: döngü biter, return 0. ata = [-8, 0, 0, 2, 0, 0, 0, 0].
int Kume_bul_2(int i) { int r, s; for (r = i; ata[r] >= 0; r = ata[r]) ; /* kökü bul */ while (i != r) { /* yolu azalt */ s = ata[i]; /* i'nin atasını sakla */ ata[i] = r; /* i'yi doğrudan köke bağla */ i = s; /* atasına geç */ } return r; }
Azaltma Kuralı
“For vardı, bir de while ekledik!”
| Çağrı | Kume_Bul_1 (ilerleme) | Kume_bul_2 (ilerleme + bağlama) |
|---|---|---|
| 1. Küme_bul(7) | 3 | 3 + 3 |
| 2. Küme_bul(7) | 3 | 1 + 1 |
| 10 kez Küme_bul(7) | 30 | 6 + 9·2 = 24 |
| Küme_bul(5) (sonra) | 2 | 2 + 2, ardından 1 |
Azaltma Kuralı
Lemma (Tarjan ve van Leeuwen): neredeyse sabit
k₁·(n + f·α(f+n, n)) ≤ T(f, u) ≤ k₂·(n + f·α(f+n, n))
- α: ters Ackermann fonksiyonu; çok yavaş artar.
- Evrendeki atom sayısı kadar öğe için bile uygulamada α ≤ 4.
- İşlem başına maliyet n'e bölünce neredeyse O(1).
/* Ackermann: çok hızlı büyür */
A(m, n) = n + 1 m = 0
= A(m-1, 1) m > 0, n = 0
= A(m-1, A(m, n-1)) m > 0, n > 0Azaltma Kuralı
Ackermann fonksiyonu ve tersi
| n = 0 | n = 1 | n = 2 | n = 3 | n = 4 | |
|---|---|---|---|---|---|
| m = 0 | 1 | 2 | 3 | 4 | 5 |
| m = 1 | 2 | 3 | 4 | 5 | 6 |
| m = 2 | 3 | 5 | 7 | 9 | 11 |
| m = 3 | 5 | 13 | 29 | 61 | 125 |
| m = 4 | 13 | 65533 | 265536−3 | … | … |
- A(5, 1) = 22…2 − 3 (65536 tane 2'nin üs kulesi): akıl almaz büyüklük.
- Ters: α(m, n) = min{ i ≥ 1 : A(i, ⌊m/n⌋) ≥ lg n } — A çok hızlı büyüdüğü için α çok yavaş büyür.
Ackermann fonksiyonunun birden çok tanımı vardır. Yukarıdaki tanımla A(3,4) = 125'tir; Horowitz'in kullandığı tanımda (A(1,j) = 2j …) ise A(3,1) = 16'dır. Hangi tanım seçilirse seçilsin sonuç değişmez: uygulamada α ≤ 4.
Azaltma Kuralı
Sınav sorusu: Ackermann fonksiyonunu özyinelemeli yazınız
Çözüm
unsigned long ackermann(unsigned long m, unsigned long n) { if (m == 0) return n + 1; if (n == 0) return ackermann(m - 1, 1); return ackermann(m - 1, ackermann(m, n - 1)); }
A(1,2) = A(0, A(1,1)) = A(0, A(0, A(1,0))) = A(0, A(0, A(0,1))) = A(0, A(0, 2)) = A(0, 3) = 4. A(4, 2) bile hiçbir bilgisayarda bitmez; yığın taşar.
Özyineleme · kodu adım adım çalıştır
Kod izleme: ackermann(1, 2)
ackermann(1, 2) çağrılır.
A(1,2): önce içteki A(1,1) hesaplanmalı.
A(1,1): önce içteki A(1,0) hesaplanmalı.
A(1,0): n = 0 → A(0, 1) çağrılır.
A(0,1): m = 0 → return 2.
A(1,0) = A(0,1) = 2, dönülür.
İç çağrı 2 verdi → dış çağrı A(0,2).
A(0,2): m = 0 → return 3.
A(1,1) = 3.
İç çağrı 3 verdi → dış çağrı A(0,3).
A(0,3): m = 0 → return 4.
A(1,2) = 4.
unsigned long ackermann(unsigned long m, unsigned long n) { if (m == 0) return n + 1; if (n == 0) return ackermann(m - 1, 1); return ackermann(m - 1, ackermann(m, n - 1)); }
Azaltma Kuralı
Bir parantez: bu lemmalar nereden geliyor?
Uygulama · kodu adım adım çalıştır
Kod izleme: bağlantılı bileşen sayma
Kenarlar u–v: (0,1), (2,3), (1,4), (3,2). Kaç bağlantılı bileşen var?
Her düğüm kendi kümesi: ata = −1, sayi = 6.
Kenar (0, 1): Kume_bul_2(0) = 0, Kume_bul_2(1) = 1.
0 ≠ 1: farklı bileşenler → Birlesim_2(0, 1), sayi = 5.
Kenar (2, 3): Kume_bul_2(2) = 2, Kume_bul_2(3) = 3.
2 ≠ 3: farklı bileşenler → Birlesim_2(2, 3), sayi = 4.
Kenar (1, 4): Kume_bul_2(1) = 0, Kume_bul_2(4) = 4.
0 ≠ 4: farklı bileşenler → Birlesim_2(0, 4), sayi = 3.
Kenar (3, 2): Kume_bul_2(3) = 2, Kume_bul_2(2) = 2.
a == b = 2: 3 ile 2 zaten aynı bileşende; kenar bir döngü kapatır, sayı değişmez.
return 3: bileşenler {0,1,4}, {2,3}, {5}. Toplam O(n + m·α).
int bilesen_say(int n, int m, int u[], int v[]) { for (int i = 0; i < n; i++) ata[i] = -1; int sayi = n; /* her düğüm ayrı */ for (int e = 0; e < m; e++) { int a = Kume_bul_2(u[e]), b = Kume_bul_2(v[e]); if (a != b) { /* farklı bileşen */ Birlesim_2(a, b); sayi--; } } return sayi; }
Uygulamalar
Union-find başka nerelerde?
i ≡ j bildirimlerinden sınıfları çıkarmak (Horowitz'in eşdeğerlik problemi).
“Bu iki bilgisayar bağlı mı?” sorularını çevrimiçi yanıtlamak (Sedgewick'in açılış örneği).
Aynı renkteki komşu pikselleri bölgelere ayırmak (bağlantılı bileşen etiketleme).
Izgarada üstten alta açık yol var mı? Monte Carlo deneylerinin çekirdeği.
Tip çıkarımında birleştirme (unification), takma ad (alias) analizi.
Tarjan'ın en yakın ortak ata algoritması.
Özet
Sınav sorusu: ata dizisinin içeriği
(i) İlk başta
ata dizisinin içeriği nedir?(ii)
Birlesim_2(4, 2); Kume_bul_2(5); Birlesim_2(0, 4); Kume_bul_2(7); Kume_bul_2(3); çağrılarından sonra ata ne olur? Not: (i) hatalıysa (ii) değerlendirilmez.Çözüm
ata = [-4, 4, -3, 2, -3, 2, 0, 0, 0, 4]— köklerde eksi düğüm sayısı (−4, −3, −3).- Birlesim_2(4,2): eşit (−3, −3) → 2, 4'e bağlanır, ata[4] = −6. Kume_bul_2(5): 5 → 2 → 4, 5 doğrudan 4'e bağlanır. Birlesim_2(0,4): −4 > −6 → 0, 4'e bağlanır, ata[4] = −10. Kume_bul_2(7): 7 → 0 → 4, 7 köke bağlanır. Kume_bul_2(3): 3 → 2 → 4, 3 köke bağlanır. Sonuç:
[4, 4, 4, 4, -10, 4, 0, 4, 0, 4].
Özet
Karmaşıklık özeti
| Sürüm | Birleşim | Küme bul (en kötü) | n−1 birleşim + m bulma |
|---|---|---|---|
| Birlesim_1 + Kume_Bul_1 | O(1) | O(n) | O(n + m·n) |
| Birlesim_2 (ağırlıklı) | O(1) | O(lg n) | O(n + m lg n) |
| Birlesim_2 + Kume_bul_2 | O(1) | O(lg n) tek işlem | O(n + m·α(m+n, n)) |
Bellek: yalnızca n tamsayılık ata dizisi. α ≤ 4 olduğundan son satır uygulamada doğrusaldır.
Özet
Sık yapılan hatalar
ata[i] = j i'nin alt ağacını koparır.ata dizisini −1 ile doldurmamak.Özet
Alıştırmalar
- n = 6, ata tümüyle −1.
Birlesim_2(0,1); Birlesim_2(2,3); Birlesim_2(0,2); Birlesim_2(4,5)sonrası ata? - Ağırlıklı kural kullanılırken 16 düğümlü bir ağacın derinliği en çok kaçtır?
- Ağırlık yerine yükseklik (rank) tutan bir birleşim kuralı yazın. Ne değişir?
- Kume_bul_2'yi özyinelemeli yazın (tek satırlık yol sıkıştırma).
- Kruskal'da bir kenarın reddedilmesi neden döngü demektir?
- n düğüm, m kenarlı bir çizgede bileşen sayısını bulmanın maliyeti nedir?
Cevapları göster
[-4, 0, 0, 2, -2, 4](0 kökünde 4 düğüm; 4 kökünde 2).- ⌊lg 16⌋ + 1 = 5 düzey.
- Kökte −(yükseklik) tutulur; eşitse biri bağlanır ve yükseklik 1 artar. Derinlik yine O(lg n); azaltma ile rank üst sınır olur.
int bul(int i) { return ata[i] < 0 ? i : (ata[i] = bul(ata[i])); }- Uçlar zaten aynı kümede, yani aralarında seçilmiş kenarlardan oluşan bir yol var; kenar yolu döngüye kapatır.
- O(n + m·α(m+n, n)) — pratikte doğrusal.
Özet
Bölüm özeti
Ayrık kümeler = kökü küme adı olan, çocuktan ataya bağlı ağaçlar.
Tek ata dizisi; kökte negatif değer.
Rastgele bağlama zincir üretir: n bulma O(n²).
Küçük ağaç büyüğe: derinlik ≤ ⌊lg m⌋ + 1.
Yol köke bağlanır; sonraki aramalar bir çırpıda.
İkisi birlikte: işlem başına α ≤ 4 — neredeyse O(1).
Özet
Kaynaklar ve devam
- E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, Bölüm 5.10 (Set Representation).
- T. H. Cormen vd., Introduction to Algorithms (CLRS), “Data Structures for Disjoint Sets” bölümü.
- R. Sedgewick, Algorithms in C, Bölüm 1 (union-find ile bağlantılılık).
- R. E. Tarjan, J. van Leeuwen, “Worst-case analysis of set union algorithms”, J. ACM 31(2), 1984.