⚡ 10.2: Quick-Find Algoritması

Hızlı Sorgu, Yavaş Birleştirme (Eager Approach)

📌 Quick-Find Yaklaşımı

İlk yaklaşımımız: Find işlemini O(1) yapmak için her elemanın hangi kümeye ait olduğunu doğrudan tutan bir dizi kullanacağız.

Aynı kümedeki tüm elemanlar aynı ID değerine sahip olacak.

📊 Veri Yapısı

💡 Temel Fikir

  • id[] dizisi: Her eleman için bir küme ID'si tut
  • p ve q bağlıid[p] == id[q]
  • Find işlemi: Sadece id[p] değerini döndür

Başlangıç durumu: Her eleman kendi ID'sine sahip (N ayrı küme)

Başlangıç: id[i] = i
0
0
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9

Her eleman kendi kümesinde (10 farklı küme)

🔧 İşlemler

🔍 Find(p)

Çok basit: return id[p]

Karmaşıklık: O(1) - Tek dizi erişimi!

🔗 Union(p, q)

p ve q'yu birleştirmek için, tüm id[p] değerine sahip elemanların ID'sini id[q] yapmalıyız.

  • Diziyi baştan sona tara
  • id[i] == id[p] olan her i için: id[i] = id[q]
  • Karmaşıklık: O(N) - Tüm diziyi gezmeli!

📝 Adım Adım Örnek

Başlangıç: index: 0 1 2 3 4 5 6 7 8 9 id[]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ Her eleman kendi kümesinde ═══════════════════════════════════════════════════════════ union(4, 3): 4'ün ID'si (4) → 3'ün ID'sine (3) değiş ───────────────────────────────────────────────────────────── index: 0 1 2 3 4 5 6 7 8 9 id[]: [0, 1, 2, 3, 3, 5, 6, 7, 8, 9] ↑ değişti ═══════════════════════════════════════════════════════════ union(3, 8): ID=3 olan herkesin ID'si → 8 3 ve 4'ün ID'si 3 → 8 olmalı ───────────────────────────────────────────────────────────── index: 0 1 2 3 4 5 6 7 8 9 id[]: [0, 1, 2, 8, 8, 5, 6, 7, 8, 9] ↑ ↑ 2 eleman değişti! ═══════════════════════════════════════════════════════════ union(6, 5): 6'nın ID'si (6) → 5'in ID'sine (5) ───────────────────────────────────────────────────────────── index: 0 1 2 3 4 5 6 7 8 9 id[]: [0, 1, 2, 8, 8, 5, 5, 7, 8, 9] ↑ ═══════════════════════════════════════════════════════════ union(9, 4): 9'un ID'si (9) → 4'ün ID'sine (8) ───────────────────────────────────────────────────────────── index: 0 1 2 3 4 5 6 7 8 9 id[]: [0, 1, 2, 8, 8, 5, 5, 7, 8, 8] ↑ ═══════════════════════════════════════════════════════════ union(2, 1): 2'nin ID'si (2) → 1'in ID'sine (1) ───────────────────────────────────────────────────────────── index: 0 1 2 3 4 5 6 7 8 9 id[]: [0, 1, 1, 8, 8, 5, 5, 7, 8, 8] ↑ ═══════════════════════════════════════════════════════════ connected(8, 9)? → id[8]=8, id[9]=8 → TRUE ✓ connected(5, 4)? → id[5]=5, id[4]=8 → FALSE ✗ ═══════════════════════════════════════════════════════════ union(5, 0): ID=5 olan herkes → 0'ın ID'sine (0) 5 ve 6'nın ID'si 5 → 0 ───────────────────────────────────────────────────────────── index: 0 1 2 3 4 5 6 7 8 9 id[]: [0, 1, 1, 8, 8, 0, 0, 7, 8, 8] ↑ ↑ 2 eleman değişti! ═══════════════════════════════════════════════════════════ union(7, 2): 7'nin ID'si (7) → 2'nin ID'sine (1) ───────────────────────────────────────────────────────────── index: 0 1 2 3 4 5 6 7 8 9 id[]: [0, 1, 1, 8, 8, 0, 0, 1, 8, 8] ↑ ═══════════════════════════════════════════════════════════ union(6, 1): ID=0 olan herkes → 1'in ID'sine (1) 0, 5, 6'nın ID'si 0 → 1 ───────────────────────────────────────────────────────────── index: 0 1 2 3 4 5 6 7 8 9 id[]: [1, 1, 1, 8, 8, 1, 1, 1, 8, 8] ↑ ↑ ↑ 3 eleman değişti! ═══════════════════════════════════════════════════════════ connected(0, 7)? → id[0]=1, id[7]=1 → TRUE ✓ Son durum: 2 bileşen Bileşen 1: {0, 1, 2, 5, 6, 7} (ID = 1) Bileşen 2: {3, 4, 8, 9} (ID = 8)

