Bölüm 12: Kümeler

Veri Yapıları
Küme ADTBirleşim, kesişim, farkHash ve ağaç tabanlı küme Çoklu kümeUnion-FindYol kısaltma, rütbeKruskal
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Küme modeli

Küme ADT'sini ve Java Set arayüzünü açıklamak.

Çoklu küme

Tekrarlı eleman gerektiren problemlerde çoklu küme yaklaşımını kullanmak.

Union-Find

Yol sıkıştırma ve rütbeye göre birleştirme yöntemlerini uygulamak.

Uygulama

Union-Find yapısını Kruskal algoritması ve bağlılık problemleriyle ilişkilendirmek.

Küme ADT

Küme (Set)

  • Küme (set), benzersiz (tekil) öğelerden oluşan bir koleksiyondur: aynı öğe iki kez bulunmaz.
  • Matematikteki küme kavramının veri yapısı karşılığıdır.
  • Öğelerin sırası önemsizdir: {1, 2, 3} = {3, 1, 2}.
  • Temel soru: "x kümede var mı?" (üyelik testi) hızlı yanıtlanmalıdır.
S = {7, 2, 9, 5}7295add(2) → değişmez (2 zaten var)
Tekrar ayıklamaBir listedeki farklı kelimeler.new HashSet<>(liste)
ÜyelikYasaklı kullanıcılar, ziyaret edilen düğümler (Bölüm 11).
Küme cebriOrtak arkadaşlar = kesişim; tüm etiketler = birleşim.

Küme ADT

Küme İşlemleri (İşleçler)

AB
Birleşim A ∪ B
A'da veya B'de olanlar
AB
Kesişim A ∩ B
Hem A'da hem B'de olanlar
AB
Fark A − B
A'da olup B'de olmayanlar
AB
Alt küme A ⊆ B
A'nın her öğesi B'de
A = {1, 2, 3, 4, 5}, B = {3, 4, 5, 6, 7}Sonuç
Birleşim A ∪ B{1, 2, 3, 4, 5, 6, 7}
Kesişim A ∩ B{3, 4, 5}
Fark A − B  /  B − A{1, 2}  /  {6, 7}
Alt küme?A ⊄ B (1 ∉ B) · {3, 4} ⊆ A ✓

Küme ADT

java.util.Set Arayüzü

MetotAçıklama
add(e)ekler; varsa false
remove(o), contains(o)çıkarır; üyeliği sorgular
addAll(T)birleşim: S ← S ∪ T
retainAll(T)kesişim: S ← S ∩ T
removeAll(T)fark: S ← S − T
Set<Integer> a = new HashSet<>(List.of(1,2,3,4,5));
Set<Integer> b = new HashSet<>(List.of(3,4,5,6,7));
Set<Integer> bir = new HashSet<>(a);
bir.addAll(b);     // [1..7]
Set<Integer> kes = new HashSet<>(a);
kes.retainAll(b);  // [3, 4, 5]
Set<Integer> fark = new HashSet<>(a);
fark.removeAll(b); // [1, 2]
a.containsAll(b);  // false

addAll, retainAll, removeAll kümeyi yerinde değiştirir; a'yı korumak için önce kopyalanır.

Küme Uygulamaları

java.util.Set Uygulamaları

Sınıfİç yapıGezinme sırasıadd / contains / remove
HashSethash tablosu (Bölüm 9)hash koduna bağlı, öngörülemezO(1) ortalama
LinkedHashSethash tablosu + çift bağlı listeekleme sırasıO(1) ortalama
TreeSetkırmızı-siyah ağaç (Bölüm 10)sıralı (küçükten büyüğe, alfabetik)O(log n)
for (String s : List.of("elma", "kiraz", "armut", "elma")) {
    hash.add(s);  linked.add(s);  tree.add(s);
}
// HashSet       → [armut, kiraz, elma]   (sıra hash'e bağlı, farklı olabilir)
// LinkedHashSet → [elma, kiraz, armut]   (eklenme sırası)
// TreeSet       → [armut, elma, kiraz]   (alfabetik)
Tekrar eden "elma" üçünde de bir kez bulunur. Sıra önemsizse HashSet, ekleme sırası için LinkedHashSet, sıralı erişim için TreeSet.

Küme Uygulamaları

