🎯 10.6: Union-Find Uygulamaları

Gerçek Dünya Problemleri ve Çözümleri

📌 Union-Find Nerede Kullanılır?

Union-Find, birçok önemli problemin temel yapı taşıdır. Bu bölümde gerçek uygulama örneklerini göreceğiz.

🌍 Uygulama Alanları

🌲 Kruskal MST

Minimum Spanning Tree algoritmasında döngü kontrolü.

Graf bölümünde detaylı göreceğiz!

🎮 Percolation

Sıvı veya elektrik yukarıdan aşağıya geçebilir mi?

Monte Carlo simülasyonu

👥 Sosyal Ağlar

Arkadaşlık grupları, topluluk tespiti.

Dinamik bağlantı sorguları

🖼️ Görüntü Segmentasyonu

Benzer pikselleri grupla, nesneleri ayır.

Bölge büyütme algoritması

🔌 Ağ Bağlantısı

İki bilgisayar aynı ağda mı?

Dinamik graf sorguları

🧩 LeetCode Problemleri

Number of Islands, Accounts Merge, vb.

Popüler mülakat soruları

🌲 Uygulama 1: Kruskal MST (Önizleme)

Minimum Spanning Tree Problemi

Bir grafta tüm düğümleri minimum maliyetle bağlayan ağaç.

Kruskal algoritması: Kenarları sırala, döngü oluşturmayanları ekle.

Kruskal Algoritması: ──────────────────── 1. Tüm kenarları ağırlığa göre sırala 2. Her kenar için: - Eğer kenarın iki ucu FARKLI kümelerde: → Kenarı MST'ye ekle → İki kümeyi birleştir (UNION!) - Eğer AYNI kümede: → Kenarı atla (döngü oluşur) Union-Find kullanımı: - connected(u, v): u ve v aynı kümede mi? (döngü kontrolü) - union(u, v): Kenar eklendi, kümeleri birleştir
Kruskal MST Taslağı
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
    
    def find(self, p):
        if self.parent[p] != p:
            self.parent[p] = self.find(self.parent[p])
        return self.parent[p]
    
    def union(self, p, q):
        rp, rq = self.find(p), self.find(q)
        if rp == rq: return False
        if self.rank[rp] < self.rank[rq]: rp, rq = rq, rp
        self.parent[rq] = rp
        if self.rank[rp] == self.rank[rq]: self.rank[rp] += 1
        return True
    
    def connected(self, p, q):
        return self.find(p) == self.find(q)


def kruskal_mst(n, edges):
    """
    Kruskal MST Algoritması
    
    Args:
        n: Düğüm sayısı
        edges: [(weight, u, v), ...] formatında kenarlar
    
    Returns:
        MST kenarları ve toplam ağırlık
    """
    # Kenarları ağırlığa göre sırala
    edges = sorted(edges)
    
    uf = UnionFind(n)
    mst = []
    total_weight = 0
    
    for weight, u, v in edges:
        # Döngü kontrolü: u ve v farklı kümelerde mi?
        if not uf.connected(u, v):
            # Kenarı MST'ye ekle
            mst.append((u, v, weight))
            total_weight += weight
            # Kümeleri birleştir
            uf.union(u, v)
            print(f"  Kenar ({u}-{v}, w={weight}) eklendi ✓")
        else:
            print(f"  Kenar ({u}-{v}, w={weight}) atlandı (döngü) ✗")
    
    return mst, total_weight


# Örnek graf
print("=" * 50)
print("KRUSKAL MST ÖRNEĞİ")
print("=" * 50)

# Kenarlar: (weight, u, v)
edges = [
    (4, 0, 1), (8, 0, 7), (11, 1, 7),
    (8, 1, 2), (7, 2, 3), (4, 2, 5),
    (2, 2, 8), (9, 3, 4), (14, 3, 5),
    (10, 4, 5), (2, 5, 6), (1, 6, 7),
    (6, 6, 8), (7, 7, 8)
]

print(f"\nGraf: 9 düğüm, {len(edges)} kenar")
print("\nKruskal algoritması çalışıyor:\n")

mst, total = kruskal_mst(9, edges)

print(f"\nMST Kenarları: {mst}")
print(f"Toplam Ağırlık: {total}")
print(f"\n→ Graf bölümünde Kruskal'ı detaylı göreceğiz!")
Çıktı bekleniyor...

