Bölüm 13: Kümeler ve Ayrık 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ütbeBağlantılı bileşenler
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ı öğe gerektiren problemlerde çoklu küme yaklaşımını kullanmak.

Union-Find

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

Uygulama

Union-Find yapısını bağlantılı bileşen sayma gibi bağlılık problemlerinde kullanmak.

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.
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 ADT · Kodu adım adım çalıştır

Kod İzleme: add dönüşüyle ilk tekrar

a 4 0 7 1 2 2 7 3 4 4 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Boş bir HashSet oluşturulur: 16 boş kova. Her öğe hash & 15 kovasına gider.

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 4 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Döngü sıradaki öğeyi alır: x = 4.

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 4 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 3 4 4 5 6 7 8 9 10 11 12 13 14 15

add(4): kova 4 boş, öğe eklenir ve true döner → !true yanlış, döngü sürer. Tek kova bakıldı: O(1).

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 7 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 3 4 4 5 6 7 8 9 10 11 12 13 14 15

Döngü sıradaki öğeyi alır: x = 7.

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 7 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 3 4 4 5 6 7 7 8 9 10 11 12 13 14 15

add(7): kova 7 boş, öğe eklenir ve true döner → !true yanlış, döngü sürer. Tek kova bakıldı: O(1).

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 2 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 3 4 4 5 6 7 7 8 9 10 11 12 13 14 15

Döngü sıradaki öğeyi alır: x = 2.

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 2 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 2 3 4 4 5 6 7 7 8 9 10 11 12 13 14 15

add(2): kova 2 boş, öğe eklenir ve true döner → !true yanlış, döngü sürer. Tek kova bakıldı: O(1).

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 7 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 2 3 4 4 5 6 7 7 8 9 10 11 12 13 14 15

Döngü sıradaki öğeyi alır: x = 7.

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 7 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 2 3 4 4 5 6 7 7 8 9 10 11 12 13 14 15 zaten var!

add(7): kova 7'de 7 zaten var; küme değişmez ve false döner → koşul doğru.

a 4 0 7 1 2 2 7 3 4 4 ▲ x = 7 gorulen: HashSet, 16 kova kova = hash(x) & 15 (Integer: hash = x) 0 1 2 2 3 4 4 5 6 7 7 8 9 10 11 12 13 14 15 zaten var!

İlk tekrar eden öğe 7 döndürülür. Toplam iş O(n): her öğe için bir add; iç içe döngü yok.

int ilkTekrar(int[] a) {
  Set<Integer> gorulen = new HashSet<>();
  for (int x : a) {
    if (!gorulen.add(x))   // eklenemedi mi?
      return x;            // zaten vardı: tekrar
  }
  return -1;               // tekrar yok
}

Küme Uygulamaları

java.util.Set Uygulamaları

Sınıfİç yapıGezinme sırasıadd / contains / remove
HashSethash tablosuhash 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ç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       → [elma, armut, kiraz]   (kova sırası: hash'e bağlı)
// 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; 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üme yapan 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ı · Kodu adım adım çalıştır

Kod İzleme: add / contains (m = 7)

// alanlar: List<Integer>[] kova; n; m = 7
boolean add(int x) {          // x ≥ 0
  int i = x % m;               // kova no
  for (int y : kova[i])
    if (y == x) return false;  // zaten var
  kova[i].add(x);
  n++;
  return true;
}
boolean contains(int x) {
  int i = x % m;
  for (int y : kova[i])
    if (y == x) return true;
  return false;
}
0 1 2 3 4 5 6

add(12): kova no = 12 % 7 = 5. Kova boş; tarama hemen biter.

0 1 2 3 4 5 12 6

12 kovada yok → zincirin sonuna eklenir, n = 1, return true.

0 1 2 3 4 5 12 6

add(25): kova no = 25 % 7 = 4. Kova boş; tarama hemen biter.

0 1 2 3 4 25 5 12 6

25 kovada yok → zincirin sonuna eklenir, n = 2, return true.

0 1 2 3 4 25 5 12 6

add(7): kova no = 7 % 7 = 0. Kova boş; tarama hemen biter.

0 7 1 2 3 4 25 5 12 6

7 kovada yok → zincirin sonuna eklenir, n = 3, return true.

0 7 1 2 3 4 25 5 12 6

add(19): kova no = 19 % 7 = 5. Kovadaki zincir taranmalı: 12.

0 7 1 2 3 4 25 5 12 6

y = 12 ≠ 19 → zincirde ilerle.

0 7 1 2 3 4 25 5 12 19 6

19 kovada yok → zincirin sonuna eklenir, n = 4, return true. (Çakışma: kova 5'te artık 2 öğe var.)

0 7 1 2 3 4 25 5 12 19 6

add(12): kova no = 12 % 7 = 5. Kovadaki zincir taranmalı: 12, 19.

0 7 1 2 3 4 25 5 12 19 6

y = 12 == 12 → öğe zaten kümede: return false, hiçbir şey eklenmez. Kümeyi küme yapan budur.

0 7 1 2 3 4 25 5 12 19 6

contains(26): 26 % 7 = 5 → yalnızca kova 5 taranır; diğer kovalara hiç bakılmaz.

0 7 1 2 3 4 25 5 12 19 6

y = 12 ≠ 26 → devam.

0 7 1 2 3 4 25 5 12 19 6

y = 19 ≠ 26 → devam.

0 7 1 2 3 4 25 5 12 19 6

Zincir bitti, 26 yok → return false. Ortalama zincir boyu n/m sabit → O(1).

0 7 1 2 3 4 25 5 12 19 6

contains(19): 19 % 7 = 5 → yalnızca kova 5 taranır; diğer kovalara hiç bakılmaz.

0 7 1 2 3 4 25 5 12 19 6

y = 12 ≠ 19 → devam.

0 7 1 2 3 4 25 5 12 19 6

y = 19 == 19 → bulundu, return true.

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; kök ortada (inorder) gezinme sıralı çıktı verir.
  • Arama, ekleme, silme O(log n).
  • Öğeler karşılaştırılabilir olmalı (Comparable veya Comparator).
  • compare(a, b) == 0 ise TreeSet iki öğeyi aynı kabul eder; bu nedenle sıralama equals ile tutarlı olmalıdır.
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ı · Kodu adım adım çalıştır

Sıralı Dizilerle Birleşim ve Kesişim (iki imleç)

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++]);
}
a 1 3 5 7 9 ▲ i = 0 b 3 4 5 8 ▲ j = 0 bir kes

