Bölüm 16: Kümeler ve Union-Find
Çoktan Seçmeli · Ayrık Kümeler
Soru 1: Kaç Küme?
ata = [2, −1, −1, 1, 2, 3, −1, 6] dizisi kaç kümeyi gösterir ve Kume_Bul_1(5) ne döndürür?
C: Negatif değerli hücreler 1, 2 ve 6'dır: üç kök, üç küme ({1, 3, 5}, {0, 2, 4}, {6, 7}). 5'ten başlayan yol 5 → 3 → 1'dir ve ata[1] = −1 olduğu için 1 döner (2 ilerleme). A ilk ilerlemede durur; 3'ün ebeveyni olduğu için 3 kök değildir.
Çıktı Sorusu · Dizi ile Gerçekleme
Soru 2: Kök Yerine Düğüm
Aşağıdaki kod ne yazdırır? Son birleşimde amaç 0 ile 4'ün kümelerini birleştirmektir; amaç gerçekleşir mi?
int ata[6] = {-1, -1, -1, -1, -1, -1}; Birlesim_1(0, 1); Birlesim_1(2, 3); Birlesim_1(1, 3); Birlesim_1(0, 4); for (int i = 0; i < 6; i++) printf("%d ", Kume_Bul_1(i));
Cevabı göster
Çıktı: 4 3 3 3 4 5
- İlk üç çağrıdan sonra 0 → 1 → 3 ve 2 → 3 bağları vardır: {0, 1, 2, 3} tek küme.
Birlesim_1(0, 4)yalnızata[0] = 4yazar. 0 kök değildir; 1'e olan bağı kopar ve 0 tek başına 4'ün altına geçer. Sonuç iki küme: {1, 2, 3} ve {0, 4}. Amaç gerçekleşmez.
Birleşime kökler verilmelidir: Birlesim_1(Kume_Bul_1(0), Kume_Bul_1(4)); → ata[3] = 4, çıktı 4 4 4 4 4 5.
Çoktan Seçmeli · Dizi ile Gerçekleme
Soru 3: Kendine Bağlanan Kök
5 ve 7 aynı kümededir ve kümenin kökü 3'tür. Yanlışlıkla Birlesim_1(3, 3) çağrılıyor, ardından Kume_Bul_1(5). Ne olur?
A: Birlesim_1 hiçbir denetim yapmadan ata[i] = j yazar: ata[3] = 3. Kök artık negatif değil, kendini gösteriyor. Kume_Bul_1(5) 3'e varınca ata[3] >= 0 hep doğru kalır ve i = ata[i] yine 3 verir: sonsuz döngü. Bu yüzden birleşimden önce Kume_Bul ile bulunan iki kökün farklı olduğu denetlenir.
Çoktan Seçmeli · Analiz
Soru 4: Yıldız Biçimli Birleşim
n tek öğeli kümeyle başlanıyor. Birlesim_1(1, 0), Birlesim_1(2, 0), …, Birlesim_1(n−1, 0) çağrılıyor, ardından her i için Kume_Bul_1(i). Toplam kaç ilerleme (i = ata[i]) yapılır?
D: Her yeni öğe doğrudan köke (0) bağlanır; ağaç yıldızdır ve iki düzeylidir. Kume_Bul_1(0) hiç, diğer n − 1 çağrı birer ilerleme yapar (sayaçlı programda n = 8 için 7). Zincir üreten Birlesim_1(i, i+1) sırasında ise toplam n(n − 1)/2 olur. Birleşim sayısı aynıdır; maliyeti hangi ağacın hangisine bağlandığı belirler.
Çoktan Seçmeli · Ağırlıklı Kural
Soru 5: Argüman Sırası
ata = [−2, 0, −4, 2, 2, 2] iken Birlesim_2(2, 0) çağrılıyor. Dizi ne olur?
B: p = −4 + (−2) = −6. ata[2] = −4 > ata[0] = −2 yanlıştır (2 kümesinde 4, 0 kümesinde 2 düğüm), else dalı çalışır: ata[0] = 2, ata[2] = −6. Birlesim_2(0, 2) de aynı diziyi verir; kural argüman sırasından bağımsız olarak küçük ağacı büyüğe bağlar. A büyüğü küçüğe bağlar, C düğüm sayısını günlemeyi unutur.
Adım Adım Çalıştırma · Ağırlıklı Kural
Soru 6: Ağırlıklı Birleşimle Orman
n = 7, ata tümüyle −1. Sırasıyla Birlesim_2(1,2), (3,1), (0,4), (1,0), (5,6), (5,1) çağrılıyor. Son dizi nedir, en derin düğüm kaçıncı düzeydedir?
Yedi tek öğeli küme; her kök −1 tutar (bir düğüm).
Birlesim_2(1, 2): −1 > −1 yanlış: eşit boyda j, i'ye bağlanır. ata[2] = 1, ata[1] = −2.
Birlesim_2(3, 1): −1 > −2 doğru: 3'ün ağacı küçük, 1'e bağlanır. ata[1] = −3.
Birlesim_2(0, 4): Eşit boy: 4, 0'a bağlanır. İki ağaç: 1 kökünde 3, 0 kökünde 2 düğüm.
Birlesim_2(1, 0): −3 > −2 yanlış: 0'ın ağacı (2 düğüm) 1'in altına girer. 4 artık 3. düzeyde.
Birlesim_2(5, 6): Eşit boy: 6, 5'e bağlanır; ata[5] = −2.
Birlesim_2(5, 1): −2 > −5 doğru: 5'in ağacı 1'e bağlanır, ata[1] = −7. En derin düğümler (4 ve 6) 3. düzeyde: ⌊lg 7⌋ + 1 = 3.
void Birlesim_2(int i, int j) { int p = ata[i] + ata[j]; /* -(toplam) */ if (ata[i] > ata[j]) { /* i'de az düğüm */ ata[i] = j; ata[j] = p; } else { /* j'de az/eşit */ ata[j] = i; ata[i] = p; } }
Çoktan Seçmeli · Ağırlıklı Kural
Soru 7: Dört Düzey İçin
Yalnız Birlesim_2 ile kurulmuş bir ağacın düzey sayısı 4'tür. Ağaçta en az kaç düğüm vardır?
B: Lemmaya göre düzey sayısı ⌊lg m⌋ + 1'i aşamaz. 4 ≤ ⌊lg m⌋ + 1 için ⌊lg m⌋ ≥ 3, yani m ≥ 8. Bir düğümün düzeyi ancak ağacı eşit ya da daha büyük bir ağaca bağlanınca artar ve ağaç en az iki katına çıkar: 1 → 2 → 4 → 8. Eşit boylu ağaçları ikişer ikişer birleştiren 8 düğüm bu sınıra ulaşır.
Hata Bulma · Ağırlıklı Kural
Soru 8: Ters Karşılaştırma
Bu Birlesim_2 sürümünde iki hata var. ata = [−2, 0, −1] iken Birlesim_2(0, 2) çağrılırsa dizi ne olur, ardından Kume_Bul_1(1) ne yapar? Hataları düzeltin.
void Birlesim_2(int i, int j) { if (ata[i] < ata[j]) { /* i'de az düğüm */ ata[i] = j; ata[j] = ata[i] + ata[j]; } else { ata[j] = i; ata[i] = ata[i] + ata[j]; } }
Cevabı göster
−2 < −1doğru: iki düğümlü ağaç tek düğümlünün altına girer. Negatiflerde büyük olan daha az düğümdür; doğrusu>.- Toplam,
ata[0]ezildikten sonra alınıyor:ata[2] = 2 + (−1) = 1. Dizi[2, 0, 1]: hiç kök yok.
1 → 0 → 2 → 1 çevrimi yüzünden Kume_Bul_1(1) sonsuz döngüye girer. Düzeltme: önce p = ata[i] + ata[j], karşılaştırma ata[i] > ata[j]. Doğru sürüm [−3, 0, 0] verir.
Adım Adım Çalıştırma · Azaltma Kuralı
Soru 9: Zincirde Azaltma
ata = [−6, 0, 1, 2, 3, 0] iken Kume_bul_2(4) çağrılıyor. İki döngü kaçar tur döner, dizi ne olur?
Birlesim_1 ile oluşmuş zincir: 4 → 3 → 2 → 1 → 0. Kume_bul_2(4) çağrıldı.
ata[4] = 3 ≥ 0 → r = 3.
ata[3] = 2 ≥ 0 → r = 2.
ata[2] = 1 ≥ 0 → r = 1.
ata[1] = 0 ≥ 0 → r = 0. ata[0] = −6 < 0: kök bulundu (4 ilerleme).
4 doğrudan köke bağlandı; i = s = 3.
3 doğrudan köke bağlandı; i = s = 2.
2 doğrudan köke bağlandı; i = s = 1.
ata[1] zaten 0: fazladan bir tur; i = 0.
i == r: return 0. Yol üzerindeki her düğüm artık köke bağlı; ikinci Kume_bul_2(4) tek ilerleme yapar.
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]; ata[i] = r; i = s; } return r; }
Çoktan Seçmeli · Azaltma Kuralı
Soru 10: Ebeveyni Saklamadan
s değişkeni kaldırılmış bu sürümle ata = [−6, 0, 1, 2, 3, 0] iken Kume_bul_2(4) çağrılırsa ne olur?
int Kume_bul_2(int i) { int r; for (r = i; ata[r] >= 0; r = ata[r]) ; while (i != r) { ata[i] = r; i = ata[i]; } return r; }
C: ata[4] = 0 yazıldıktan sonra i = ata[4] 0 olur, yani r'ye eşit; döngü ilk turda biter. Dizi [−6, 0, 1, 2, 0, 0]: kök doğru bulunur, 3, 2 ve 1 eski ebeveynlerine bağlı kalır (kopmazlar) ama yolları kısalmaz. Ebeveyn, bağ değiştirilmeden önce saklanmalıdır.
Kod Yazma · Azaltma Kuralı
Soru 11: Yol Yarılama
Kume_bul_2 yolu iki geçişte kısaltır. Tek geçişte çalışan bir azaltma da yazılabilir: yoldaki düğüm, ebeveyninin ebeveynine bağlanır ve o düğüme geçilir (yol yarılama). int bul_yari(int i) yordamını yazın. ata = [−6, 0, 1, 2, 3, 0] iken bul_yari(4) sonrası dizi ne olur?
Cevabı göster
int bul_yari(int i) { while (ata[i] >= 0) { if (ata[ata[i]] >= 0) /* büyük ata var */ ata[i] = ata[ata[i]]; i = ata[i]; } return i; }
i = 4: ata[4] = 2, i = 2. i = 2: ata[2] = 0, i = 0: kök, 0 döner. Dizi [−6, 0, 0, 2, 2, 0].
4'ün yolu 4 kenardan 2 kenara indi; atlanan 3'ün bağı değişmez. Tek döngü yeter, ek değişken gerekmez.
Çoktan Seçmeli · Özet
Soru 12: Yalnız Ağırlıklı Kural
Azaltma kuralı kullanılmıyor: birleşimler Birlesim_2, aramalar Kume_Bul_1 ile yapılıyor. n − 1 birleşim ve m küme bulmanın en kötü toplam maliyeti nedir?
A: Her birleşim O(1)'dir: toplam O(n). Ağırlıklı kural derinliği ⌊lg n⌋ + 1 ile sınırladığı için her arama O(lg n): m arama O(m lg n). B kuralsız Birlesim_1'in sınırıdır (zincir); C ancak azaltma kuralı da eklenince elde edilir.
Çıktı Sorusu · Uygulamalar
Soru 13: Bağlı mı Sorguları
ata[0..4] −1 ile başlıyor; islem[k][0] 1 ise bağla, 0 ise sor. Program ne yazdırır, sonda ata nedir?
int islem[7][3] = {{1,0,1}, {1,2,3}, {0,1,3}, {1,1,2}, {0,0,3}, {1,3,0}, {0,4,0}}; for (int k = 0; k < 7; k++) { int a = Kume_bul_2(islem[k][1]); int b = Kume_bul_2(islem[k][2]); if (islem[k][0] == 0) printf(a == b ? "E " : "H "); else if (a != b) Birlesim_2(a, b); else printf("gereksiz "); }
Cevabı göster
Çıktı: H E gereksiz H
- (0,1) ve (2,3) bağlanır:
ata = [−2, 0, −2, 2, −1]. Soru (1,3): kökler 0 ve 2 → H. - Bağla (1,2): kökler 0 ve 2, eşit boy → 2, 0'a bağlanır,
ata[0] = −4. - Soru (0,3):
Kume_bul_2(3)3 → 2 → 0 yolunu bulur ve 3'ü köke bağlar → E. - Bağla (3,0): ikisinin kökü 0 → gereksiz. Soru (4,0): 4 tek başına → H.
Son dizi [−4, 0, 0, 0, −1]: {0, 1, 2, 3} ve {4}. Ağ bağlantısı sorularının çevrimiçi yanıtlanması böyle yapılır.
Adım Adım Çalıştırma · Uygulamalar
Soru 14: Döngüyü Kapatan Kenar
Yönsüz bir çizgenin kenarları sırayla işleniyor. dongu_bul(5, 6, u, v) neyi döndürür?
Kenarlar sırayla: (0,1), (2,3), (1,3), (4,2), (0,2), (3,4). Her düğüm ayrı küme.
(0,1): kökler 0 ve 1 farklı → Birlesim_2(0, 1).
(2,3): kökler 2 ve 3 farklı → Birlesim_2(2, 3).
(1,3): kökler 0 ve 2 farklı → Birlesim_2(0, 2).
(4,2): kökler 4 ve 0 farklı → Birlesim_2(4, 0).
(0,2): iki ucun kökü de 0. Aralarında zaten yol var (0 – 1 – 3 – 2); kenar döngüyü kapatır: return 4.
int dongu_bul(int n, int m, int u[], int v[]) { for (int i = 0; i < n; i++) ata[i] = -1; for (int e = 0; e < m; e++) { int a = Kume_bul_2(u[e]), b = Kume_bul_2(v[e]); if (a == b) return e; /* döngü */ Birlesim_2(a, b); } return -1; /* orman */ }
Çoktan Seçmeli · Uygulamalar
Soru 15: Desteklenmeyen İşlem
Ebeveyn dizisi, ağırlıklı kural ve azaltma kuralıyla kurulan union-find yapısı aşağıdakilerden hangisini verimli biçimde yapamaz?
D: Yapı yalnız birleştirir: hangi düğümün hangi birleşimle geldiği saklanmaz, azaltma kuralı da ağaç biçimini değiştirir. Bir ağ bağlantısı silindiğinde yapı baştan kurulur. A iki Kume_bul ile, B kökteki −ata[kök] ile, C Birlesim_2 ile yapılır.
Kod Yazma · Özet
Soru 16: En Büyük Küme
Birlesim_2 ile kurulmuş bir ata dizisinde en çok öğeli kümenin kökünü döndüren, öğe sayısını da işaretçi parametresiyle veren int en_buyuk(const int ata[], int n, int *boy) yordamını yazın. ata = [3, −3, −5, 2, 2, 2, −1, 1, 1] için sonuç nedir?
Cevabı göster
int en_buyuk(const int ata[], int n, int *boy) { int kok = -1, b = 0; for (int i = 0; i < n; i++) if (ata[i] < 0 && -ata[i] > b) { /* kök */ b = -ata[i]; kok = i; } if (boy != NULL) *boy = b; return kok; }
Kökler 1 (3 öğe), 2 (5 öğe) ve 6 (1 öğe): yordam 2 döndürür, *boy = 5.
Öğe sayısı kökte tutulduğu için hiçbir Kume_bul çağrısı gerekmez: tek geçiş, O(n). const diziyi değiştirmeyeceğini, NULL denetimi de çağıranın boyu istemeyebileceğini gösterir.