Hash Tabanlı Küme

  • Küme bir hash tablosu olarak temsil edilir (Bölüm 9); yalnızca anahtar saklanır, değer yoktur.
  • Her öğenin konumu hash koduna göre hızla hesaplanır.
  • Ekleme, silme ve arama ortalama O(1); kötü hash ile en kötü O(n).
  • Java'da HashSet içeride bir HashMap kullanır.
0 1 2 3 4 5 6

Başlangıç: 7 kovalı boş tablo. Konum = hashCode(x) mod 7.

0 1 2 3 4 5 12 6

add(12): 12 mod 7 = 5 → kova 5 boş, eklenir.

0 1 2 3 4 25 5 12 6

add(25): 25 mod 7 = 4 → kova 4'e eklenir.

0 7 1 2 3 4 25 5 12 6

add(7): 7 mod 7 = 0 → kova 0'a eklenir.

0 7 1 2 3 4 25 5 12 19 6

add(19): 19 mod 7 = 5 → çakışma! Kova 5'te 12 var; 19 zincirin sonuna eklenir.

0 7 1 2 3 4 25 5 12 19 6

add(12) tekrar: kova 5 taranır, 12 bulunur → eklenmez, false döner. Kümeyi kümeyapan budur.

0 7 1 2 3 4 25 5 12 19 6

contains(26): 26 mod 7 = 5 → yalnızca kova 5 (12, 19) taranır, bulunamaz → false. Diğer kovalara hiç bakılmaz.

Küme Uygulamaları

Ağaç Tabanlı (Sıralı) Küme

  • Küme bir dengeli ikili arama ağacı olarak temsil edilir (Java TreeSet: kırmızı-siyah ağaç).
  • Öğeler sıralı saklanır; in-order gezinme sıralı çıktı verir.
  • Arama, ekleme, silme O(log n).
  • Öğeler karşılaştırılabilir olmalı (Comparable veya Comparator).
20 10 30 5 15 25 40

Sıralı yapının getirdiği ek işlemler (S = {5, 10, 15, 20, 25, 30, 40}):

first / last5 / 40
floor(17)≤ 17 olan en büyük15
ceiling(17)≥ 17 olan en küçük20
subSet(10, 30)aralık sorgusu[10, 15, 20, 25]

Küme Uygulamaları

Sıralı Yapıyla Birleşim ve Kesişim

Küme sıralı dizi/liste olarak tutulursa birleşim ve kesişim, birleştirme sıralamasındaki merge adımıyla tek geçişte bulunur (Goodrich: genel birleştirme, generic merge):

A 1 3 5 7 9 i B 3 4 5 8 j A ∪ B A ∩ B (boş)

Başlangıç: iki sıralı dizi, i = j = 0. Her adımda A[i] ile B[j] karşılaştırılır.

A 1 3 5 7 9 i B 3 4 5 8 j A ∪ B A ∩ B 1 (boş)

A[i] = 1 < B[j] = 3 → 1 yalnızca A'da: birleşime eklenir, i++.

A 1 3 5 7 9 i B 3 4 5 8 j A ∪ B A ∩ B 1 3 3

A[i] = B[j] = 3 → ortak öğe: hem birleşime hem kesişime (bir kez) eklenir, i++ ve j++.

A 1 3 5 7 9 i B 3 4 5 8 j A ∪ B A ∩ B 1 3 4 3

B[j] = 4 < A[i] = 5 → 4 yalnızca B'de: birleşime eklenir, j++.

A 1 3 5 7 9 i B 3 4 5 8 j A ∪ B A ∩ B 1 3 4 5 3 5

A[i] = B[j] = 5 → ortak öğe: hem birleşime hem kesişime (bir kez) eklenir, i++ ve j++.

A 1 3 5 7 9 i B 3 4 5 8 j A ∪ B A ∩ B 1 3 4 5 7 3 5

A[i] = 7 < B[j] = 8 → 7 yalnızca A'da: birleşime eklenir, i++.

A 1 3 5 7 9 i B 3 4 5 8 bitti A ∪ B A ∩ B 1 3 4 5 7 8 3 5

B[j] = 8 < A[i] = 9 → 8 yalnızca B'de: birleşime eklenir, j++.

A 1 3 5 7 9 bitti B 3 4 5 8 bitti A ∪ B A ∩ B 1 3 4 5 7 8 9 3 5

A[i] = 9 (B bitti) → 9 yalnızca A'da: birleşime eklenir, i++. İki dizi de bitti: toplam O(n + m) karşılaştırma.

Küme Uygulamaları