👥 Uygulama 2: Sosyal Ağ Analizi

Arkadaşlık Ağı ve Topluluklar
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
        self.size = [1] * n  # Her kümenin boyutu
        self._count = n
    
    def find(self, p):
        if self.parent[p] != p:
            self.parent[p] = self.find(self.parent[p])
        return self.parent[p]
    
    def union(self, p, q):
        rp, rq = self.find(p), self.find(q)
        if rp == rq: return False
        if self.rank[rp] < self.rank[rq]: rp, rq = rq, rp
        self.parent[rq] = rp
        self.size[rp] += self.size[rq]
        if self.rank[rp] == self.rank[rq]: self.rank[rp] += 1
        self._count -= 1
        return True
    
    def connected(self, p, q):
        return self.find(p) == self.find(q)
    
    def component_size(self, p):
        return self.size[self.find(p)]
    
    def count(self):
        return self._count


class SocialNetwork:
    """Sosyal ağ analizi için Union-Find wrapper"""
    
    def __init__(self, users):
        self.users = users
        self.user_to_id = {u: i for i, u in enumerate(users)}
        self.id_to_user = {i: u for i, u in enumerate(users)}
        self.uf = UnionFind(len(users))
        self.friendships = []
    
    def add_friendship(self, user1, user2):
        """İki kullanıcıyı arkadaş yap"""
        id1, id2 = self.user_to_id[user1], self.user_to_id[user2]
        if self.uf.union(id1, id2):
            self.friendships.append((user1, user2))
            return True
        return False
    
    def are_connected(self, user1, user2):
        """İki kullanıcı aynı toplulukta mı?"""
        id1, id2 = self.user_to_id[user1], self.user_to_id[user2]
        return self.uf.connected(id1, id2)
    
    def get_communities(self):
        """Tüm toplulukları döndür"""
        communities = {}
        for user, uid in self.user_to_id.items():
            root = self.uf.find(uid)
            if root not in communities:
                communities[root] = []
            communities[root].append(user)
        return list(communities.values())
    
    def community_size(self, user):
        """Kullanıcının topluluğundaki kişi sayısı"""
        uid = self.user_to_id[user]
        return self.uf.component_size(uid)
    
    def num_communities(self):
        """Toplam topluluk sayısı"""
        return self.uf.count()


# Demo
print("=" * 60)
print("SOSYAL AĞ ANALİZİ")
print("=" * 60)

users = ["Ali", "Berk", "Cemre", "Deniz", "Elif", 
         "Fatma", "Gül", "Hakan", "Irmak", "Jale"]

network = SocialNetwork(users)

print(f"\n👥 Kullanıcılar: {users}")
print(f"📊 Başlangıç: {network.num_communities()} ayrı topluluk")

# Arkadaşlıklar
friendships = [
    ("Ali", "Berk"),      # Grup 1 başlangıç
    ("Cemre", "Deniz"),   # Grup 2 başlangıç
    ("Elif", "Fatma"),    # Grup 3 başlangıç
    ("Gül", "Hakan"),     # Grup 4 başlangıç
    ("Ali", "Cemre"),     # Grup 1+2 birleşti
    ("Elif", "Gül"),      # Grup 3+4 birleşti
    ("Irmak", "Ali"),     # Irmak grup 1+2'ye katıldı
]

print("\n🤝 Arkadaşlıklar ekleniyor:")
for u1, u2 in friendships:
    network.add_friendship(u1, u2)
    print(f"  {u1} ↔ {u2}")

print(f"\n📊 Şu an {network.num_communities()} topluluk var")

# Toplulukları göster
print("\n🏘️ Topluluklar:")
for i, community in enumerate(network.get_communities(), 1):
    print(f"  Grup {i} ({len(community)} kişi): {', '.join(community)}")

# Sorgular
print("\n🔍 Bağlantı Sorguları:")
queries = [
    ("Ali", "Deniz"),   # Aynı grupta
    ("Ali", "Fatma"),   # Farklı grupta
    ("Elif", "Hakan"),  # Aynı grupta
    ("Jale", "Ali"),    # Jale tek başına
]