Kümeler sıralı dizilerde. Birleştirme sıralamasındaki merge gibi iki imleç tek geçişte ilerler (Goodrich: generic merge).

a 1 3 5 7 9 ▲ i = 1 b 3 4 5 8 ▲ j = 0 bir 1 kes

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

a 1 3 5 7 9 ▲ i = 2 b 3 4 5 8 ▲ j = 1 bir 1 3 kes 3

a[i] = b[j] = 3 → ortak öğe: birleşime bir kez ve kesişime eklenir; iki imleç de ilerler.

a 1 3 5 7 9 ▲ i = 2 b 3 4 5 8 ▲ j = 2 bir 1 3 4 kes 3

a[i] = 5 > b[j] = 4 → 4 yalnızca b'de: birleşime eklenir, j++.

a 1 3 5 7 9 ▲ i = 3 b 3 4 5 8 ▲ j = 3 bir 1 3 4 5 kes 3 5

a[i] = b[j] = 5 → ortak öğe: birleşime bir kez ve kesişime eklenir; iki imleç de ilerler.

a 1 3 5 7 9 ▲ i = 4 b 3 4 5 8 ▲ j = 3 bir 1 3 4 5 7 kes 3 5

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

a 1 3 5 7 9 ▲ i = 4 b 3 4 5 8 ▲ j = 4 (bitti) bir 1 3 4 5 7 8 kes 3 5

a[i] = 9 > b[j] = 8 → 8 yalnızca b'de: birleşime eklenir, j++.

a 1 3 5 7 9 ▲ i = 4 b 3 4 5 8 ▲ j = 4 (bitti) bir 1 3 4 5 7 8 kes 3 5

j = 4 = b.length → b bitti, ana döngüden çıkılır. Kesişim tamam: artık ortak öğe olamaz.

a 1 3 5 7 9 ▲ i = 5 (bitti) b 3 4 5 8 ▲ j = 4 (bitti) bir 1 3 4 5 7 8 9 kes 3 5

a'da kalan 9 doğrudan birleşime eklenir, i++.

a 1 3 5 7 9 ▲ i = 5 (bitti) b 3 4 5 8 ▲ j = 4 (bitti) bir 1 3 4 5 7 8 9 kes 3 5

b'de kalan yok. Birleşim [1, 3, 4, 5, 7, 8, 9], kesişim [3, 5]. Her adımda bir imleç ilerler → en fazla n + m adım: O(n + m).

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.

Küme Uygulamaları

Bit Vektörü ile Küme (Bitset)

7 6 5 4 3 2 1 0 A 0 0 0 1 1 0 1 0 = 26 → {1, 3, 4} B 0 0 1 0 1 0 0 0 = 40 → {3, 5} A | B 0 0 1 1 1 0 1 0 = 58 → {1, 3, 4, 5}
FikirEvren 0..U−1 ise i. bit 1 ⇔ i kümede.
Tek öğe: O(1)ekle, sil, üyelik = tek bit işlemi.
Küme cebri: O(U / 64)∪ ∩ − kelime kelime | & &~.
İşlemint maskesi (U ≤ 32)java.util.BitSet
ekle x / var mı x?a |= 1 << x · (a >> x & 1) == 1set(x) · get(x)
birleşim / kesişima | b · a & bor(b) · and(b)
fark / öğe sayısıa & ~b · Integer.bitCount(a)andNot(b) · cardinality()

Bellek U bit: 10⁶ öğelik evren ≈ 122 KB. Evren büyük ama küme seyrekse (ör. 3 öğe, U = 10⁹) hash tabanlı küme seçilir.

Küme Uygulamaları · Kodu adım adım çalıştır

Kod İzleme: bit işlemleriyle küme cebri