Küme Uygulamalarının Karşılaştırması

İşlem (|A| = n, |B| = m)Sırasız listeSıralı diziHash tablosuDengeli BST
containsO(n)O(log n)O(1)*O(log n)
add / removeO(n)†O(n)O(1)*O(log n)
A ∪ B, A ∩ B, A − BO(n·m)O(n + m)O(n + m)*O(m log(n+m))
Sıralı gezinmeO(n log n)O(n)O(n log n)O(n)
En küçük / floor / aralıkO(n)O(log n)O(n)O(log n)

* ortalama durum (en kötü O(n)). † tekrar kontrolü için önce arama gerekir. Hash ile birleşim: B'nin her öğesi için contains + add.

Varsayılan seçim: hashSıra gerekmiyorsa en hızlı üyelik testi.
Sıra gerekiyorsa: ağaçHer işlem O(log n) ve sıralı sorgular bedava.

Çoklu Küme

Çoklu Küme (Multiset / Bag)

  • Çoklu küme (multiset, bag): aynı öğenin birden çok kez bulunabildiği küme.
  • Sıra yine önemsizdir; önemli olan her öğenin kaç kez geçtiğidir (çokluk, multiplicity).
  • Doğal uygulama: öğe → sayaç eşlemesi (Bölüm 9).
  • Örnek: bir metindeki kelime frekansları, alışveriş sepeti, oy sayımı.
ÖğeSayaç
a2
b1
c3

M = {a, a, b, c, c, c} → eşleme {a:2, b:1, c:3}

Multiset ADT (Goodrich)AnlamıHash ile
add(e) / add(e, n)e'yi 1 / n kez ekle (sayaç += n)O(1)*
count(e)e kaç kez var? (yoksa 0)O(1)*
remove(e) / remove(e, n)sayacı azalt; 0 olursa girdiyi silO(1)*
size()tekrarlar dahil öğe sayısı (örnekte 6)O(1)

Çoklu Küme

Java ile Çoklu Küme

class CokluKume<E> {
    private final Map<E, Integer> say = new HashMap<>();
    private int n = 0;                                   // tekrarlar dahil öğe sayısı
    void add(E e) { say.merge(e, 1, Integer::sum); n++; }  // yoksa 1, varsa +1
    int count(E e) { return say.getOrDefault(e, 0); }
    boolean remove(E e) {
        Integer c = say.get(e);
        if (c == null) return false;                     // e yok
        if (c == 1) say.remove(e);                          // sayaç 0 olacak: girdiyi sil
        else say.put(e, c - 1);
        n--;
        return true;
    }
    int size() { return n; }
}
Kullanımadd("a"); add("a");
count("a") → 2
Küme ile farkıSet'te ikinci add("a") etkisiz; burada sayaç artar.
Sıralı çoklu kümeTreeMap<E, Integer> ile sıralı gezinme.

Ayrık Kümeler (Union-Find)

Ayrık Kümeler ve Union-Find

  • Ayrık kümeler (disjoint sets): hiçbir öğesi ortak olmayan kümeler topluluğu. Her öğe tam olarak bir kümeye aittir.
  • Union-Find (Disjoint Set Union, DSU) bu kümeleri tutan veri yapısıdır. İki ana işlemi vardır:
find(x) · bulx'in ait olduğu kümenin temsilcisini (kökünü) döndürür.find(x) == find(y) ⇔ aynı küme
union(x, y) · birleştirx'in ve y'nin kümelerini tek kümede birleştirir.{1,2} ∪ {3} → {1,2,3}
0 1 2 5 6 3 9 4 8 7

Her küme bir ağaç: her düğüm atasını (parent) gösterir; kök kendini gösterir ve kümenin temsilcisidir.

Ayrık Kümeler (Union-Find)

Mıknatıs Örneği

  • Birbirini güçlü şekilde çeken mıknatıslar düşünelim: iki mıknatıs değince ayrılmaz.
  • Birleşmeler sürerken "hangi mıknatıs hangi grupta?" sorusunu izlemek istiyoruz.
  • Önce her mıknatısa bir numara verilir.
  • Birleşme = union, "aynı grupta mı?" = iki find karşılaştırması.
1 2 3 4 5 6 7 8 9 10 11 12 13 14

14 mıknatıs, her biri kendi grubunda: 14 ayrık küme. find(x) = x.

1 2 3 4 5 6 7 8 9 10 11 12 13 14