for u1, u2 in queries:
    connected = network.are_connected(u1, u2)
    symbol = "✅" if connected else "❌"
    print(f"  {u1} ↔ {u2}: {symbol}")

# İstatistikler
print("\n📈 Topluluk Boyutları:")
for user in ["Ali", "Elif", "Jale"]:
    size = network.community_size(user)
    print(f"  {user}'nin topluluğu: {size} kişi")
Çıktı bekleniyor...

🧩 Mülakat Soruları

1. Number of Islands (LeetCode 200) Orta

2D grid'de kaç ada var? 1'ler kara, 0'lar su. Yatay/dikey bitişik 1'ler aynı ada.

Örnek:
Input: grid = [["1","1","0"],["1","0","0"],["0","0","1"]]
Output: 2 (sol üstte bir ada, sağ altta bir ada)
💡 İpucu: Her 1 hücresini ayrı bir ada olarak başlat, sonra komşu 1'leri Union-Find ile birleştir.

🧠 Düşünce Süreci

  1. Her '1' hücresini gördüğünde ada sayısını 1 artır
  2. Üst ve sol komşuya bak, onlar da '1' ise union yap
  3. Union yapınca ada sayısını 1 azalt (iki ada birleşti)
  4. Final ada sayısını döndür
Çözüm: Number of Islands
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
        self.count = 0  # Ada sayısı
    
    def find(self, p):
        if self.parent[p] != p:
            self.parent[p] = self.find(self.parent[p])
        return self.parent[p]
    
    def union(self, p, q):
        rp, rq = self.find(p), self.find(q)
        if rp == rq: return
        if self.rank[rp] < self.rank[rq]: rp, rq = rq, rp
        self.parent[rq] = rp
        if self.rank[rp] == self.rank[rq]: self.rank[rp] += 1
        self.count -= 1  # İki ada birleşti
    
    def add_island(self):
        self.count += 1


def num_islands(grid):
    """
    LeetCode 200: Number of Islands
    
    Union-Find çözümü:
    1. Her 1 hücresini bir ada olarak başlat
    2. Komşu 1'leri birleştir
    3. Final ada sayısını döndür
    """
    if not grid or not grid[0]:
        return 0
    
    rows, cols = len(grid), len(grid[0])
    uf = UnionFind(rows * cols)
    
    def cell_id(r, c):
        return r * cols + c
    
    # Grid'i tara
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                uf.add_island()
                if r > 0 and grid[r-1][c] == '1':
                    uf.union(cell_id(r, c), cell_id(r-1, c))
                if c > 0 and grid[r][c-1] == '1':
                    uf.union(cell_id(r, c), cell_id(r, c-1))
    
    return uf.count


# Test
print("=" * 50)
print("NUMBER OF ISLANDS (LeetCode 200)")
print("=" * 50)

grid1 = [['1','1','1'],['1','0','0'],['0','0','1']]
print("\nGrid 1:")
for row in grid1: print("  " + " ".join(row))
print(f"Ada sayısı: {num_islands(grid1)}")  # 2

grid2 = [['1','1','0','0'],['0','0','1','0'],['0','0','0','1']]
print("\nGrid 2:")
for row in grid2: print("  " + " ".join(row))
print(f"Ada sayısı: {num_islands(grid2)}")  # 3
Çıktı bekleniyor...
⏱️ Karmaşıklık:
Zaman: O(M×N × α(M×N)) ≈ O(M×N)
Alan: O(M×N) - parent dizisi
2. Accounts Merge (LeetCode 721) Zor

Her hesap [isim, email1, email2, ...] şeklinde. Aynı email'e sahip hesapları birleştirin.

Örnek:
Input: [["Ali","ali@g.com","ali@y.com"],["Ali","ali@g.com","ali2@g.com"]]
Output: [["Ali","ali@g.com","ali@y.com","ali2@g.com"]]
💡 İpucu: Her email'i bir düğüm olarak düşün. Aynı hesaptaki email'leri birleştir.

🧠 Düşünce Süreci

  1. Her email'e bir ID ver
  2. Aynı hesaptaki email'leri Union-Find ile birleştir
  3. Her kümenin root'una göre grupla
  4. Email'leri sırala ve isimle birlikte döndür