7 6 5 4 3 2 1 0 a 0 0 0 0 0 0 0 0 = {} b 0 0 0 0 0 0 0 0 = {}

İki boş küme: tüm bitler 0.

7 6 5 4 3 2 1 0 a 0 0 0 0 0 0 1 0 = {1} b 0 0 0 0 0 0 0 0 = {} 1 << x 0 0 0 0 0 0 1 0 maske = 2

1 << 1 = 2 maskesi; a |= maske → 1. bit 1 olur: 1 kümeye eklendi.

7 6 5 4 3 2 1 0 a 0 0 0 0 1 0 1 0 = {1, 3} b 0 0 0 0 0 0 0 0 = {} 1 << x 0 0 0 0 1 0 0 0 maske = 8

1 << 3 = 8 maskesi; a |= maske → 3. bit 1 olur: 3 kümeye eklendi.

7 6 5 4 3 2 1 0 a 0 0 0 1 1 0 1 0 = {1, 3, 4} b 0 0 0 0 0 0 0 0 = {} 1 << x 0 0 0 1 0 0 0 0 maske = 16

1 << 4 = 16 maskesi; a |= maske → 4. bit 1 olur: 4 kümeye eklendi.

7 6 5 4 3 2 1 0 a 0 0 0 1 1 0 1 0 = {1, 3, 4} b 0 0 0 0 1 0 0 0 = {3} 1 << x 0 0 0 0 1 0 0 0 maske = 8

b kümesine 3 eklenir: 3. bit yanar.

7 6 5 4 3 2 1 0 a 0 0 0 1 1 0 1 0 = {1, 3, 4} b 0 0 1 0 1 0 0 0 = {3, 5} 1 << x 0 0 1 0 0 0 0 0 maske = 32

b kümesine 5 eklenir: 5. bit yanar.

7 6 5 4 3 2 1 0 a 0 0 0 1 1 0 1 0 = {1, 3, 4} b 0 0 1 0 1 0 0 0 = {3, 5} a | b 0 0 1 1 1 0 1 0 = {1, 3, 4, 5}

VEYA: bitlerden biri 1 ise 1 → birleşim {1, 3, 4, 5}. Tek makine komutu.

7 6 5 4 3 2 1 0 a 0 0 0 1 1 0 1 0 = {1, 3, 4} b 0 0 1 0 1 0 0 0 = {3, 5} a & b 0 0 0 0 1 0 0 0 = {3}

VE: iki bit de 1 ise 1 → kesişim {3}.

7 6 5 4 3 2 1 0 a 0 0 0 1 1 0 1 0 = {1, 3, 4} b 0 0 1 0 1 0 0 0 = {3, 5} a & ~b 0 0 0 1 0 0 1 0 = {1, 4}

~b b'nin bitlerini ters çevirir; a & ~b: a'da olup b'de olmayanlar → fark {1, 4}.

7 6 5 4 3 2 1 0 a 0 0 0 1 1 0 1 0 = {1, 3, 4} b 0 0 1 0 1 0 0 0 = {3, 5} a >> 3 0 0 0 0 0 0 1 1 & 1 → 1

Üyelik testi: a >> 3 3. biti en sağa kaydırır, & 1 onu ayırır → 1, yani 3 ∈ a. O(1).

int a = 0, b = 0;
for (int x : new int[]{1, 3, 4})
  a |= 1 << x;          // x. biti yak
for (int x : new int[]{3, 5})
  b |= 1 << x;
int bir = a | b;        // birleşim
int kes = a & b;        // kesişim
int fark = a & ~b;      // fark
boolean var3 = (a >> 3 & 1) == 1;

Ç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.
  • Ö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.

Çoklu Küme · Kodu adım adım çalıştır

Kod İzleme: CokluKume sayaçları

void add(E e) {
  say.merge(e, 1, Integer::sum);  n++;
}
int count(E e) {
  return say.getOrDefault(e, 0);
}
boolean remove(E e) {
  Integer c = say.get(e);
  if (c == null) return false;
  if (c == 1) say.remove(e);
  else say.put(e, c - 1);
  n--;
  return true;
}
say (öğe → sayaç) öğe sayaç "a" 1 n (tekrar dahil) 1 M = {a}

merge: "a" yoktu → sayaç 1 ile eklenir; n = 1.

say (öğe → sayaç) öğe sayaç "a" 1 "b" 1 n (tekrar dahil) 2 M = {a, b}

merge: "b" yoktu → sayaç 1 ile eklenir; n = 2.

say (öğe → sayaç) öğe sayaç "a" 2 "b" 1 n (tekrar dahil) 3 M = {a, a, b}

merge: "a" vardı → Integer::sum ile 1 + 1 = 2; n = 3. Set olsaydı değişmezdi.

say (öğe → sayaç) öğe sayaç "a" 2 "b" 1 "c" 1 n (tekrar dahil) 4 M = {a, a, b, c}

merge: "c" yoktu → sayaç 1 ile eklenir; n = 4.

say (öğe → sayaç) öğe sayaç "a" 2 "b" 1 "c" 1 n (tekrar dahil) 4 M = {a, a, b, c}

count("a"): getOrDefault → 2. Tek hash araması: O(1).