💻 Python Implementasyonu

Quick-Find Implementasyonu
class QuickFind:
    """
    Quick-Find Union-Find Implementasyonu
    
    Eager Approach (İstekli Yaklaşım):
    - Find çok hızlı: O(1)
    - Union yavaş: O(N)
    
    Veri yapısı:
    - id[i]: i elemanının küme ID'si
    - Aynı kümedeki tüm elemanlar aynı ID'ye sahip
    """
    
    def __init__(self, n: int):
        """
        N nesne ile Union-Find oluştur.
        Başlangıçta her nesne kendi kümesinde.
        
        Zaman: O(N)
        Alan: O(N)
        """
        # Her eleman kendi ID'sine sahip
        self.id = list(range(n))
        self.n = n
        self._count = n  # Küme sayısı
    
    def find(self, p: int) -> int:
        """
        p'nin küme ID'sini döndür.
        
        Zaman: O(1) - sadece dizi erişimi
        """
        return self.id[p]
    
    def connected(self, p: int, q: int) -> bool:
        """
        p ve q aynı kümede mi?
        
        Zaman: O(1)
        """
        return self.find(p) == self.find(q)
    
    def union(self, p: int, q: int) -> None:
        """
        p ve q'nun kümelerini birleştir.
        
        Zaman: O(N) - tüm diziyi taramak gerekiyor!
        
        Algoritma:
        1. p ve q'nun ID'lerini bul
        2. Eğer aynıysa: birşey yapma
        3. Değilse: p'nin ID'sine sahip TÜM elemanları
           q'nun ID'sine değiştir
        """
        pid = self.find(p)
        qid = self.find(q)
        
        # Zaten aynı kümedeler
        if pid == qid:
            return
        
        # p'nin ID'sine sahip tüm elemanları değiştir
        # Bu O(N) işlem!
        for i in range(self.n):
            if self.id[i] == pid:
                self.id[i] = qid
        
        self._count -= 1  # Bir küme azaldı
    
    def count(self) -> int:
        """Kaç ayrı küme var?"""
        return self._count
    
    def display(self):
        """Mevcut durumu göster"""
        print(f"id[]: {self.id}")
        
        # Kümeleri grupla
        groups = {}
        for i in range(self.n):
            gid = self.id[i]
            if gid not in groups:
                groups[gid] = []
            groups[gid].append(i)
        
        print(f"Kümeler ({self.count()} adet):")
        for gid, members in sorted(groups.items()):
            print(f"  ID={gid}: {members}")


# ===== DEMO =====
print("=" * 60)
print("QUICK-FIND DEMO")
print("=" * 60)

qf = QuickFind(10)
print(f"\nBaşlangıç ({qf.n} eleman, {qf.count()} küme):")
qf.display()

# İşlemler
operations = [
    ("union", 4, 3),
    ("union", 3, 8),
    ("union", 6, 5),
    ("union", 9, 4),
    ("union", 2, 1),
    ("query", 8, 9),
    ("query", 5, 4),
    ("union", 5, 0),
    ("union", 7, 2),
    ("union", 6, 1),
    ("query", 0, 7),
]

print("\n" + "─" * 60)
print("İşlemler:")
print("─" * 60)