Çözüm: Accounts Merge
class UnionFind:
    def __init__(self):
        self.parent = {}
    
    def find(self, x):
        if x not in self.parent:
            self.parent[x] = x
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
    
    def union(self, x, y):
        self.parent[self.find(x)] = self.find(y)


def accounts_merge(accounts):
    uf = UnionFind()
    email_to_name = {}
    
    # Her hesaptaki email'leri birleştir
    for account in accounts:
        name = account[0]
        first_email = account[1]
        for email in account[1:]:
            email_to_name[email] = name
            uf.union(email, first_email)
    
    # Root'a göre grupla
    from collections import defaultdict
    groups = defaultdict(list)
    for email in email_to_name:
        groups[uf.find(email)].append(email)
    
    # Sonuçları formatla
    result = []
    for root, emails in groups.items():
        name = email_to_name[root]
        result.append([name] + sorted(emails))
    
    return result


# Test
accounts = [
    ["Ali", "ali@gmail.com", "ali@yahoo.com"],
    ["Berk", "berk@gmail.com"],
    ["Ali", "ali@gmail.com", "ali_work@corp.com"],
    ["Cemre", "cemre@outlook.com"]
]

print("Hesaplar:")
for acc in accounts:
    print(f"  {acc}")

print("\nBirleştirilmiş:")
for acc in accounts_merge(accounts):
    print(f"  {acc}")
Çıktı bekleniyor...
⏱️ Karmaşıklık: O(N × α(N)) ≈ O(N) where N = toplam email sayısı
3. Redundant Connection (LeetCode 684) Orta

N düğümlü ağaca 1 fazla kenar eklendi. Döngü oluşturan kenarı bulun.

Örnek:
Input: edges = [[1,2],[1,3],[2,3]]
Output: [2,3] (bu kenar döngü oluşturuyor)
💡 İpucu: Kenarları sırayla ekle. İki uç zaten bağlıysa, bu kenar fazladan!

🧠 Düşünce Süreci

  1. Boş Union-Find başlat
  2. Her kenar için: iki uç zaten bağlı mı kontrol et
  3. Bağlıysa → bu kenar döngü oluşturuyor, döndür
  4. Değilse → union yap, devam et
Çözüm: Redundant Connection
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n + 1))
        self.rank = [0] * (n + 1)
    
    def find(self, p):
        if self.parent[p] != p:
            self.parent[p] = self.find(self.parent[p])
        return self.parent[p]
    
    def union(self, p, q):
        rp, rq = self.find(p), self.find(q)
        if rp == rq:
            return False  # Zaten bağlı!
        if self.rank[rp] < self.rank[rq]:
            rp, rq = rq, rp
        self.parent[rq] = rp
        if self.rank[rp] == self.rank[rq]:
            self.rank[rp] += 1
        return True


def find_redundant_connection(edges):
    n = len(edges)
    uf = UnionFind(n)
    
    for u, v in edges:
        if not uf.union(u, v):
            return [u, v]  # Bu kenar döngü oluşturdu!
    
    return []


# Test
print("=" * 50)
print("REDUNDANT CONNECTION (LeetCode 684)")
print("=" * 50)

test_cases = [
    [[1,2], [1,3], [2,3]],
    [[1,2], [2,3], [3,4], [1,4], [1,5]],
]

for edges in test_cases:
    print(f"\nEdges: {edges}")
    result = find_redundant_connection(edges)
    print(f"Fazladan kenar: {result}")
Çıktı bekleniyor...
⏱️ Karmaşıklık: O(N × α(N)) ≈ O(N)

🎮 Uygulama 4: Percolation

Percolation Problemi

N×N grid var. Her hücre açık veya kapalı. Üstten alta bir yol var mı?

Uygulama: Sıvı geçirgenliği, elektrik iletkenliği, ormanda yangın yayılımı.

Percolation Simülasyonu
import random

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
    
    def find(self, p):
        if self.parent[p] != p:
            self.parent[p] = self.find(self.parent[p])
        return self.parent[p]
    
    def union(self, p, q):
        rp, rq = self.find(p), self.find(q)
        if rp == rq: return
        if self.rank[rp] < self.rank[rq]: rp, rq = rq, rp
        self.parent[rq] = rp
        if self.rank[rp] == self.rank[rq]: self.rank[rp] += 1
    
    def connected(self, p, q):
        return self.find(p) == self.find(q)