say (öğe → sayaç) öğe sayaç "a" 2 "b" 1 "c" 1 n (tekrar dahil) 4 M = {a, a, b, c}

remove("a"): c = say.get(e) = 2.

say (öğe → sayaç) öğe sayaç "a" 1 "b" 1 "c" 1 n (tekrar dahil) 3 M = {a, b, c}

c = 2 > 1 → sayaç 1'e iner (girdi kalır); n = 3.

say (öğe → sayaç) öğe sayaç "a" 1 "b" 1 "c" 1 n (tekrar dahil) 3 M = {a, b, c}

remove("b"): c = say.get(e) = 1.

say (öğe → sayaç) öğe sayaç "a" 1 "c" 1 "b"0 (silindi) n (tekrar dahil) 2 M = {a, c}

c == 1 → sayaç 0 olacaktı: girdi tamamen silinir (0 sayaç saklanmaz); n = 2.

say (öğe → sayaç) öğe sayaç "a" 1 "c" 1 "x" eşlemede yok n (tekrar dahil) 2 M = {a, c}

remove("x"): c = say.get(e) = null.

say (öğe → sayaç) öğe sayaç "a" 1 "c" 1 "x" eşlemede yok n (tekrar dahil) 2 M = {a, c}

c == null → öğe yok: return false, n değişmez.

say (öğe → sayaç) öğe sayaç "a" 1 "c" 1 "b" eşlemede yok n (tekrar dahil) 2 M = {a, c}

count("b"): getOrDefault → 0. Tek hash araması: O(1).

say (öğe → sayaç) öğe sayaç "a" 1 "c" 1 n (tekrar dahil) 2 M = {a, c}

Sonuç M = {a, c}, n = 2. Tüm işlemler tek hash erişimi → ortalama O(1).

Çoklu Küme

Çoklu Eşleme (Multimap)

  • Çoklu eşleme (multimap): bir anahtar birden çok değere eşlenebilir (ders → öğrenciler).
  • Goodrich ADT: get(k) tüm değerleri, put(k, v), remove(k, v), removeAll(k), size() tüm (k, v) çiftleri.
  • Java'da hazır sınıf yok: Map<K, List<V>> ile kurulur.
BIL201 Ali Ece Can BIL203 Ece MAT101 Can Zeynep
Map<String, List<String>> m = new HashMap<>();
void put(String k, String v) {   // yoksa boş liste aç
  m.computeIfAbsent(k, x -> new ArrayList<>()).add(v);
}
List<String> get(String k) {
  return m.getOrDefault(k, List.of());
}