for op in operations:
    if op[0] == "union":
        _, p, q = op
        print(f"\nunion({p}, {q}):")
        qf.union(p, q)
        qf.display()
    else:
        _, p, q = op
        result = qf.connected(p, q)
        symbol = "✓" if result else "✗"
        print(f"\nconnected({p}, {q})? → {result} {symbol}")
Çıktı bekleniyor...

💰 Maliyet Analizi

Quick-Find Maliyet Testi
import time

class QuickFind:
    def __init__(self, n):
        self.id = list(range(n))
        self.n = n
        self.array_accesses = 0  # Dizi erişim sayacı
    
    def find(self, p):
        self.array_accesses += 1
        return self.id[p]
    
    def union(self, p, q):
        # 2 find işlemi
        pid = self.find(p)
        qid = self.find(q)
        
        if pid == qid:
            return
        
        # N elemana bak, eşleşenleri değiştir
        for i in range(self.n):
            self.array_accesses += 1  # Okuma
            if self.id[i] == pid:
                self.id[i] = qid
                self.array_accesses += 1  # Yazma


def test_quick_find(n, m):
    """N elemanlı yapıda M rastgele union yap"""
    import random
    random.seed(42)
    
    qf = QuickFind(n)
    start = time.time()
    
    for _ in range(m):
        p = random.randint(0, n-1)
        q = random.randint(0, n-1)
        qf.union(p, q)
    
    elapsed = time.time() - start
    return elapsed, qf.array_accesses


print("=" * 60)
print("QUICK-FIND MALİYET ANALİZİ")
print("=" * 60)
print(f"\n{'N':>10} {'M işlem':>12} {'Süre (s)':>12} {'Dizi Erişimi':>15}")
print("-" * 60)

# Farklı boyutlarda test
test_cases = [
    (100, 100),
    (1000, 1000),
    (5000, 5000),
    (10000, 10000),
]

for n, m in test_cases:
    elapsed, accesses = test_quick_find(n, m)
    print(f"{n:>10,} {m:>12,} {elapsed:>12.4f} {accesses:>15,}")

print("\n" + "─" * 60)
print("💡 Gözlem:")
print("─" * 60)
print("""
Her union işlemi O(N) maliyet!
N büyüdükçe dizi erişimi N² gibi artıyor.

Bu çok pahalı:
- N = 10⁹ (1 milyar) nesne
- N union işlemi
- Toplam: 10⁹ × 10⁹ = 10¹⁸ dizi erişimi!
- Saniyede 10⁹ işlem yapan bilgisayarda: 30+ yıl!
""")
Çıktı bekleniyor...

📊 Karmaşıklık Özeti

İşlem Karmaşıklık Açıklama
init(n) O(N) N elemanlı dizi oluştur
find(p) O(1) Tek dizi erişimi
union(p, q) O(N) Tüm diziyi tara
connected(p, q) O(1) 2 × find

⚠️ Problem: Quadratic Time

N nesne üzerinde N union işlemi için:

  • Her union O(N)
  • Toplam: O(N²) dizi erişimi

Bu quadratic (karesel) karmaşıklık, büyük veri setleri için kabul edilemez!

⚖️ Değerlendirme

✅ Avantajlar

  • Find çok hızlı: O(1)
  • Anlaması ve kodlaması kolay
  • Bağlantı sorgusu O(1)

❌ Dezavantajlar

  • Union çok yavaş: O(N)
  • Her union tüm diziyi tarar
  • Büyük N için kullanışsız

🎯 Sonuç ve Devam

Quick-Find, find işlemini O(1) yapmak için union'ı feda ediyor.

Peki tam tersini yapsak? Union'ı hızlı yapıp find'ı yavaşlatsak?

Bir sonraki bölümde: Quick-Union algoritması ile farklı bir yaklaşım deneyeceğiz!

🎮 İnteraktif Quick-Find Simülasyonu

Union: ile | Find:
id[] = [0,1,2,3,4,5,6,7] - Her eleman kendi kümesinde. Aynı id = aynı küme!