union(6, 8): en yakın olan 6 ile 8 birleşir; artık find(6) = find(8).

1 2 3 4 5 6 7 8 9 10 11 12 13 14

union(3, 4): 3 ile 4 birbirini çeker, yeni grup {3, 4}.

1 2 3 4 5 6 7 8 9 10 11 12 13 14

union(2, 3): 2, {3, 4} grubuna katılır → {2, 3, 4}.

1 2 3 4 5 6 7 8 9 10 11 12 13 14

union(9, 10), union(13, 14), union(9, 13) → {9, 10, 13, 14}.

1 2 3 4 5 6 7 8 9 10 11 12 13 14

union(5, 7), union(7, 11), union(11, 12) → {5, 7, 11, 12}. Beş grup kaldı (1 tek başına).

1 2 3 4 5 6 7 8 9 10 11 12 13 14

4 ile 10 aynı grupta mı? find(4) ≠ find(10) → hayır. union(4, 10) iki grubu birleştirir.

1 2 3 4 5 6 7 8 9 10 11 12 13 14

Birleşmeler sürer ve sonunda tüm mıknatıslar tek grupta toplanır: find(x) herkes için aynı kökü verir.

Ayrık Kümeler (Union-Find)

Find ve Union İşlemleri

  • find(x): x'i içeren kümenin adını (kökünü) döndürür. Ata bağlantıları kök bulunana kadar izlenir.
  • Bu, arama ağaçlarındaki (Bölüm 10) arama ile aynı şey değildir: değer aranmaz, yukarı doğru köke çıkılır.
  • union(x, y): iki kümenin yerine, ikisinin birleşimi olan tek bir küme yaratır: bir kökü diğerinin altına bağlar.
Kümeler baştan ayrık olduğu için birleşimde tekrar kontrolü yok: yalnızca bir ata bağlantısı değişir.
find(x):
  if x.ata != x:
    x.ata = find(x.ata)  // yol kısaltma
  return x.ata

union(x, y):
  kök1 = find(x)
  kök2 = find(y)
  if kök1 != kök2:
    // bir kümenin kökünü diğer
    // kümenin köküne ata
    kök1.ata = kök2

Ayrık Kümeler (Union-Find)

Nesneleri İndislere Eşleme

  • Union-Find en verimli hâliyle dizi üzerinde çalışır: öğeler 0..n−1 tamsayılarıdır.
  • Öğeler harf, şehir, kişi… ise önce her nesne bir indise rastgele (ama kalıcı) eşlenir ve bu eşleme bir hash tablosunda saklanır.
  • Sonra ata[i] = i ile her öğe kendi kümesinin kökü olarak başlar (makeSet).
E → 0F → 1I → 2D → 3C → 4A → 5J → 6L → 7G → 8K → 9B → 10H → 11
Map<Character, Integer> indis = new HashMap<>();   // 'E'→0, 'F'→1, …
int[] ata = new int[12];
for (int i = 0; i < 12; i++) ata[i] = i;             // 12 tekil küme

Ayrık Kümeler (Union-Find)

Union İşlemi Adım Adım

öğe indis ata E 0 0 F 1 1 I 2 2 D 3 3 C 4 4 A 5 5 J 6 6 L 7 7 G 8 8 K 9 9 B 10 10 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

Başlangıç: 12 tekil küme, ata[i] = i. Kural: küçük ağacın kökü büyüğün köküne bağlanır; eşitse ikinci kök birinciye bağlanır.

öğe indis ata E 0 0 F 1 1 I 2 2 D 3 3 C 4 4 A 5 5 J 6 6 L 7 7 G 8 8 K 9 4 B 10 10 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(C, K): find(C) = C, find(K) = K; boyutlar eşit (1) → K, C'nin altına bağlanır (ata[9] = 4).

öğe indis ata E 0 1 F 1 1 I 2 2 D 3 3 C 4 4 A 5 5 J 6 6 L 7 7 G 8 8 K 9 4 B 10 10 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(F, E): find(F) = F, find(E) = E; boyutlar eşit (1) → E, F'nin altına bağlanır (ata[0] = 1).

öğe indis ata E 0 1 F 1 1 I 2 2 D 3 3 C 4 4 A 5 5 J 6 5 L 7 7 G 8 8 K 9 4 B 10 10 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(A, J): find(A) = A, find(J) = J; boyutlar eşit (1) → J, A'nin altına bağlanır (ata[6] = 5).