class Percolation:
    """
    Percolation sistemi.
    
    N×N grid + 2 sanal düğüm (üst ve alt).
    Üst düğüm: tüm üst satıra bağlı
    Alt düğüm: tüm alt satıra bağlı
    
    Percolates = üst ve alt düğüm bağlı mı?
    """
    
    def __init__(self, n):
        self.n = n
        self.grid = [[False] * n for _ in range(n)]
        self.open_count = 0
        
        # N*N hücre + 2 sanal düğüm
        # 0 ~ N*N-1: gerçek hücreler
        # N*N: üst sanal düğüm
        # N*N+1: alt sanal düğüm
        self.uf = UnionFind(n * n + 2)
        self.top_virtual = n * n
        self.bottom_virtual = n * n + 1
    
    def _cell_id(self, row, col):
        return row * self.n + col
    
    def open(self, row, col):
        """Hücreyi aç"""
        if self.grid[row][col]:
            return  # Zaten açık
        
        self.grid[row][col] = True
        self.open_count += 1
        cell = self._cell_id(row, col)
        
        # Üst satırsa, üst sanal düğüme bağla
        if row == 0:
            self.uf.union(cell, self.top_virtual)
        
        # Alt satırsa, alt sanal düğüme bağla
        if row == self.n - 1:
            self.uf.union(cell, self.bottom_virtual)
        
        # Komşularla birleştir
        for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
            nr, nc = row + dr, col + dc
            if 0 <= nr < self.n and 0 <= nc < self.n and self.grid[nr][nc]:
                self.uf.union(cell, self._cell_id(nr, nc))
    
    def is_open(self, row, col):
        return self.grid[row][col]
    
    def percolates(self):
        """Sistem percolate ediyor mu?"""
        return self.uf.connected(self.top_virtual, self.bottom_virtual)
    
    def display(self):
        """Grid'i göster"""
        symbols = {False: '█', True: '░'}
        for row in self.grid:
            print("  " + "".join(symbols[c] for c in row))


def percolation_threshold(n, trials=100):
    """
    Monte Carlo simülasyonu ile percolation eşiğini tahmin et.
    
    Eşik: Sistemin percolate etmesi için gereken minimum açık hücre oranı.
    Teorik değer ≈ 0.593 (N→∞ için)
    """
    thresholds = []
    
    for _ in range(trials):
        perc = Percolation(n)
        cells = [(r, c) for r in range(n) for c in range(n)]
        random.shuffle(cells)
        
        for row, col in cells:
            perc.open(row, col)
            if perc.percolates():
                threshold = perc.open_count / (n * n)
                thresholds.append(threshold)
                break
    
    return sum(thresholds) / len(thresholds)


# Demo
print("=" * 50)
print("PERCOLATION SİMÜLASYONU")
print("=" * 50)

# Küçük örnek
print("\nKüçük örnek (5×5):")
perc = Percolation(5)

# Bazı hücreleri aç
opens = [(0,1), (1,1), (2,1), (2,2), (3,2), (4,2)]
for r, c in opens:
    perc.open(r, c)
    print(f"\nopen({r}, {c}):")
    perc.display()
    print(f"  Percolates: {perc.percolates()}")

# Monte Carlo simülasyonu
print("\n" + "─" * 50)
print("Monte Carlo Simülasyonu")
print("─" * 50)

random.seed(42)
for n in [10, 20]:
    threshold = percolation_threshold(n, trials=50)
    print(f"N={n}: Percolation eşiği ≈ {threshold:.3f}")

print("\nTeorik eşik (N→∞): ≈ 0.593")
Çıktı bekleniyor...

📝 Union-Find Bölüm Özeti

✅ Öğrendiklerimiz

  1. Problem: Dinamik bağlantı - elemanları birleştir ve sorgu yap
  2. 4 Algoritma: Quick-Find → Quick-Union → Weighted QU → WQU+PC
  3. Final Karmaşıklık: O(α(N)) ≈ O(1) amortize
  4. Uygulamalar: MST, sosyal ağlar, görüntü işleme, oyunlar

Sonraki Bölüm: Graf algoritmaları! Union-Find'ı Kruskal MST'de kullanacağız.