🎯 6.2: İkili Arama (Binary Search)

Böl ve Fethet ile Logaritmik Hız

📌 Bu Bölümde Öğrenecekleriniz

✂️

Böl ve Fethet

Divide & Conquer

📐

O(log n)

Logaritmik hız

🔄

Recursive vs Iterative

İki yaklaşım

📦

bisect Modülü

Python hazır fonksiyon

🤔 Binary Search Nedir?

İkili Arama (Binary Search), sıralı bir dizide aranan elemanı bulmak için diziyi sürekli ikiye bölen çok verimli bir algoritmadır.

💡 Gerçek Hayat Analojisi: Sözlükte Kelime Aramak

Bir sözlükte "kalem" kelimesini arıyorsun. Ne yaparsın?

  1. Sözlüğü ortadan aç - "M" harfleri çıktı
  2. "K" harfi "M"den önce → Sol yarıya bak
  3. Sol yarının ortasını aç - "G" harfleri çıktı
  4. "K" harfi "G"den sonra → Sağ yarıya bak
  5. Birkaç adımda "kalem" kelimesini buldun! 🎉

Baştan başlayıp tek tek sayfa çevirmek yerine, her seferinde yarısını eleyerek çok daha hızlı buldun!

🔑 Temel Fikir: Böl ve Fethet (Divide & Conquer)

Her adımda arama alanı yarıya iner!

mid = (low + high) // 2

1.000.000 elemanlı dizide sadece ~20 karşılaştırma yeterli!

⚠️ ÖNEMLİ: Ön Koşul

Binary Search sadece SIRALI dizilerde çalışır!

Dizi sıralı değilse önce sıralamanız gerekir (O(n log n) maliyet).

🎬 Algoritma Adımları

  1. Başlangıç: low = 0, high = n-1 (tüm dizi)
  2. Orta noktayı bul: mid = (low + high) // 2
  3. Karşılaştır:
    • arr[mid] == target → BULUNDU! mid döndür
    • arr[mid] > target → Sağ yarıyı ele (high = mid - 1)
    • arr[mid] < target → Sol yarıyı ele (low = mid + 1)
  4. Tekrarla: low ≤ high olduğu sürece 2-3 adımlarını tekrarla
  5. Bulunamadı: low > high olunca -1 döndür

👁️ Görsel Açıklama: Adım Adım İzleyelim

Aşağıdaki sıralı dizide 45 sayısını arayalım:

dizi = [11, 22, 33, 45, 55, 66, 77, 88, 99]
indeksler: [0, 1, 2, 3, 4, 5, 6, 7, 8]
Adım 1

low = 0, high = 8 → mid = (0+8) // 2 = 4

11L 22 33 45 55M 66 77 88 99H

arr[4] = 55 → 55 > 45 → Sola git! (high = mid - 1 = 3)

Adım 2

low = 0, high = 3 → mid = (0+3) // 2 = 1

11L 22M 33 45H 55 66 77 88 99

arr[1] = 22 → 22 < 45 → Sağa git! (low = mid + 1 = 2)

Adım 3

low = 2, high = 3 → mid = (2+3) // 2 = 2

11 22 33L,M 45H 55 66 77 88 99

arr[2] = 33 → 33 < 45 → Sağa git! (low = mid + 1 = 3)

Adım 4

low = 3, high = 3 → mid = (3+3) // 2 = 3

11 22 33 45L,M,H 55 66 77 88 99

arr[3] = 45 → 45 == 45 → ✅ BULUNDU! return 3

Sonuç: 9 elemanlı dizide sadece 4 adımda bulduk!
(Lineer arama en kötü 9 adım alırdı)
L = low (sol sınır)
M = mid (orta nokta)
H = high (sağ sınır)

🎮 İnteraktif Binary Search Simülasyonu

Sıralı Dizi (15 eleman): [5, 11, 15, 22, 28, 34, 41, 45, 52, 58, 64, 71, 78, 85, 90]

🎯 Bir sayı girin ve "Ara" butonuna tıklayın...

📊 Karmaşıklık Analizi

🐢 Lineer Arama

O(n)

1.000.000 eleman = 1.000.000 karşılaştırma

🚀 Binary Search

O(log n)

1.000.000 eleman = ~20 karşılaştırma

📐 Logaritma Nedir? (Basit Açıklama)

log₂(n) şu soruyu cevaplar: "n sayısını kaç kez 2'ye bölersem 1 elde ederim?"

log₂(8)
8 → 4 → 2 → 1
= 3
log₂(16)
16 → 8 → 4 → 2 → 1
= 4
log₂(1024)
1024'ü 10 kez böl
= 10
log₂(1M)
1 Milyon!
≈ 20

Sihir burada: Veri 2 katına çıkınca, adım sayısı sadece 1 artıyor! Bu yüzden Binary Search büyük verilerde muazzam hızlı.

Neden log₂(n)?

Her adımda arama alanı yarıya iniyor:

Adım Kalan Eleman Formül
0nn / 2⁰
1n/2n / 2¹
2n/4n / 2²
kn/2ᵏ = 1k = log₂(n)