Çoklu kümeden farkı: yalnız kaç kez değil, değerlerin kendisi tutulur. Hash ile put / get O(1)*, remove(k, v) O(1 + liste boyu).

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 ebeveynini (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. Ebeveyn bağlantıları kök bulunana kadar izlenir.
  • Bu, arama ağaçlarındaki 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 ebeveyn bağlantısı değişir.
find(x):
  if x.ebeveyn != x:
    x.ebeveyn = find(x.ebeveyn)  // yol kısaltma
  return x.ebeveyn

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 ebeveyn
    kök1.ebeveyn = 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 ebeveyn[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[] ebeveyn = new int[12];
for (int i = 0; i < 12; i++) ebeveyn[i] = i;             // 12 tekil küme

Ayrık Kümeler (Union-Find)

Union İşlemi Adım Adım

öğe indis ebeveyn 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, ebeveyn[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. Yol kısaltma yok.

öğe indis ebeveyn 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 (ebeveyn[9] = 4).

öğe indis ebeveyn 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 (ebeveyn[0] = 1).

öğe indis ebeveyn 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'nın altına bağlanır (ebeveyn[6] = 5).

öğe indis ebeveyn 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'nın altına bağlanır (ebeveyn[10] = 5).

öğe indis ebeveyn 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 (ebeveyn[3] = 4).

öğe indis ebeveyn 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 (ebeveyn[2] = 4).

öğe indis ebeveyn 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 (ebeveyn[7] = 1).

öğe indis ebeveyn 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 (ebeveyn[5] = 4).

öğe indis ebeveyn 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 (ebeveyn[8] = 11).

öğe indis ebeveyn 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 (ebeveyn[11] = 1).

öğe indis ebeveyn 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 (ebeveyn[1] = 4).

öğe indis ebeveyn 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; kökler aynı → evet (toplam 4 adım).

Ayrık Kümeler (Union-Find)

Başka Bir Fikir: Quick-Find (Hızlı Bul)

  • Ağaç yerine düz dizi: id[i] = i'nin küme adı; aynı kümedekilerin id'si aynı. find(p) = id[p] → O(1).
  • union(p, q): id'si id[p] olan herkesi id[q] yap → tüm dizi taranır, O(n).
i id[i] 0 1 2 3 4 5 1 1 3 3 4 5 kümeler: {0, 1} {2, 3} {4} {5}

union(0,1), union(2,3) sonrası: 4 küme (aynı renk = aynı küme)

Yöntem (Sedgewick)findunionn − 1 union toplamı
Quick-Find (dizi, küme adı)O(1)O(n)O(n²)
Quick-Union (ebeveyn ağacı, naif)O(h), en kötü O(n)O(h)en kötü O(n²)
Rütbe/boyut + yol kısaltmaO(α(n)) amortize≈ O(n)

Ayrık Kümeler (Union-Find) · Kodu adım adım çalıştır

Kod İzleme: Quick-Find union

i id[i] 0 1 2 3 4 5 1 1 3 3 4 5 pid = 1 qid = 3 kümeler: {0, 1} {2, 3} {4} {5}

union(1, 3): p'nin küme adı pid = id[1] = 1, q'nunki qid = id[3] = 3.

i id[i] 0 1 2 3 4 5 1 1 3 3 4 5 pid = 1 qid = 3 kümeler: {0, 1} {2, 3} {4} {5}

pid ≠ qid: farklı kümeler, birleştirme gerekli. Şimdi tüm dizi taranacak.

i id[i] 0 1 2 3 4 5 3 1 3 3 4 5 ▲ i pid = 1 qid = 3 kümeler: {0, 2, 3} {1} {4} {5}

id[0] = 1 = pid → id[0] = 3 yapılır.

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 1 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[1] = 1 = pid → id[1] = 3 yapılır.

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 1 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[2] = 3 ≠ pid: dokunulmaz (ama yine de bakıldı).

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 1 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[3] = 3 ≠ pid: dokunulmaz (ama yine de bakıldı).

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 1 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[4] = 4 ≠ pid: dokunulmaz (ama yine de bakıldı).

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 1 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[5] = 5 ≠ pid: dokunulmaz (ama yine de bakıldı).

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 pid = 4 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

union(4, 3): p'nin küme adı pid = id[4] = 4, q'nunki qid = id[3] = 3.

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 pid = 4 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

pid ≠ qid: farklı kümeler, birleştirme gerekli. Şimdi tüm dizi taranacak.

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 4 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[0] = 3 ≠ pid: dokunulmaz (ama yine de bakıldı).

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 4 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[1] = 3 ≠ pid: dokunulmaz (ama yine de bakıldı).

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 4 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[2] = 3 ≠ pid: dokunulmaz (ama yine de bakıldı).

i id[i] 0 1 2 3 4 5 3 3 3 3 4 5 ▲ i pid = 4 qid = 3 kümeler: {0, 1, 2, 3} {4} {5}

id[3] = 3 ≠ pid: dokunulmaz (ama yine de bakıldı).

i id[i] 0 1 2 3 4 5 3 3 3 3 3 5 ▲ i pid = 4 qid = 3 kümeler: {0, 1, 2, 3, 4} {5}

id[4] = 4 = pid → id[4] = 3 yapılır.

i id[i] 0 1 2 3 4 5 3 3 3 3 3 5 ▲ i pid = 4 qid = 3 kümeler: {0, 1, 2, 3, 4} {5}

id[5] = 5 ≠ pid: dokunulmaz. Her union 6 hücrenin hepsine baktı: n öğede O(n); n − 1 union → O(n²).

int find(int p) { return id[p]; }  // O(1)
void union(int p, int q) {
  int pid = id[p], qid = id[q];
  if (pid == qid) return;
  for (int i = 0; i < id.length; i++)
    if (id[i] == pid)
      id[i] = qid;           // O(n) tarama
}

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 → en kötü O(n).
Naif: eski kök yeninin altına Boyuta göre: küçük ağaç büyüğün altına 1 2 3 4 5 1 2 3 4 5 yükseklik 0 · find(1) = 0 adım yükseklik 0 · find(1) = 0 adım

Başlangıç: 5 tekil küme; her düğüm kendi kökü. Sırayla union(1,2), union(2,3), union(3,4), union(4,5) yapılacak.

Naif: eski kök yeninin altına Boyuta göre: küçük ağaç büyüğün altına 1 2 3 4 5 1 2 3 4 5 yükseklik 1 · find(1) = 1 adım yükseklik 1 · find(1) = 1 adım

union(1, 2): iki tekil küme, iki yöntem de 1'i 2'nin altına bağlar.

Naif: eski kök yeninin altına Boyuta göre: küçük ağaç büyüğün altına 1 2 3 4 5 1 2 3 4 5 yükseklik 2 · find(1) = 2 adım yükseklik 1 · find(1) = 1 adım

union(2, 3): naif yöntem 2 kökünü (2 öğeli ağaç) tekil 3'ün altına bağlar → zincir uzar. Boyuta göre: tekil 3, büyük ağacın kökü 2'ye bağlanır.

Naif: eski kök yeninin altına Boyuta göre: küçük ağaç büyüğün altına 1 2 3 4 5 1 2 3 4 5 yükseklik 3 · find(1) = 3 adım yükseklik 1 · find(1) = 1 adım

union(3, 4): naif zincir bir kat daha uzar; boyuta göre 4 yine köke eklenir.

Naif: eski kök yeninin altına Boyuta göre: küçük ağaç büyüğün altına 1 2 3 4 5 1 2 3 4 5 yükseklik 4 · find(1) = 4 adım yükseklik 1 · find(1) = 1 adım

union(4, 5): naif ağaç zincir oldu (yükseklik n − 1 → find O(n)); boyuta göre ağaç yıldız (yükseklik 1).

İki basit fikir ağaçları basık tutar: (1) boyuta / rütbeye göre birleştirme ve (2) yol kısaltma. 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):
  ebeveyn[x] = x
  sira[x] = 0

find(x):   // yol kısaltmalı
  if x != ebeveyn[x]:
    ebeveyn[x] = find(ebeveyn[x])
  return ebeveyn[x]

link(x, y):   // x, y kök
  if sira[x] > sira[y]:
    ebeveyn[y] = x
  else:
    ebeveyn[x] = y
    if sira[x] == sira[y]:
      sira[y] = sira[y] + 1

union(x, y):
  link(find(x), find(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 ebeveyni 7 yapılır: ebeveyn[3] = ebeveyn[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: ebeveyn[n] ve sira[n] (veya boyut[n]) → O(n).
Tarjan (1975): m işlem dizisi O(m · α(n)) sürede biter. Kruskal, bağlantı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 ebeveyn;

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

SetDugum find(SetDugum d) {
  if (d != d.ebeveyn)
    d.ebeveyn = find(d.ebeveyn); // kısaltma
  return d.ebeveyn;
}
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.ebeveyn = kokY;
  } else if (kokX.sira > kokY.sira) {
    kokY.ebeveyn = kokX;
  } else { // eşit: birini seç
    kokX.ebeveyn = kokY;
    kokY.sira++;
  }
}

Ayrık Kümeler (Union-Find)

Java: Dizi Tabanlı Union-Find

class UnionFind {
  private final int[] ebeveyn, sira;
  UnionFind(int n) {
    ebeveyn = new int[n];  sira = new int[n];
    for (int i = 0; i < n; i++) ebeveyn[i] = i;
  }
  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;
  }
}
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.

Ayrık Kümeler (Union-Find) · Kodu adım adım çalıştır

Kod İzleme: find + union

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;
}
0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 0 1 2 3 4 5 sira 0 0 0 0 0 0

6 öğe, ebeveyn[i] = i, sira[i] = 0: 6 tekil küme (her kök kendini gösterir).

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 0 1 2 3 4 5 sira 0 0 0 0 0 0

find(0): ebeveyn[0] == 0 → 0 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 0 1 2 3 4 5 sira 0 0 0 0 0 0

find(1): ebeveyn[1] == 1 → 1 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 2 3 4 5 sira 0 1 0 0 0 0

Rütbeler eşit (0) → ebeveyn[0] = 1 ve yeni kökün rütbesi artar: sira[1] = 1.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 2 3 4 5 sira 0 1 0 0 0 0

find(2): ebeveyn[2] == 2 → 2 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 2 3 4 5 sira 0 1 0 0 0 0

find(3): ebeveyn[3] == 3 → 3 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 3 3 4 5 sira 0 1 0 1 0 0

Rütbeler eşit (0) → ebeveyn[2] = 3 ve yeni kökün rütbesi artar: sira[3] = 1.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 3 3 4 5 sira 0 1 0 1 0 0

find(0): ebeveyn[0] = 1 ≠ 0 → kök değil; find(1) ile yukarı çıkılır.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 3 3 4 5 sira 0 1 0 1 0 0

find(1): ebeveyn[1] == 1 → 1 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 3 3 4 5 sira 0 1 0 1 0 0

Dönüşte ebeveyn[0] = 1 (zaten kökün çocuğu, değişmez); find(0) = 1 döner.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 3 3 4 5 sira 0 1 0 1 0 0

find(2): ebeveyn[2] = 3 ≠ 2 → kök değil; find(3) ile yukarı çıkılır.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 3 3 4 5 sira 0 1 0 1 0 0

find(3): ebeveyn[3] == 3 → 3 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 1 3 3 4 5 sira 0 1 0 1 0 0

Dönüşte ebeveyn[2] = 3 (zaten kökün çocuğu, değişmez); find(2) = 3 döner.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 3 3 3 4 5 sira 0 1 0 2 0 0

Rütbeler eşit (1) → ebeveyn[1] = 3 ve yeni kökün rütbesi artar: sira[3] = 2.

Ayrık Kümeler (Union-Find) · Kodu adım adım çalıştır

Kod İzleme: find + union (devam)

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;
}
0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 3 3 3 4 5 sira 0 1 0 2 0 0