öğe indis ata E 0 1 F 1 1 I 2 2 D 3 3 C 4 4 A 5 5 J 6 5 L 7 7 G 8 8 K 9 4 B 10 5 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(A, B): find(A) = A, find(B) = B; boyut 1 < 2 → B, A'nin altına bağlanır (ata[10] = 5).

öğe indis ata E 0 1 F 1 1 I 2 2 D 3 4 C 4 4 A 5 5 J 6 5 L 7 7 G 8 8 K 9 4 B 10 5 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(C, D): find(C) = C, find(D) = D; boyut 1 < 2 → D, C'nin altına bağlanır (ata[3] = 4).

öğe indis ata E 0 1 F 1 1 I 2 4 D 3 4 C 4 4 A 5 5 J 6 5 L 7 7 G 8 8 K 9 4 B 10 5 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(D, I): find(D) = C, find(I) = I; boyut 1 < 3 → I, C'nin altına bağlanır (ata[2] = 4).

öğe indis ata E 0 1 F 1 1 I 2 4 D 3 4 C 4 4 A 5 5 J 6 5 L 7 1 G 8 8 K 9 4 B 10 5 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(L, F): find(L) = L, find(F) = F; boyut 1 < 2 → L, F'nin altına bağlanır (ata[7] = 1).

öğe indis ata E 0 1 F 1 1 I 2 4 D 3 4 C 4 4 A 5 4 J 6 5 L 7 1 G 8 8 K 9 4 B 10 5 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(C, A): find(C) = C, find(A) = A; boyut 3 < 4 → A, C'nin altına bağlanır (ata[5] = 4).

öğe indis ata E 0 1 F 1 1 I 2 4 D 3 4 C 4 4 A 5 4 J 6 5 L 7 1 G 8 11 K 9 4 B 10 5 H 11 11 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(A, B): find(A) = find(B) = C → zaten aynı kümede, hiçbir şey değişmez. union(H, G): find(H) = H, find(G) = G; boyutlar eşit (1) → G, H'nin altına bağlanır (ata[8] = 11).

öğe indis ata E 0 1 F 1 1 I 2 4 D 3 4 C 4 4 A 5 4 J 6 5 L 7 1 G 8 11 K 9 4 B 10 5 H 11 1 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(H, F): find(H) = H, find(F) = F; boyut 2 < 3 → H, F'nin altına bağlanır (ata[11] = 1).

öğe indis ata E 0 1 F 1 4 I 2 4 D 3 4 C 4 4 A 5 4 J 6 5 L 7 1 G 8 11 K 9 4 B 10 5 H 11 1 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

union(H, B): find(H) = F, find(B) = C; boyut 5 < 7 → F, C'nin altına bağlanır (ata[1] = 4).

öğe indis ata E 0 1 F 1 4 I 2 4 D 3 4 C 4 4 A 5 4 J 6 5 L 7 1 G 8 11 K 9 4 B 10 5 H 11 1 İşlemler union(C, K) union(F, E) union(A, J) union(A, B) union(C, D) union(D, I) union(L, F) union(C, A) union(A, B) union(H, G) union(H, F) union(H, B) E F I D C A J L G K B H

Sonuç: tek küme, kök C. H ile B aynı kümede mi? find(H): H → F → C, find(B): B → A → C → 4 adım.

Ayrık Kümeler (Union-Find)

Neden İyileştirme Gerekir?

  • find, kökten uzaklık kadar adım atar. Birleştirme sırası kötü seçilirse ağaç bir zincire dönüşür.
  • Örnek: union(2,1), union(3,2), union(4,3), union(5,4) ve her seferinde eski kök yeninin altına bağlanırsa find(5) = 4 adım → en kötü O(n).
1 2 3 4 5
Kötü: yükseklik n − 1, find O(n)
1 2 3 4 5
İyi: yükseklik 1, find O(1)
İki basit fikir ağaçları basık tutar: (1) boyuta / rütbeye göre birleştirme (union by size / rank) ve (2) yol kısaltma (path compression). Birlikte işlem başına neredeyse sabit süre: O(α(n)).

Ayrık Kümeler (Union-Find)

Boyuta Göre Birleştirme (Union by Size)

  • Her kökte ağacın boyutunu (öğe sayısı) sakla.
  • Birleştirirken küçük ağacın kökünü büyük ağacın köküne bağla; yeni boyut = iki boyutun toplamı.