💻 Python Implementasyonları

1. Iteratif (Döngü ile)

Python Kodu
def binary_search_iterative(arr, target):
    """
    İkili Arama - Iteratif Yöntem

    Args:
        arr: SIRALI dizi
        target: Aranan değer

    Returns:
        Bulunursa indeks, bulunamazsa -1
    """
    low = 0
    high = len(arr) - 1
    step = 0

    while low <= high:
        step += 1
        mid = (low + high) // 2

        print(f"Adım {step}: low={low}, high={high}, mid={mid}")
        print(f"         arr[{mid}] = {arr[mid]}")

        if arr[mid] == target:
            print(f"         ✅ BULUNDU!")
            return mid
        elif arr[mid] < target:
            print(f"         {arr[mid]} < {target} → Sağa git")
            low = mid + 1
        else:
            print(f"         {arr[mid]} > {target} → Sola git")
            high = mid - 1

    print("❌ Bulunamadı!")
    return -1

# Test
dizi = [11, 12, 22, 25, 34, 45, 64, 78, 90]
print(f"Sıralı Dizi: {dizi}")
print(f"Aranan: 45\n")

sonuc = binary_search_iterative(dizi, 45)
print(f"\nSonuç: İndeks = {sonuc}")
Çıktı bekleniyor...

2. Recursive (Özyinelemeli)

Python Kodu
def binary_search_recursive(arr, target, low, high, step=1):
    """
    İkili Arama - Recursive Yöntem
    """
    if low > high:
        return -1

    mid = (low + high) // 2
    print(f"Adım {step}: arr[{mid}] = {arr[mid]} {'✅' if arr[mid] == target else ''}")

    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search_recursive(arr, target, mid + 1, high, step + 1)
    else:
        return binary_search_recursive(arr, target, low, mid - 1, step + 1)

# Test
dizi = [11, 12, 22, 25, 34, 45, 64, 78, 90]
print(f"Sıralı Dizi: {dizi}")
print(f"Aranan: 22\n")

sonuc = binary_search_recursive(dizi, 22, 0, len(dizi) - 1)
print(f"\nSonuç: İndeks = {sonuc}")

print("\n" + "="*40)
print("Recursive vs Iterative:")
print("- Recursive: Daha okunaklı, ama call stack kullanır")
print("- Iterative: Daha verimli (O(1) ek alan)")
Çıktı bekleniyor...

3. Python bisect Modülü

📦 bisect Modülü Nedir?

Python'un bisect modülü, binary search'ü sizin için yapan hazır bir kütüphanedir. "Bisect" kelimesi İngilizce'de "ikiye bölmek" anlamına gelir.

bisect_left(arr, x)

x'in eklenmesi gereken sol pozisyonu bulur. Aynı değerden varsa soluna ekler.

bisect_right(arr, x)

x'in eklenmesi gereken sağ pozisyonu bulur. Aynı değerden varsa sağına ekler.

insort(arr, x)

x'i sıralı diziye doğru pozisyona ekler. Hem bulur hem ekler!

Neden kullanmalı? Kendi binary search'ünüzü yazmak yerine, test edilmiş ve optimize edilmiş bu modülü kullanmak daha güvenlidir.

Python Kodu
# ==========================================
# PYTHON BISECT MODÜLÜ - HAZIR BINARY SEARCH
# ==========================================

import bisect

dizi = [11, 12, 22, 25, 34, 45, 64, 78, 90]
print(f"📋 Sıralı Dizi: {dizi}")
print("=" * 50)

# --------- bisect_left ---------
# x değerinin eklenmesi gereken EN SOL pozisyonu bulur
# Eğer x zaten varsa, mevcut x'lerin SOLUNA ekler
print("\n🔍 bisect_left örnekleri:")

pos = bisect.bisect_left(dizi, 45)
print(f"  bisect_left(45) = {pos}")
print(f"  → 45 dizide {'VAR' if pos < len(dizi) and dizi[pos] == 45 else 'YOK'}!")

# Olmayan bir değer için
pos2 = bisect.bisect_left(dizi, 30)
print(f"  bisect_left(30) = {pos2}")
print(f"  → 30 olsaydı indeks {pos2}'e eklenmeli (25 ile 34 arasına)")

# --------- bisect_right ---------
# x değerinin eklenmesi gereken EN SAĞ pozisyonu bulur
print("\n🔍 bisect_right örneği:")
tekrarli = [1, 2, 3, 3, 3, 3, 4, 5]
print(f"  Dizi: {tekrarli}")
print(f"  bisect_left(3)  = {bisect.bisect_left(tekrarli, 3)} (ilk 3'ün soluna)")
print(f"  bisect_right(3) = {bisect.bisect_right(tekrarli, 3)} (son 3'ün sağına)")

# --------- insort ---------
# Elemanı sıralı şekilde ekler (bul + ekle tek adımda)
print("\n🔍 insort örneği:")
print(f"  Orijinal: {dizi}")
bisect.insort(dizi, 50)
print(f"  50 eklendi: {dizi}")
print(f"  → 50, sırayı bozmadan doğru yere yerleşti!")
Çıktı bekleniyor...