find(4): ebeveyn[4] == 4 → 4 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 3 3 3 4 5 sira 0 1 0 2 0 0

find(0): ebeveyn[0] = 1 ≠ 0 → kök değil; find(1) ile yukarı çıkılır.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 3 3 3 4 5 sira 0 1 0 2 0 0

find(1): ebeveyn[1] = 3 ≠ 1 → kök değil; find(3) ile yukarı çıkılır.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 3 3 3 4 5 sira 0 1 0 2 0 0

find(3): ebeveyn[3] == 3 → 3 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 1 3 3 3 4 5 sira 0 1 0 2 0 0

Dönüşte ebeveyn[1] = 3 (zaten kökün çocuğu, değişmez); find(1) = 3 döner.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 4 5 sira 0 1 0 2 0 0

Yol kısaltma: dönüşte ebeveyn[0] = 1 → 3; 0 artık doğrudan köke bağlı. find(0) = 3.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

sira[4] = 0 < sira[3] = 2 → alçak ağacın kökü 4, 3'e bağlanır: ebeveyn[4] = 3. Yükseklik artmaz.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

find(1): ebeveyn[1] = 3 ≠ 1 → kök değil; find(3) ile yukarı çıkılır.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

find(3): ebeveyn[3] == 3 → 3 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

Dönüşte ebeveyn[1] = 3 (zaten kökün çocuğu, değişmez); find(1) = 3 döner.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

