Bölüm 13: Kümeler ve Ayrık Kümeler
Klasik Soru · Küme ADT
Soru 1: Set Metotlarıyla Küme Cebri
Programın çıktısı nedir? Kullanılan her metodun hangi küme işlemine karşılık geldiğini belirtiniz.
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:
addfalsedöner, küme değişmez.
Çıktı: [3, 5] [1, 7, 9] 7 false
Üç metot da kümeyi yerinde değiştirir; a'yı korumak için önce kopya (new TreeSet<>(a)) alınmıştır. TreeSet olduğu için sonuçlar sıralı yazdırılır.
Çoktan Seçmeli · Küme ADT
Soru 2: Kopya mı, Referans mı?
Programın çıktısı nedir?
Set<Integer> a =
new TreeSet<>(List.of(1, 2, 3, 4));
Set<Integer> b =
new TreeSet<>(List.of(3, 4, 5));
Set<Integer> kes = a;
kes.retainAll(b);
System.out.println(a + " " + kes.size());Doğru: C. kes = a kopya oluşturmaz; iki değişken aynı nesneyi gösterir. retainAll yerinde çalıştığı için a da [3, 4] olur. A şıkkı a'nın kopyalandığını varsayar; kesişimi a'yı bozmadan almak için new TreeSet<>(a) gerekir.
Çoktan Seçmeli · Küme Uygulamaları
Soru 3: Gezinme Sırası
Programın çıktısı nedir?
Set<String> l = new LinkedHashSet<>(); Set<String> t = new TreeSet<>(); for (String s : List.of("muz", "elma", "kiraz", "elma")) { l.add(s); t.add(s); } System.out.println(l + " " + t);
Doğru: A. LinkedHashSet ekleme sırasını korur (hash tablosu + çift bağlı liste); TreeSet kırmızı-siyah ağaçtır ve alfabetik sırayla gezer. B yanlıştır: ikinci "elma" iki kümeye de eklenmez, çünkü küme tekrar tutmaz.
Klasik Soru · Küme Uygulamaları
Soru 4: Zincirlemeli Hash Küme
m = 7 kovalı, zincirlemeli bir hash kümesine (kova = x % 7) sırasıyla 9, 16, 5, 30, 12, 16 ekleniyor. Kovaların son durumunu yazınız ve ardından yapılan contains(23) çağrısının hangi öğelere baktığını gösteriniz.
Cevabı göster
| add | 9 | 16 | 5 | 30 | 12 | 16 |
|---|---|---|---|---|---|---|
| x % 7 | 2 | 2 | 5 | 2 | 5 | 2 |
| dönüş | true | true | true | true | true | false |
Son durum: kova 2: 9 → 16 → 30, kova 5: 5 → 12; diğer kovalar boş; n = 5. İkinci 16 için kova 2 taranır, 16 bulunur ve eklenmez: kümeyi küme yapan budur.
contains(23): 23 % 7 = 2 → yalnızca kova 2'deki 9, 16, 30 ile karşılaştırılır (3 karşılaştırma), bulunamaz → false. Diğer kovalara hiç bakılmaz; ortalama zincir boyu n/m sabit kaldıkça işlem O(1).
Klasik Soru · Küme Uygulamaları
Soru 5: TreeSet ile Sıralı Sorgular
Programın çıktısı nedir? Bu sorgular HashSet ile yapılsaydı maliyet nasıl değişirdi?
TreeSet<Integer> s = new TreeSet<>(
List.of(8, 3, 14, 21, 5, 17, 30));
System.out.println(s.first() + " "
+ s.last() + " " + s.floor(10) + " "
+ s.ceiling(17) + " " + s.subSet(5, 21));
Cevabı göster
Çıktı: 3 30 8 17 [5, 8, 14, 17]
first/last: en küçük 3, en büyük 30.floor(10): ≤ 10 olan en büyük öğe → 8.ceiling(17): ≥ 17 olan en küçük öğe → 17'nin kendisi (eşitlik dahil).subSet(5, 21): alt sınır dahil, üst sınır hariç → [5, 8, 14, 17].
Dengeli ağaçta her biri O(log n) (aralık sorgusu + k çıktı). HashSet'te sıra bilgisi olmadığından aynı sorgular tüm öğeleri tarayarak O(n) sürer.
Çoktan Seçmeli · Küme Uygulamaları
Soru 6: TreeSet ve compare
Kisi sınıfında ad ve soyad alanları var. Program ne yazdırır?
TreeSet<Kisi> s = new TreeSet<>( Comparator.comparing((Kisi k) -> k.soyad)); s.add(new Kisi("Ali", "Kaya")); s.add(new Kisi("Ayşe", "Kaya")); s.add(new Kisi("Can", "Demir")); System.out.println(s.size());
Doğru: D. TreeSet eşitliği compare(a, b) == 0 ile belirler; "Ayşe Kaya", "Ali Kaya" ile aynı sayılır ve eklenmez (add false). A şıkkı equals'a bakıldığını varsayar. Düzeltme: .thenComparing(k -> k.ad), yani sıralama equals ile tutarlı olmalı. Comparator verildiği için Comparable gerekmez (C yanlış).
Adım Adım Çalıştırma · Küme Uygulamaları
Soru 7: İki İmleçle Birleşim ve Kesişim
Sıralı a = {2, 4, 6, 8} ve b = {1, 4, 5, 8, 9} dizileri için metot bittiğinde bir ve kes listeleri ne olur?
İki imleç dizilerin başında. Birleştirme sıralamasındaki merge gibi tek geçiş yapılacak.
a[0] = 2 > b[0] = 1 → 1 yalnızca b'de: birleşime eklenir, j++.
a[0] = 2 < b[1] = 4 → 2 yalnızca a'da: birleşime eklenir, i++.
a[1] = b[1] = 4 → ortak öğe: birleşime bir kez, kesişime de eklenir; iki imleç ilerler.
a[2] = 6 > b[2] = 5 → 5 birleşime eklenir, j++.
a[2] = 6 < b[3] = 8 → 6 birleşime eklenir, i++.
a[3] = b[3] = 8 → ortak öğe; i = 4, j = 4.
i = 4 = a.length → ana döngü biter; a'da kalan yok. Kesişim tamam: artık ortak öğe olamaz.
b'de kalan 9 doğrudan birleşime eklenir.
Sonuç: bir = [1, 2, 4, 5, 6, 8, 9], kes = [4, 8]. Her adımda en az bir imleç ilerler → O(n + m).
void birlesimKesisim(int[] a, int[] b, List<Integer> bir, List<Integer> kes) { int i = 0, j = 0; while (i < a.length && j < b.length) { if (a[i] < b[j]) bir.add(a[i++]); else if (a[i] > b[j]) bir.add(b[j++]); else { // ortak öğe bir.add(a[i]); kes.add(a[i]); i++; j++; } } while (i < a.length) bir.add(a[i++]); while (j < b.length) bir.add(b[j++]); }
Çoktan Seçmeli · Küme Uygulamaları
Soru 8: Hash mi, Ağaç mı?
Aşağıdaki işlemlerden hangisinde hash tabanlı küme, dengeli BST tabanlı kümeden asimptotik olarak daha yavaştır?
Doğru: B. Hash tablosu sıra bilgisi tutmaz; ≤ x olan en büyük öğeyi bulmak için tüm öğeler taranır: O(n). Dengeli BST'de O(log n). contains/add hash'te O(1)*, BST'de O(log n). D çeldiricidir ama hash ile birleşim O(n + m)*, BST ile O(m log(n + m)): hash yine daha hızlıdır.
Klasik Soru · Küme Uygulamaları
Soru 9: Bit Vektörüyle Küme Cebri
Program ne yazdırır? Yazdırılan her sayının temsil ettiği kümeyi de belirtiniz.
int a = 0, b = 0; for (int x : new int[]{0, 2, 5, 6}) a |= 1 << x; for (int x : new int[]{2, 3, 6}) b |= 1 << x; System.out.println((a | b) + " " + (a & b) + " " + (a & ~b) + " " + Integer.bitCount(a));
Cevabı göster
a = 0110 0101₂ = 101 ({0, 2, 5, 6}), b = 0100 1100₂ = 76 ({2, 3, 6}).
| İfade | İşlem | Bitler | Değer | Küme |
|---|---|---|---|---|
| a | b | birleşim | 0110 1101 | 109 | {0, 2, 3, 5, 6} |
| a & b | kesişim | 0100 0100 | 68 | {2, 6} |
| a & ~b | fark a − b | 0010 0001 | 33 | {0, 5} |
| bitCount(a) | |a| | 4 tane 1 | 4 | — |
Çıktı: 109 68 33 4. Evren 0..31 içinde kaldığı için her işlem tek makine komutu, O(1); daha büyük evrende java.util.BitSet ile O(U / 64).
Klasik Soru · Çoklu Küme
Soru 10: Çoklu Küme Sayaçları
Sunumdaki CokluKume sınıfı (öğe → sayaç eşlemesi; sayaç 1 iken remove girdiyi siler) kullanılıyor. Program ne yazdırır, eşlemenin son hâli nedir?
CokluKume<Character> m = new CokluKume<>(); for (char c : "ananas".toCharArray()) m.add(c); m.remove('a'); m.remove('s'); System.out.println(m.count('a') + " " + m.count('s') + " " + m.size() + " " + m.remove('x'));
Cevabı göster
- Eklemelerden sonra {a:3, n:2, s:1}, size = 6 (tekrarlar dahil).
remove('a'): sayaç 3 → 2, size = 5.remove('s'): sayaç 1 olduğundan girdi tamamen silinir (0 sayaç saklanmaz), size = 4.remove('x'):say.getnull →false, size değişmez.
Çıktı: 2 0 4 false; eşleme {a:2, n:2}. count('s'), getOrDefault sayesinde 0 döner.
Aynı harfler bir HashSet'e eklenseydi küme {a, n, s} olurdu (boyut 3): küme yalnızca "var mı?" bilgisini, çoklu küme ise çokluğu tutar. Tüm işlemler tek hash erişimi, ortalama O(1).
Çoktan Seçmeli · Çoklu Küme
Soru 11: Sıfır Sayaçlı Girdiler
remove metodunda if (c == 1) say.remove(e); else say.put(e, c - 1); yerine her zaman say.put(e, c - 1); yazılırsa ne olur?
boolean remove(E e) { Integer c = say.get(e); if (c == null) return false; say.put(e, c - 1); // değişiklik n--; return true; }
Doğru: C. Sayaç 0 olunca girdi silinmezse c == null kontrolü artık "öğe yok" durumunu yakalayamaz: ikinci remove sayaç −1 yapar ve size'ı bozar; ayrıca farklı öğe sayısı (say.size()) şişer. A çeldiricidir: ilk remove'dan sonra count gerçekten 0 döner, hata ancak sonraki işlemlerde ortaya çıkar.
Çoktan Seçmeli · Ayrık Kümeler (Union-Find)
Soru 12: Naif Birleştirmede Zincir
Naif union(x, y) her zaman kök(x)'i kök(y)'nin altına bağlıyor (boyut/rütbe yok, yol kısaltma yok). 5 tekil kümede sırasıyla union(1,2), union(2,3), union(3,4), union(4,5) yapılırsa ağacın yüksekliği ve find(1)'in izlediği bağlantı sayısı ne olur?
Doğru: D. Her adımda büyüyen ağacın kökü tekil düğümün altına girer: 1 → 2 → 3 → 4 → 5 zinciri, yükseklik n − 1 = 4; find(1) dört bağlantı izler, en kötü O(n). A, boyuta göre birleştirmenin sonucudur: orada tekil düğümler büyük ağacın köküne eklenir ve ağaç yıldız (yükseklik 1) olur.
Klasik Soru · Ayrık Kümeler (Union-Find)
Soru 13: Boyuta Göre Birleştirme
A–H harfleri bir hash tablosuyla 0–7 indislerine eşlenmiş; boyuta göre birleştirme (küçük ağacın kökü büyüğün köküne; boyutlar eşitse ikinci kök birincinin altına) yapılıyor, yol kısaltma yok. Aşağıdaki işlemlerden sonra her harfin ebeveyni ve ağacın yüksekliği nedir?
union(A,B), union(C,D), union(E,F), union(C,G), union(A,C), union(E,H), union(E,B)
Cevabı göster
- (A,B), (C,D), (E,F): eşit boyut → B → A, D → C, F → E.
- (C,G): 2 > 1 → G → C (boyut C = 3).
- (A,C): boyut 2 < 3 → A → C (boyut C = 5).
- (E,H): 2 > 1 → H → E (boyut E = 3).
- (E,B): find(B) = C (B → A → C); 3 < 5 → E → C (boyut C = 8).
| A | B | C | D | E | F | G | H | |
|---|---|---|---|---|---|---|---|---|
| ebeveyn | C | A | C | C | C | E | C | E |
Tek küme, kök C; yükseklik 2 (B, F, H derinlik 2). Boyuta göre birleştirme yüksekliği ≤ log₂ 8 = 3 tutar.
Adım Adım Çalıştırma · Ayrık Kümeler (Union-Find)
Soru 14: Rütbe ve Yol Kısaltma
6 öğeli yapıda (ebeveyn[i] = i, sira[i] = 0) koddaki beş union çağrısından sonra ebeveyn ve sira dizileri ne olur?
union(0,1): kökler 0 ve 1, rütbeler eşit (0) → ebeveyn[0] = 1, sira[1] = 1.
union(2,3): eşit rütbe → ebeveyn[2] = 3, sira[3] = 1.
union(4,5): eşit rütbe → ebeveyn[4] = 5, sira[5] = 1.
union(1,3): 1 ve 3 zaten kök, rütbeler eşit (1) → ebeveyn[1] = 3, sira[3] = 2.
union(4,0): find(4): ebeveyn[4] = 5 ≠ 4 → find(5); 5 kök. kx = 5 (ebeveyn[4] zaten 5).
find(0): 0 → 1 → 3 yolu boyunca özyineleme; 3 köktür.
Dönüşte yol kısaltma: ebeveyn[0] = 1 → 3. 0 artık doğrudan köke bağlı; ky = 3.
sira[5] = 1 < sira[3] = 2 → alçak ağacın kökü 5, 3'e bağlanır: ebeveyn[5] = 3. Rütbe değişmez.
Sonuç: ebeveyn = [3, 3, 3, 3, 5, 3], sira = [0, 1, 0, 2, 0, 1]; tek küme, kök 3. 4 hâlâ 5 üzerinden bağlı; yolu ilk find(4)'te kısalır.
int find(int x) { if (ebeveyn[x] != x) ebeveyn[x] = find(ebeveyn[x]); return ebeveyn[x]; } boolean union(int x, int y) { int kx = find(x), ky = find(y); if (kx == ky) return false; if (sira[kx] < sira[ky]) ebeveyn[kx] = ky; else if (sira[kx] > sira[ky]) ebeveyn[ky] = kx; else { ebeveyn[kx] = ky; sira[ky]++; } return true; } // union(0,1); union(2,3); union(4,5); // union(1,3); union(4,0);
Çoktan Seçmeli · Ayrık Kümeler (Union-Find)
Soru 15: Union-Find Karmaşıklığı
n öğe üzerinde m tane find/union işlemi, rütbeye göre birleştirme ve yol kısaltma birlikte kullanılarak yapılıyor. Toplam maliyet nedir?
Doğru: C (Tarjan, 1975). α(n) son derece yavaş büyür; evrendeki atom sayısı kadar öğe için bile ≤ 4, yani pratikte sabittir. A çeldiricidir ama bu sınır yalnızca rütbe/boyuta göre birleştirme (ya da yalnızca yol kısaltma) içindir. D yanlıştır: sınır amortizedir, tek bir find yine O(log n) sürebilir. Bellek: ebeveyn ve sira dizileri, O(n).
Klasik Soru · Ayrık Kümeler (Union-Find)
Soru 16: Bağlantılı Bileşen Sayısı
bilesenSay(8, new int[][]{{0,1}, {2,3}, {1,2}, {0,3}, {4,5}, {5,4}}) ne döndürür? Hangi kenarlarda union false döner ve bu ne anlama gelir?
int bilesenSay(int n, int[][] kenar) { UnionFind uf = new UnionFind(n); int say = n; // n tekil küme for (int[] e : kenar) if (uf.union(e[0], e[1])) say--; return say; }
Cevabı göster
| Kenar | (0,1) | (2,3) | (1,2) | (0,3) | (4,5) | (5,4) |
|---|---|---|---|---|---|---|
| union | true | true | true | false | true | false |
| say | 7 | 6 | 5 | 5 | 4 | 4 |
Dönüş: 4; bileşenler {0, 1, 2, 3}, {4, 5}, {6}, {7}.
(0,3): 0 ve 3 zaten (1,2) üzerinden bağlı, kenar bir döngü kapatır. (5,4): aynı kenarın tekrarı. İkisinde de say değişmez.
Toplam O(n + m·α(n)); kenarlar sonradan geldikçe sayaç anında güncellenir (dinamik bağlılık).
Çoktan Seçmeli · Özet
Soru 17: Kümedeki Öğeyi Değiştirmek
Nokta sınıfının equals ve hashCode metotları x ve y alanlarını kullanıyor. Program ne yazdırır?
Set<Nokta> s = new HashSet<>(); Nokta p = new Nokta(1, 2); s.add(p); p.x = 5; System.out.println(s.contains(p) + " " + s.size());
Doğru: A. p, (1, 2) hash'inin kovasına yerleşti. x değişince hash kodu da değişir; contains(p) yeni hash'in kovasına bakar ve p'yi bulamaz. Öğe ise eski kovada durur, size 1 kalır. B çeldiricidir ("aynı nesne"), ama HashSet nesneyi kova üzerinden arar. Kümedeki nesnelerin hash'e giren alanları değiştirilmemelidir.
Klasik Soru · Çoklu Küme
Soru 18: Çoklu Eşleme İşlemleri
Yorumdaki çağrılar sırayla yapılıyor. Üç remove ne döndürür, sonunda m nedir ve çoklu eşlemenin size()'ı (tüm (k, v) çiftleri) kaçtır?
Map<String, List<String>> m = new HashMap<>(); void put(String k, String v) { m.computeIfAbsent(k, x -> new ArrayList<>()) .add(v); } boolean remove(String k, String v) { List<String> l = m.get(k); if (l == null || !l.remove(v)) return false; if (l.isEmpty()) m.remove(k); return true; } // put("BIL203","Ali"); put("BIL203","Can"); // put("MAT101","Ali"); put("BIL203","Ali"); // remove("BIL203","Ali"); // remove("MAT101","Ali"); // remove("MAT101","Ece");
Cevabı göster
- put'lardan sonra: BIL203 → [Ali, Can, Ali], MAT101 → [Ali]. Anahtarın ilk put'unda
computeIfAbsentboş liste açıp onu döndürür; sonrakilerde var olan listeyi döndürür. remove("BIL203","Ali")→ true:List.remove(Object)yalnızca ilk Ali'yi siler → [Can, Ali].remove("MAT101","Ali")→ true; liste boşaldığı için anahtar da silinir.remove("MAT101","Ece")→ false:m.getnull döner.
Son: m = {BIL203=[Can, Ali]}, size = 2. Boş liste silinmeseydi containsKey("MAT101") true kalırdı. put ortalama O(1), remove(k, v) O(1 + liste boyu).
Çoktan Seçmeli · Çoklu Küme
Soru 19: computeIfAbsent Yerine put
Çoklu eşlemenin put metodu m.put(k, new ArrayList<>()); m.get(k).add(v); olarak yazılırsa put("A","x"), put("A","y"), put("B","z") sonrası m.get("A") ne döner?
Doğru: B. Her put, anahtarın listesini yeni ve boş bir listeyle değiştirir; x'i tutan liste kaybolur. A, computeIfAbsent'in sonucudur: liste yalnızca anahtar yoksa açılır, varsa mevcut liste döner. C, ilk listenin korunduğunu sanmaktır. D yanlış: put'tan hemen sonra m.get(k) null olamaz; NPE ancak hiç liste açmadan m.get(k).add(v) denenirse çıkar.
Klasik Soru · Ayrık Kümeler (Union-Find)
Soru 20: Quick-Find İzleme
7 öğeli Quick-Find yapısında başta id[i] = i. Sunumdaki union(p, q) (id'si id[p] olan herkesi id[q] yapar; eşitse hemen döner) ile sırasıyla union(0,1), union(2,3), union(1,3), union(4,5), union(5,3), union(6,2), union(0,6) çağrılıyor.
Her çağrıdan sonra id dizisi nedir? Döngüde toplam kaç id[i] == pid karşılaştırması ve kaç yazma yapılır?
Cevabı göster
| union | pid → qid | Yazma | id |
|---|---|---|---|
| (0,1) | 0 → 1 | 1 | [1, 1, 2, 3, 4, 5, 6] |
| (2,3) | 2 → 3 | 1 | [1, 1, 3, 3, 4, 5, 6] |
| (1,3) | 1 → 3 | 2 | [3, 3, 3, 3, 4, 5, 6] |
| (4,5) | 4 → 5 | 1 | [3, 3, 3, 3, 5, 5, 6] |
| (5,3) | 5 → 3 | 2 | [3, 3, 3, 3, 3, 3, 6] |
| (6,2) | 6 → 3 | 1 | [3, 3, 3, 3, 3, 3, 3] |
| (0,6) | 3 = 3 | – | değişmez (return) |
İlk altı union'un her biri diziyi baştan sona tarar: 6 · 7 = 42 karşılaştırma; yazma 1 + 1 + 2 + 1 + 2 + 1 = 8. (6,2)'de qid, 2 değil id[2] = 3'tür. find O(1), ama her union O(n): n − 1 union O(n²).
Adım Adım Çalıştırma · Ayrık Kümeler (Union-Find)
Soru 21: Yinelemeli find İzleme
ebeveyn = [3, 0, 1, 6, 6, 4, 6] iken find(2) çağrılıyor. Ne döner ve çağrıdan sonra ebeveyn dizisi nedir?
kok = x = 2 ile başlanır. İlk döngü kökü bulur, ikincisi yolu kısaltır.
ebeveyn[2] = 1 ≠ 2 → kok = 1.
ebeveyn[1] = 0 ≠ 1 → kok = 0.
ebeveyn[0] = 3 ≠ 0 → kok = 3.
ebeveyn[3] = 6 → kok = 6; ebeveyn[6] = 6 olduğundan kök bulundu: yol 2 → 1 → 0 → 3 → 6.
ebeveyn[2] = 1 ≠ 6 → sonraki = 1 saklanır, ebeveyn[2] = 6.
x = 1: ebeveyn[1] = 0 ≠ 6 → sonraki = 0, ebeveyn[1] = 6.
x = 0: ebeveyn[0] = 3 ≠ 6 → sonraki = 3, ebeveyn[0] = 6.
x = 3: ebeveyn[3] = 6 = kok → döngü biter; 3 zaten köke bağlıydı.
return 6. Yol üzerindeki 2, 1, 0 köke bağlandı. Yol dışındaki 5 (5 → 4 → 6) değişmedi; onun yolu kendi find'ında kısalır.
int find(int x) { int kok = x; while (ebeveyn[kok] != kok) kok = ebeveyn[kok]; while (ebeveyn[x] != kok) { // yol kısaltma int sonraki = ebeveyn[x]; ebeveyn[x] = kok; x = sonraki; } return kok; } // ebeveyn = [3, 0, 1, 6, 6, 4, 6]; find(2)