Bölüm 12: Kümeler
Klasik Soru · Kümeler
Soru 1: Küme İşlemleri
Programın çıktısı nedir? TreeSet ile HashSet arasındaki farkı yazınız.
Set<Integer> a =
new TreeSet<>(List.of(5, 1, 9, 3, 7));
Set<Integer> b =
new HashSet<>(List.of(3, 4, 5, 6));
Set<Integer> k = new TreeSet<>(a);
k.retainAll(b);
Set<Integer> f = new TreeSet<>(a);
f.removeAll(b);
Set<Integer> u = new TreeSet<>(a);
u.addAll(b);
System.out.println(k + " " + f + " "
+ u.size() + " " + a.add(3));Cevabı göster
retainAll= kesişim A ∩ B = [3, 5]removeAll= fark A − B = [1, 7, 9]addAll= birleşim = [1, 3, 4, 5, 6, 7, 9] → boyut 7- 3 zaten A'da:
addfalse döner (küme tekrar tutmaz).
Çıktı: [3, 5] [1, 7, 9] 7 false
TreeSet: kırmızı-siyah ağaç, elemanlar sıralı, işlemler O(log n). HashSet: hash tablosu, sıra garantisi yok, ortalama O(1).
Klasik Soru · Kümeler
Soru 2: Union-Find İzleme
0..7 elemanlı ayrık küme yapısı ata[i] = i, rutbe[i] = 0 ile başlıyor. Rütbeye göre birleştirme: küçük rütbeli kök büyüğün altına bağlanır; eşitse kok1.ata = kok2 yapılır ve kok2'nin rütbesi 1 artar. Yol kısaltma uygulanır.union(0,1), union(2,3), union(4,5), union(1,3), union(6,7), union(5,7), union(3,7)
(a) Son ata ve rutbe dizileri? (b) Ardından find(0) çağrılınca ata nasıl değişir?
Cevabı göster
- union(0,1): 0 → 1, r[1]=1 · union(2,3): 2 → 3, r[3]=1 · union(4,5): 4 → 5, r[5]=1
- union(1,3): kökler 1 ve 3, rütbe eşit → 1 → 3, r[3]=2
- union(6,7): 6 → 7, r[7]=1 · union(5,7): eşit → 5 → 7, r[7]=2
- union(3,7): kökler 3 ve 7, rütbe eşit (2) → 3 → 7, r[7]=3
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| ata | 1 | 3 | 3 | 7 | 5 | 7 | 7 | 7 |
| rutbe | 0 | 1 | 0 | 2 | 0 | 1 | 0 | 3 |
| find(0) sonrası ata | 7 | 7 | 3 | 7 | 5 | 7 | 7 | 7 |
(b) find(0) yolu 0 → 1 → 3 → 7. Yol kısaltma ile yol üzerindeki 0 ve 1 doğrudan köke (7) bağlanır.
Rütbe + yol kısaltma ile işlem başına amortize ≈ O(α(n)), pratikte sabit.
Klasik Soru · Kümeler
Soru 3: Kruskal ile Minimum Kapsayan Ağaç
Ağırlıklı yönsüz çizgenin minimum kapsayan ağacını (MST) Kruskal algoritmasıyla bulunuz. Kenarları hangi sırayla incelediğinizi, hangilerinin neden reddedildiğini ve toplam ağırlığı yazınız. Union-Find bu algoritmada ne işe yarar?
Cevabı göster
| Kenar | Ağırlık | Karar |
|---|---|---|
| E–F | 1 | eklendi |
| B–D | 2 | eklendi |
| A–C | 3 | eklendi |
| C–E | 4 | eklendi |
| B–C | 5 | eklendi |
| A–B | 6 | döngü → reddedildi |
MST kenarları: E–F (1), B–D (2), A–C (3), C–E (4), B–C (5). V − 1 = 5 kenar seçilince durulur.
Toplam ağırlık: 15. A–B (6) reddedilir çünkü A ve B zaten C üzerinden bağlı.
Union-Find: kenarın iki ucu için find(u) == find(v) ise kenar döngü oluşturur; değilse union(u, v). Toplam O(E log E).
Klasik Soru · Kümeler
Soru 4: equals ve hashCode
Ogrenci sınıfı yalnızca equals metodunu (numaraya göre) geçersiz kılıyor, hashCode'u kılmıyor. Çıktı nedir? Neden? Nasıl düzeltilir?
class Ogrenci { int no; String ad; Ogrenci(int no, String ad) { this.no = no; this.ad = ad; } @Override public boolean equals(Object o) { return o instanceof Ogrenci x && x.no == no; } } Set<Ogrenci> s = new HashSet<>(); s.add(new Ogrenci(7, "Ali")); s.add(new Ogrenci(7, "Ali")); System.out.println(s.size() + " " + s.contains(new Ogrenci(7, "Ali")));
Cevabı göster
Çıktı büyük olasılıkla: 2 false
HashSet önce hashCode ile kovayı bulur, sonra yalnızca o kovada equals ile karşılaştırır. Varsayılan hashCode nesne kimliğine dayanır; eşit iki öğrenci farklı kovalara düşer, equals hiç çağrılmaz.
Sözleşme: a.equals(b) ise a.hashCode() == b.hashCode() olmalıdır.
@Override public int hashCode() { // equals ile aynı alan kullanılır return Integer.hashCode(no); }
Düzeltme sonrası çıktı: 1 true. TreeSet kullanılsaydı compareTo/Comparator tutarlı olmalıydı.
Çoktan Seçmeli · Kümeler
Test 1
A = {1, 2, 3, 4}, B = {3, 4, 5} için simetrik fark A Δ B nedir?
Yalnızca birinde olanlar: (A − B) ∪ (B − A) = {1, 2} ∪ {5}.
HashSet'e sırasıyla "a", "b", "a", "c", "b" eklenince size() kaçtır?
Küme tekrar tutmaz; tekrar eden add çağrıları false döndürür. {a, b, c}.
Çoktan Seçmeli · Kümeler
Test 2
Yol kısaltma ve rütbeye göre birleştirme birlikte kullanıldığında Union-Find işlemlerinin amortize maliyeti nedir?
α ters Ackermann fonksiyonudur; evrendeki atom sayısı kadar n için bile ≤ 4.
Kruskal algoritması bir kenarı neden reddeder?
find(u) == find(v) ise u ve v zaten bağlıdır; kenarı eklemek döngü oluşturur.
Adım Adım Çalıştırma · Kümeler
Union-Find ve Yol Kısaltma
Basit birleştirme (rütbesiz) ve yol kısaltmalı find ile işlemleri izleyin.
Önce tahmin edin: find(0) sonrası ata dizisi ne olur?
Her eleman kendi kümesinin köküdür.
union(0,1): find(0) = 0, find(1) = 1 → ata[0] = 1.
union(1,2): find(1) = 1, find(2) = 2 → ata[1] = 2.
union(3,4): find(3) = 3, find(4) = 4 → ata[3] = 4.
union(2,4): find(2) = 2, find(4) = 4 → ata[2] = 4.
find(0): 0 → 1 → 2 → 4 zinciri boyunca özyinelemeli çağrılar.
ata[4] = 4: kök bulundu, 4 döner.
Dönüşte yol kısaltma: ata[2] = 4 (zaten), ata[1] = 4.
ata[0] = 4. Artık 0 ve 1 doğrudan köke bağlı: sonraki find'lar tek adım.
int[] ata = {0, 1, 2, 3, 4, 5}; int find(int x) { if (ata[x] != x) ata[x] = find(ata[x]); return ata[x]; } void union(int a, int b) { ata[find(a)] = find(b); } // union(0,1); union(1,2); // union(3,4); union(2,4); // find(0);
Ucu Açık Soru · Kümeler
Arkadaş Grupları
- Bir sosyal ağda "kaç ayrı arkadaş grubu (bağlı bileşen) var?" ve "A ile B aynı grupta mı?" soruları soruluyor. Arkadaşlıklar sürekli ekleniyor, hiç silinmiyor. Union-Find mı, her sorguda BFS/DFS mi? Karşılaştırın.
- Arkadaşlıklar silinebilseydi Union-Find neden yetersiz kalırdı? Ne yapılabilir?
Örnek cevap ve değerlendirme ölçütleri
1. Union-Find: her yeni arkadaşlık union(a, b), sorgu find(a) == find(b), işlem başına amortize ≈ O(1). Grup sayısı bir sayaçla tutulur: başlangıçta n, gerçekleşen her birleşmede 1 azalır. BFS/DFS her sorguda O(V + E) sürer; sık sorgu ve sürekli eklemede çok yavaştır. Çizge değişmiyor ve tek seferlik analiz yapılıyorsa BFS/DFS de yeterlidir.
2. Union-Find kümeleri yalnızca birleştirir, bölemez. Bir kenar silinince grup ikiye ayrılabilir ama yapı bunu ifade edemez. Çözümler: silmeden sonra etkilenen bileşende BFS/DFS ile yeniden hesaplama; silmeler biliniyorsa işlemleri ters sırada çalıştırmak (silmeyi eklemeye çevirmek); ya da dinamik bağlantılılık için daha gelişmiş yapılar.
Değerlendirme: iki yaklaşımın karmaşıklık karşılaştırması · bileşen sayacının tutulması · Union-Find'ın bölme yapamadığının fark edilmesi · makul bir alternatif.