find(4): ebeveyn[4] = 3 ≠ 4 → kök değil; find(3) ile yukarı çıkılır.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

find(3): ebeveyn[3] == 3 → 3 köktür, döndürülür.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

Dönüşte ebeveyn[4] = 3 (zaten kökün çocuğu, değişmez); find(4) = 3 döner.

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

kx = ky = 3 → 1 ve 4 zaten aynı kümede: return false (Kruskal: döngü).

0 1 2 3 4 5 i 0 1 2 3 4 5 ebeveyn 3 3 3 3 3 5 sira 0 1 0 2 0 0

Tek küme, kök 3. Yol kısaltma 0'ı köke taşıdı; sira[3] = 2 yalnızca üst sınır. Birlikte: işlem başına ≈ O(α(n)).

Ayrık Kümeler (Union-Find) · Kodu adım adım çalıştır

Kod İzleme: döngüyle find + yol kısaltma

0 1 2 3 4 x, kok i ebeveyn 0 0 0 1 1 2 2 3 3 4

Zincir 4 → 3 → 2 → 1 → 0 (kötü birleştirmeler sonucu). find(4) önce kökü arar: kok = 4.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 2 3 3 4

1. tur: ebeveyn[4] = 3 ≠ 4 → yukarı çık, kok = 3.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 2 3 3 4

1. tur: ebeveyn[3] = 2 ≠ 3 → yukarı çık, kok = 2.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 2 3 3 4

1. tur: ebeveyn[2] = 1 ≠ 2 → yukarı çık, kok = 1.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 2 3 3 4

ebeveyn[1] = 0 → kok = 0; ebeveyn[0] = 0 olduğundan döngü biter: kök bulundu (4 adım).

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 2 3 3 4

2. tur: ebeveyn[4] = 3, kök değil → bu düğüm kısaltılacak.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 2 3 0 4

Önce eski ebeveyn saklanır (sonraki = 3), sonra ebeveyn[4] = 0: 4 artık doğrudan köke bağlı.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 2 3 0 4

Yolda bir yukarı: x = 3.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 2 3 0 4

2. tur: ebeveyn[3] = 2, kök değil → bu düğüm kısaltılacak.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 0 3 0 4

Önce eski ebeveyn saklanır (sonraki = 2), sonra ebeveyn[3] = 0: 3 artık doğrudan köke bağlı.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 0 3 0 4

Yolda bir yukarı: x = 2.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 1 2 0 3 0 4

2. tur: ebeveyn[2] = 1, kök değil → bu düğüm kısaltılacak.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 0 2 0 3 0 4

Önce eski ebeveyn saklanır (sonraki = 1), sonra ebeveyn[2] = 0: 2 artık doğrudan köke bağlı.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 0 2 0 3 0 4

Yolda bir yukarı: x = 1.

0 1 2 3 4 x kok i ebeveyn 0 0 0 1 0 2 0 3 0 4

ebeveyn[1] = 0 = kok: 1 zaten köke bağlı, döngü biter.

0 1 2 3 4 kok i ebeveyn 0 0 0 1 0 2 0 3 0 4

Kök 0 döner. Yol üzerindeki herkes köke bağlandı: sonraki find'lar 1 adım. Özyineleme yok → yığın taşması riski de yok.

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;
}

Ayrık Kümeler (Union-Find) · Uygulama · Kodu adım adım çalıştır

Kod İzleme: Union-Find ile bağlantılı bileşen sayısı

0 1 2 3 4 5 6 i ebeveyn sira 0 0 1 1 2 2 3 3 4 4 5 5 6 6 0 0 0 0 0 0 0 say = 7

7 düğüm, 5 kenar. UnionFind(7): her düğüm kendi kümesinin kökü (ebeveyn[i] = i).

0 1 2 3 4 5 6 i ebeveyn sira 0 0 1 1 2 2 3 3 4 4 5 5 6 6 0 0 0 0 0 0 0 say = 7

Başta hiç kenar yok: 7 bileşen.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 2 2 3 3 4 4 5 5 6 6 0 1 0 0 0 0 0 say = 7

Kenar (0, 1): kökler farklı → union kümeleri birleştirir, true döner.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 2 2 3 3 4 4 5 5 6 6 0 1 0 0 0 0 0 say = 6

İki bileşen bire indi: say = 6.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 1 2 3 3 4 4 5 5 6 6 0 1 0 0 0 0 0 say = 6

Kenar (1, 2): kökler farklı → union kümeleri birleştirir, true döner.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 1 2 3 3 4 4 5 5 6 6 0 1 0 0 0 0 0 say = 5