1 4 7 2 3 6 9boyut 3boyut 4

İki küme: {1, 4, 7} (boyut 3) ve {2, 3, 6, 9} (boyut 4). Yükseklikler 1 ve 2.

1 4 7 2 3 6 9

Naif union: büyük ağacın kökü 2, küçüğün altına bağlanırsa yükseklik 3'e çıkar; 9'un find'ı uzar.

2 1 4 7 3 6 9

Boyuta göre: küçük ağacın kökü 1, büyüğün kökü 2'ye bağlanır. Yükseklik 2 kalır, boyut[2] = 3 + 4 = 7.

Ayrık Kümeler (Union-Find)

Rütbeye Göre Birleştirme (Union by Rank)

  • Rütbe, yüksekliğin üst sınırıdır; yeni kümede 0'dır.
  • Küçük rütbeli kök, büyük rütbeli köke bağlanır.
  • Rütbeler eşitse yeni kökün rütbesi bir artar.
  • Bu seçim, yol kısaltma olmadan bile yüksekliği O(log n) tutar.
Yol kısaltma yüksekliği azaltır ama rütbeyi güncellemez; bu yüzden rütbe yalnızca bir üst sınırdır.
MakeSet(x):
  p[x] = x
  rank[x] = 0

FindSet(x):
  if x != p[x]:
    p[x] = FindSet(p[x]) // yol kısaltma
  return p[x]

Link(x, y):   // x, y kök
  if rank[x] > rank[y]:
    p[y] = x
  else:
    p[x] = y
    if rank[x] == rank[y]:
      rank[y] = rank[y] + 1

Union(x, y):
  Link(FindSet(x), FindSet(y))

Ayrık Kümeler (Union-Find)

Yol Kısaltma (Path Compression)

Yol kısaltma (path compression): find sırasında rastlanan tüm düğümleri doğrudan köke bağla. Sonraki find'lar tek adımda biter.

1 2 7 5 6 3 10 4 8 9

Başlangıç: 3'ün kökü 7, ama aradaki yol 3 → 6 → 5 → 7 (3 adım).

1 2 7 5 6 3 10 4 8 9

find(3): özyineleme 3 → 6 → 5 → 7 diye köke çıkar; kök 7 bulunur.

1 2 7 3 6 10 5 4 8 9