🧩 Yaygın Binary Search Varyasyonları

İlk ve Son Eşleşmeyi Bulma

Python Kodu
def find_first(arr, target):
    """Target'ın İLK göründüğü indeksi bulur"""
    low, high = 0, len(arr) - 1
    result = -1

    while low <= high:
        mid = (low + high) // 2
        if arr[mid] == target:
            result = mid       # Bulduk ama daha solda olabilir
            high = mid - 1     # Sola bakmaya devam
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1

    return result

def find_last(arr, target):
    """Target'ın SON göründüğü indeksi bulur"""
    low, high = 0, len(arr) - 1
    result = -1

    while low <= high:
        mid = (low + high) // 2
        if arr[mid] == target:
            result = mid       # Bulduk ama daha sağda olabilir
            low = mid + 1      # Sağa bakmaya devam
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1

    return result

# Test
dizi = [1, 2, 3, 3, 3, 3, 4, 5, 6]
print(f"Dizi: {dizi}")
print(f"Aranan: 3")

ilk = find_first(dizi, 3)
son = find_last(dizi, 3)

print(f"\nİlk 3'ün indeksi: {ilk}")
print(f"Son 3'ün indeksi: {son}")
print(f"3'ün sayısı: {son - ilk + 1}")
Çıktı bekleniyor...

🌍 Gerçek Hayat Kullanım Alanları

🌟 Binary Search Neden Bu Kadar Önemli?

Binary Search, yazılım dünyasının en temel ve en güçlü algoritmalarından biridir. Sadece "dizide eleman bulma" değil, çok daha geniş bir problem sınıfında kullanılır.

Bir algoritma, sorduğu soruya göre arama alanını yarıya indirebiliyorsa - orada binary search mantığı vardır!

🏗️ Diğer Algoritmalarda Ön Adım Olarak Binary Search

Birçok ileri algoritma, binary search'ü bir "alt rutin" olarak kullanır:

🌳 BST (Binary Search Tree)

Ağaç yapısında arama, ekleme, silme - hepsi binary search mantığıyla O(log n).

📊 Merge Sort & Quick Sort

"Böl ve fethet" mantığı - sıralama algoritmalarının temeli.

💼 LIS (Longest Increasing Subsequence)

O(n²)'dan O(n log n)'e düşürmek için binary search kullanılır.

🔢 Lower/Upper Bound

Sıralı dizide ekleme pozisyonu, aralık sorguları - C++ STL'de std::lower_bound.

🎯 Binary Search on Answer

Cevap üzerinde arama! "Minimum maksimum", "maksimum minimum" problemlerinde.

🗄️ B-Tree ve Veritabanları

MySQL, PostgreSQL indeksleri - milyarlarca kayıtta milisaniye erişim.

📈 Performans Farkı Ne Kadar Büyük?

Veri Boyutu (n) Linear O(n) Binary O(log n) Hız Farkı
1,0001,000 adım10 adım100x hızlı
1,000,0001,000,000 adım20 adım50,000x hızlı
1,000,000,0001 milyar adım 😱30 adım33,000,000x hızlı

💼 Mülakatlarda Kritik!

FAANG ve büyük teknoloji şirketlerinin mülakatlarında binary search çok sık sorulur. LeetCode'daki problemlerin yaklaşık %15-20'si doğrudan veya dolaylı olarak binary search kullanır. "Binary Search on Answer" paterni özellikle popülerdir!

⚠️ Dikkat Edilmesi Gerekenler

🔢 Integer Overflow Problemi Nedir?

Bilgisayarlarda sayılar sınırlı bit ile saklanır. Örneğin 32-bit integer en fazla 2,147,483,647 (yaklaşık 2 milyar) değerini tutabilir. Bu sınırı aşarsan ne olur?

Örnek Senaryo:
  • low = 1,500,000,000 (1.5 milyar)
  • high = 2,000,000,000 (2 milyar)
  • low + high = 3,500,000,000 ❌ 32-bit sınırını aştı!

Sonuç: Sayı "taşar" ve negatif veya beklenmedik bir değer olur → Program çöker veya yanlış sonuç verir!

❌ Hatalı Yöntem:
mid = (low + high) / 2

low + high toplamı taşabilir!

✅ Doğru Yöntem:
mid = low + (high - low) // 2

high - low asla taşmaz!

🐍 Python'da Neden Sorun Yok?

Python otomatik olarak büyük sayıları yönetir (arbitrary precision integers). Sınır yok! Ama C, C++, Java gibi dillerde 32/64-bit sınırı vardır, bu yüzden iş görüşmelerinde bu konuyu bilmek önemli.

📋 Bölüm Özeti

✂️ Algoritma

Böl ve Fethet - Her adımda yarıya böl

⏱️ Zaman

O(log n) - Logaritmik

💾 Alan

O(1) Iteratif / O(log n) Recursive

⚠️ Ön Koşul

Dizi SIRALI olmalı!

Pratik yapmak ister misin?
👉 Arama Algoritmaları Örnek Sorular sayfasına göz at!