İki bileşen bire indi: say = 5.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 1 2 4 3 4 4 5 5 6 6 0 1 0 0 1 0 0 say = 5

Kenar (3, 4): kökler farklı → union kümeleri birleştirir, true döner.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 1 2 4 3 4 4 5 5 6 6 0 1 0 0 1 0 0 say = 4

İki bileşen bire indi: say = 4.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 1 2 4 3 4 4 5 5 6 6 0 1 0 0 1 0 0 say = 4

Kenar (0, 2): find(0) = find(2) → zaten aynı bileşende; union false, say değişmez (bu kenar bir döngü kapatır).

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 1 2 4 3 4 4 6 5 6 6 0 1 0 0 1 0 1 say = 4

Kenar (5, 6): kökler farklı → union kümeleri birleştirir, true döner.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 1 2 4 3 4 4 6 5 6 6 0 1 0 0 1 0 1 say = 3

İki bileşen bire indi: say = 3.

0 1 2 3 4 5 6 i ebeveyn sira 1 0 1 1 1 2 4 3 4 4 6 5 6 6 0 1 0 0 1 0 1 say = 3

Sonuç 3 bileşen: {0, 1, 2}, {3, 4}, {5, 6}. Toplam O(n + m·α(n)); kenarlar sonradan gelse de sayaç anında güncel (dinamik bağlılık).

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--;            // iki bileşen birleşti
  return say;
}

Özet

Sık Yapılan Hatalar

1. Kümedeki öğeyi değiştirmekEklendikten sonra hashCode'u değişen nesne yanlış kovada kalır: contains false döner.p.x = 5; set.contains(p) → false
2. equals var, hashCode yok"Eşit" iki nesne büyük olasılıkla farklı kovalara düşer → HashSet'te iki kez bulunur. İkisi birlikte ezilmeli.
3. Gezinirken silmekfor-each içinde set.remove(x) → ConcurrentModificationException.set.removeIf(x -> x % 2 == 0);
4. Kökler yerine öğeleri bağlamakebeveyn[x] = y yanlış: x'in ağacı bölünür. Doğrusu:ebeveyn[find(x)] = find(y);
5. Eşit rütbede artırmayı unutmaksira[ky]++ yoksa rütbe yüksekliği izlemez; O(log n) garantisi kaybolur.
6. TreeSet ile tutarsız comparecompare(a, b) == 0 olan farklı nesnelerden yalnız biri saklanır (ör. yalnız soyada göre sıralama).

Ö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

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şlemleri, rütbeye göre birleştirme ve yol kısaltma, ağaç üzerinde.

Ö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 ebeveyn[x] = y). Kaç küme kalır? find(0) nedir?
  5. 0..4 için sırayla union(0,1), union(1,2), union(3,4), union(2,0) yapılıyor. Hangi union false döner? Kaç bağlantılı bileşen kalır?
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): ebeveyn[0]=1, sira[1]=1 · union(2,3): ebeveyn[2]=3, sira[3]=1 · union(1,3): eşit → ebeveyn[1]=3, sira[3]=2 · union(4,5): ebeveyn[4]=5. İki küme: {0,1,2,3} ve {4,5}; find(0) = 3.
  5. union(2,0) false döner: 0 ve 2 zaten aynı kümede (bu kenar bir döngü kapatır). Bileşenler {0, 1, 2} ve {3, 4} → 2.

Özet

Alıştırmalar

  1. Boş HashSet'e (16 kova, kova = x & 15) 12, 28, 44, 7 eklenir. Kovalar? add(28) ne döner?
  2. U = {0..7}: A = 1011 0010₂, B = 0111 0100₂. A | B, A & B, A & ~B kümeleri nedir?
  3. Quick-Find ile n öğede n − 1 union: toplam kaç hücreye bakılır?
  4. ebeveyn = [1, 2, 3, 3, 3] iken yinelemeli find(0) sonrası ebeveyn?
  5. Rütbeye göre birleştirmede rütbe en fazla kaç olabilir? Neden?
  6. Kenarlar tek tek geliyor; her kenardan sonra "çizge bağlı mı?" Hangi yapı, maliyet?
Cevaplar / ipuçları
  1. 12, 28, 44 → kova 12 (zincir), 7 → kova 7. 28 yeni öğe: true.
  2. A = {1, 4, 5, 7}, B = {2, 4, 5, 6} → ∪ {1, 2, 4, 5, 6, 7} · ∩ {4, 5} · A − B {1, 7}.
  3. Her union n hücre: (n − 1) · n → O(n²).
  4. Kök 3; ebeveyn = [3, 3, 3, 3, 3].
  5. ⌊log₂ n⌋: rütbesi r olan ağaçta en az 2r öğe var.
  6. Union-Find + sayaç (başarılı union'da say−−; say == 1 ⇔ bağlı): O(m·α(n)).

Ö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 13: Kümeler ve Ayrık Kümeler
Sonraki sunum: Bölüm 14: Çizgeler · Çalışma soruları · Simülatörler: Set ADT Simulator, Disjoint Set Union Simulator · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.