Dönüşte her düğümün atası 7 yapılır: ata[3] = ata[6] = 7 (5 zaten 7'deydi). Artık find(3), find(6) tek adım; 10'un yolu da kısaldı.

Ayrık Kümeler (Union-Find)

Union-Find Karmaşıklığı

UygulamafindunionNot
Naif (rastgele bağlama)O(n)O(n)ağaç zincire dönüşebilir
Boyuta veya rütbeye göreO(log n)O(log n)yükseklik ≤ log₂ n
Yalnızca yol kısaltmaO(log n) amortizem işlem için
Rütbe + yol kısaltmaO(α(n)) amortizepratikte sabit
α(n): ters Ackermann fonksiyonuSon derece yavaş büyür: evrendeki atom sayısı kadar öğe için bile α(n) ≤ 4.
Bellekİki tamsayı dizisi: ata[n] ve sira[n] (veya boyut[n]) → O(n).
Tarjan (1975): m işlem dizisi O(m · α(n)) sürede biter. Kruskal, bağlı bileşenler, resim işleme (bölge etiketleme), ağ bağlantı sorguları gibi yerlerde kullanılır.

Ayrık Kümeler (Union-Find)

Java: Düğüm (Nesne) Tabanlı Union-Find

class SetDugum {
  int veri;
  int sira;   // rütbe
  SetDugum ata;

  SetDugum(int veri) {
    this.veri = veri;
    this.ata = this;
    this.sira = 0;
  }
}

SetDugum find(SetDugum d) {
  if (d != d.ata)
    d.ata = find(d.ata); // kısaltma
  return d.ata;
}
void union(SetDugum x, SetDugum y) {
  SetDugum kokX = find(x);
  SetDugum kokY = find(y);
  if (kokX == kokY) return;
  // rütbeye göre birleştirme
  if (kokX.sira < kokY.sira) {
    kokX.ata = kokY;
  } else if (kokX.sira > kokY.sira) {
    kokY.ata = kokX;
  } else { // eşit: birini seç
    kokY.ata = kokX;
    kokX.sira++;
  }
}

Ayrık Kümeler (Union-Find)

Java: Dizi Tabanlı Union-Find

class UnionFind {
  private final int[] ata, sira;
  UnionFind(int n) {
    ata = new int[n];  sira = new int[n];
    for (int i = 0; i < n; i++) ata[i] = i;
  }
  int find(int x) {
    if (ata[x] != x) ata[x] = find(ata[x]);
    return ata[x];
  }
  boolean union(int x, int y) {
    int kx = find(x), ky = find(y);
    if (kx == ky) return false;
    if (sira[kx] < sira[ky]) ata[kx] = ky;
    else if (sira[kx] > sira[ky]) ata[ky] = kx;
    else { ata[kx] = ky; sira[ky]++; }
    return true;
  }
}
KullanımUnionFind uf = new UnionFind(5);
uf.union(0, 1); uf.union(3, 4);
uf.find(1) == uf.find(0) → true
uf.find(1) == uf.find(3) → false
union neden boolean?Birleşme olduysa true: Kruskal'da "kenar döngü oluşturuyor mu?" sorusunun cevabı.
Derin özyinelemeÇok büyük n'de find döngüyle de yazılabilir.

Kruskal Algoritması

Minimum Kapsayan Ağaç (Minimum Spanning Tree)

  • Kapsayan ağaç (spanning tree): bağlantılı, ağırlıklı bir çizgenin tüm düğümlerini içeren ve döngü içermeyen alt çizgesi; n düğüm için n − 1 kenar.
  • Minimum kapsayan ağaç (MKA, MST): toplam kenar ağırlığı en düşük olan kapsayan ağaç.
  • Uygulama: şehirleri en az kabloyla / boruyla birbirine bağlamak.
  • Kruskal algoritması, "bu kenar döngü oluşturur mu?" sorusunu Union-Find ile yanıtlar.
0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J

Kruskal Algoritması

Kruskal Algoritması

Kruskal(G):
  T = ∅          // seçilen kenarlar
  for her düğüm v: makeSet(v)
  kenarları ağırlığa göre sırala
  for her (u, v), küçükten büyüğe:
    if find(u) != find(v): // döngü yok
      T.ekle((u, v))
      union(u, v)
    if |T| == n − 1: break
  return T
  • Açgözlü: her zaman döngü oluşturmayan en hafif kenarı seç.
  • u ve v zaten aynı kümedeyse, kenar bir döngü kapatır → atla.
  • Başta n ayrık küme; her kabul edilen kenar iki kümeyi birleştirir.
SıralamaO(m log m)
2m find + (n−1) unionO(m · α(n))
ToplamO(m log n)

Kruskal Algoritması

Kruskal Algoritması Adım Adım

I–J = 0 A–E = 1 C–I = 1 E–F = 1 G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 0 / 9 Toplam ağırlık 0

Kenarlar ağırlığa göre sıralandı (solda). Başlangıçta 10 düğüm = 10 ayrı küme, T boş.

I–J = 0 ✓ A–E = 1 C–I = 1 E–F = 1 G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 1 / 9 Toplam ağırlık 0

I–J (0): find(I) ≠ find(J) → kabul, kümeler birleşir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 E–F = 1 G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 2 / 9 Toplam ağırlık 1

A–E (1): find(A) ≠ find(E) → kabul, kümeler birleşir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 3 / 9 Toplam ağırlık 2

C–I (1): find(C) ≠ find(I) → kabul, kümeler birleşir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 4 / 9 Toplam ağırlık 3

E–F (1): find(E) ≠ find(F) → kabul, kümeler birleşir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 5 / 9 Toplam ağırlık 4

G–H (1): find(G) ≠ find(H) → kabul, kümeler birleşir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 6 / 9 Toplam ağırlık 6

B–D (2): find(B) ≠ find(D) → kabul, kümeler birleşir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 6 / 9 Toplam ağırlık 6

C–J (2): C ve J zaten aynı kümede → döngü oluşturur, reddedilir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 7 / 9 Toplam ağırlık 8

D–E (2): find(D) ≠ find(E) → kabul, kümeler birleşir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 ✓ A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 8 / 9 Toplam ağırlık 10

D–H (2): find(D) ≠ find(H) → kabul, kümeler birleşir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 ✓ A–D = 4 ✗ B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 8 / 9 Toplam ağırlık 10

A–D (4): A ve D zaten aynı kümede → döngü oluşturur, reddedilir.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 ✓ A–D = 4 ✗ B–C = 4 ✓ C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 9 / 9 Toplam ağırlık 14

B–C (4): find(B) ≠ find(C) → kabul, kümeler birleşir. 9 = n − 1 kenar seçildi: bitti, toplam ağırlık 14.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 ✓ A–D = 4 ✗ B–C = 4 ✓ C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Seçilen kenar 9 / 9 Toplam ağırlık 14

Minimum kapsayan ağaç: A–E, E–F, D–E, B–D, D–H, G–H, B–C, C–I, I–J. Kalan kenarlara bakmaya gerek yok.

Kruskal Algoritması

İkinci Örnek: Havalimanları

2704 867 849 740 621 1846 802 144 187 184 1258 337 1464 1235 1391 946 1090 1121 2342 BOS PVD ORD JFK BWI SFO LAX DFW MIA

Goodrich'teki örnek: 9 havalimanı, 19 uçuş hattı (mil). Kruskal sırası:

144, 184, 187, 337, 621kabul
740 ORD–JFKret (döngü)
802kabul
849, 867ret
946kabul
1090, 1121ret
1235 LAX–DFWkabul → 8 kenar

Toplam: 4456 mil.

Özet

Özet

YapıTemel işlemlerMaliyetNe zaman?
HashSetadd, remove, containsO(1)*sıra gerekmiyorsa
TreeSet+ first, floor, aralıkO(log n)sıralı erişim gerekiyorsa
Çoklu kümeadd, count, removeO(1)*tekrar sayısı önemliyse
Union-Findfind, unionO(α(n))ayrık kümeler, bağlantı sorguları

* ortalama durum. Union-Find maliyeti rütbe + yol kısaltma ile amortize.

Seçim: üyelik için HashSet, sıralı erişim için TreeSet, dinamik bağlılık için Union-Find.

Özet

Etkileşimli Simülatörler

Bu bölümün konularını tarayıcıda deneyerek pekiştirin:

Küme ADTÖğe ekleme/çıkarma, üyelik testi, birleşim, kesişim ve fark işlemleri.
Ayrık Küme Birleşimi (Union-Find)find ve union işlemlerini, rütbeye göre birleştirmeyi ve yol kısaltmayı ağaç üzerinde izleyin.

Önceki bölümlerle bağlantı: Hash Tablosu · Kırmızı-Siyah Ağaç · Çizge

Özet

Örnek Problemler

  1. A = {2, 4, 6, 8}, B = {1, 2, 3, 4} için A ∪ B, A ∩ B, B − A nedir?
  2. Bir sözlükteki kelimeleri alfabetik sırayla ve tekrarsız yazdırmak için hangi Set uygulamasını seçersiniz?
  3. "kavak" kelimesinin harflerini çoklu kümede tutun: count('a') ve size() ne döner?
  4. 0..5 için sırayla union(0,1), union(2,3), union(1,3), union(4,5) yapılıyor (rütbeye göre, eşitlikte ata[x] = y). Kaç küme kalır? find(0) nedir?
  5. Kruskal örneğinde C–J kenarı neden reddedildi?
Cevapları göster
  1. A ∪ B = {1, 2, 3, 4, 6, 8}, A ∩ B = {2, 4}, B − A = {1, 3}.
  2. TreeSet (sıralı, tekrarsız, O(log n)).
  3. {k:2, a:2, v:1} → count('a') = 2, size() = 5.
  4. union(0,1): ata[0]=1, sira[1]=1 · union(2,3): ata[2]=3, sira[3]=1 · union(1,3): eşit → ata[1]=3, sira[3]=2 · union(4,5): ata[4]=5. İki küme: {0,1,2,3} ve {4,5}; find(0) = 3.
  5. C–I ve I–J daha önce seçilmişti; C ile J aynı kümede, kenar C–I–J–C döngüsünü kapatır.

Özet

Kaynaklar

  • Goodrich, Tamassia ve Goldwasser, Data Structures and Algorithms in Java, 6. baskı, 2014.
  • Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, 2022.
  • Sedgewick ve Wayne, Algorithms, 4. baskı, 2011.
  • Oracle, Java SE 17 Set, HashSet ve TreeSet API belgeleri.

Son

Bölüm 12: Kümeler
Dersin sonu · Veri Yapıları dersinin son bölümü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.

← → veya boşluk ile gezinin · F: tam ekran · Home/End: ilk/son slayt