SEN2211 · 12. hafta · Searching Algorithms

Arama algoritmaları: doğrusal, ikili, interpolasyon

Doğrusal arama, ikili aramanın döngülü ve özyinelemeli halleri, karşılaştırma sayıları ve interpolasyon aramasının fikri. Sınavda low/mid/high iz tablosu ve en kötü durumda karşılaştırma sayısı sorulur.

linear searchsequential searchbinary searchlowhighmidprobecomparison countsorted arrayinterpolation searchworst caseaverage caseinteger overflow

Bir öğrenci numarasını listede bulmak, bir kelimeyi sözlükte aramak, bir ürünü fiyat listesinde bulmak: hepsi arama problemidir. İki temel strateji var. Doğrusal arama (linear search) baştan sona tek tek bakar, her dizide çalışır ama yavaştır. İkili arama (binary search) sadece sıralı dizide çalışır ama inanılmaz hızlıdır: bir milyon elemanda en fazla 20 adım. Sınavda ikili aramanın low/mid/high iz tablosu, karşılaştırma sayısı ve kodun küçük hataları sorulur.

Altın kurallar:

  1. İkili arama sadece sıralı dizide çalışır.
  2. Döngü koşulu low <= high. Tek elemanlı aralık da kontrol edilir.
  3. Sınırı kaydırırken mid + 1 ve mid - 1.
  4. En kötü durumda ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1 karşılaştırma.
static int linearSearch(int[] a, int x) {
    for (int i = 0; i < a.length; i++)
        if (a[i] == x) return i;
    return -1;
}
DurumKarşılaştırmaNe zaman
En iyi1hedef ilk eleman
En kötünhedef son eleman ya da yok
Ortalama (başarılı)n+12\frac{n+1}{2}her konum eşit olasılıklı

Hepsi O(n). Avantajı: sıralama gerektirmez, bağlı listede de çalışır. Dizi sıralı olsa bile doğrusal arama bunu kullanmaz; olmayan bir değer için hep sonuna kadar gider (sıralı listede erken çıkış ekleyebilirsin, 3. hafta).

Sözlükte kelime ararken ortadan açarsın. Aradığın kelime o sayfadan önce geliyorsa sağ yarıyı tamamen atarsın. Her adımda arama alanı yarıya iner.

static int binarySearch(int[] a, int x) {
    int low = 0, high = a.length - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (a[mid] == x) return mid;
        else if (a[mid] < x) low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}

İz tablosu

Dizi (indeksler üstte):

indeks:  0  1   2   3   4   5   6   7   8   9
değer:   3  8  12  17  21  25  30  34  41  47

47’yi arama (en sağdaki eleman, en kötü durumlardan biri):

low=0 high=9 mid=4 a[mid]=21
low=5 high=9 mid=7 a[mid]=34
low=8 high=9 mid=8 a[mid]=41
low=9 high=9 mid=9 a[mid]=47
result 9 probes=4

5’i arama (dizide yok):

low=0 high=9 mid=4 a[mid]=21
low=0 high=3 mid=1 a[mid]=8
low=0 high=0 mid=0 a[mid]=3
stop: low=1 high=0
result -1 probes=3

Döngü low > high olunca biter. O anda low, aranan değerin sırayı bozmadan eklenebileceği konumdur (5, indeks 1’e girmeli). Java’nın hazır Arrays.binarySearch metodu bulamayınca -(low) - 1 döner: bu örnekte −2.

İPUCU · İz tablosu düzeni. Sınavda her satıra low | high | mid | a[mid] | karşılaştırma sonucu yaz. mid’i hesaplarken tam sayı bölmesini unutma: (0 + 9) / 2 = 4, 4.5 değil.

Kaç karşılaştırma?

Her probe aralığı yarıya indirir. n elemandan 1 elemana inmek için log⁡2n\log_2 n yarılama gerekir, bir de son elemanı kontrol etmek için 1. En kötü durum ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1:

n=10 worst probes=4 floor(log2 n)+1=4
n=100 worst probes=7 floor(log2 n)+1=7
n=1000 worst probes=10 floor(log2 n)+1=10
n=1000000 worst probes=20 floor(log2 n)+1=20
nDoğrusal (en kötü)İkili (en kötü)
10104
1000100010
1 000 0001 000 00020
10⁹10⁹30

Bedel: dizinin sıralı olması gerekir. Sıralama en iyi O(n log n) sürer (14. hafta). Tek bir arama için sıralamaya değmez; aynı dizide çok arama yapılacaksa değer.

Özyinelemeli hali

static int binarySearch(int[] a, int x, int low, int high) {
    if (low > high) return -1;
    int mid = low + (high - low) / 2;
    if (a[mid] == x) return mid;
    if (a[mid] < x) return binarySearch(a, x, mid + 1, high);
    return binarySearch(a, x, low, mid - 1);
}

Bağıntı T(n)=T(n/2)+1T(n) = T(n/2) + 1, çözümü O(log n) (11. hafta). Döngülü versiyon O(1), özyinelemeli versiyon O(log n) ek yığın belleği kullanır.

Üç klasik hata

1. while (low < high). Aralık tek elemana inince döngü o elemana bakmadan biter. {1, 3, 5} üzerinde:

find 1 -> -1  find 3 -> 1  find 5 -> -1  

2. low = mid ya da high = mid. low = 3, high = 4 iken mid = 3; low = mid yazarsan low hiç değişmez, sonsuz döngü.

3. (low + high) / 2 taşması.

int low = 1_500_000_000, high = 2_000_000_000;
System.out.println((low + high) / 2 + " vs " + (low + (high - low) / 2));
-397483648 vs 1750000000

Bu hata Java’nın kendi kütüphanesinde yıllarca kaldı. Çok büyük dizilerde ortaya çıkar; sınavda doğru formülü yazman beklenir.

TUZAK · Sırasız dizide ikili arama. Hata vermez, sessizce yanlış sonuç döndürür. {9, 2, 7, 4, 1, 8, 3} üzerinde 2 ve 8 şans eseri bulunur ama 7 bulunamaz (−1), çünkü algoritma 7’nin bulunduğu sol yarıyı ilk adımda atar.

Tekrarlı değerler

{2, 4, 4, 4, 7} üzerinde standart ikili arama 4 için indeks 2’yi döner, çünkü ilk mid orası. İlk konumu istiyorsan eşleşmede durma: sonucu kaydet ve high = mid - 1 ile solda aramaya devam et. Bu varyasyon indeks 1’i döner. Son konum için simetrik olarak low = mid + 1.

Telefon rehberinde “Yılmaz”ı ararken ortadan değil, sona yakın bir yerden açarsın. İnterpolasyon araması aynısını yapar: hedefin değerine bakarak konumunu tahmin eder.

pos=low+(x−a[low])⋅(high−low)a[high]−a[low]pos = low + \frac{(x - a[low]) \cdot (high - low)}{a[high] - a[low]}

Sonra ikili aramadaki gibi a[pos] ile karşılaştırıp low ya da high’ı kaydırır.

low=0 high=9 pos=6 a[pos]=70
6 probes=1
8 probes(skewed)=9
8 probes(binary)=3

İlk iki satır: {10, 20, ..., 100} dizisinde 70, ilk tahminde bulundu. Son iki satır: {1, 2, ..., 9, 1000} gibi çarpık bir dizide 9’u aramak interpolasyonla 9 probe, ikili aramayla 3 probe sürdü. 1000 değeri tahmini hep sola kaydırıyor.

AlgoritmaÖn koşulOrtalamaEn kötü
DoğrusalyokO(n)O(n)
İkilisıralıO(log n)O(log n)
İnterpolasyonsıralı + sayısal, düzgün dağılımO(log log n)O(n)

Sık yapılan hatalar

YanlışDoğrusu
İkili aramayı sırasız dizide kullanmakÖnce sırala ya da doğrusal ara
while (low < high)while (low <= high)
low = mid / high = midmid + 1 / mid - 1
mid = (low + high) / 2 her zaman güvenliBüyük değerlerde taşar; low + (high - low) / 2
İkili arama en kötü n/2 karşılaştırma⌊log⁡2n⌋+1\lfloor\log_2 n\rfloor + 1
Bağlı listede ikili arama O(log n)Ortaya O(1) erişim yok; liste için anlamsız
Tekrarlı dizide ilk konumu dönerHerhangi birini döner
İnterpolasyon her zaman ikiliden hızlıÇarpık veride O(n)‘e düşer

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

Linear search is run on {3, 8, 12, 17, 21, 25, 30, 34, 41, 47}. How many elements are compared when searching for 30? For 5?

Cevabı göster
7 and 10

30 indeks 6'da: a[0]..a[6], yedi karşılaştırma. 5 dizide yok: doğrusal arama dizinin sıralı olduğundan habersizdir ve on elemanın hepsine bakar.

Soru 2kolay

What are the best, worst and average number of comparisons for a successful linear search on n elements (each position equally likely)?

Cevabı göster
best 1, worst n, average (n+1)/2

Hedef başta: 1. Sonda: n. Ortalama 1+2+⋯+nn=n+12\frac{1 + 2 + \dots + n}{n} = \frac{n+1}{2}. n = 1000 için program 500.5 buldu. Big-O: O(n).

Soru 3orta

Binary search (mid = low + (high - low) / 2) looks for 34 in {3, 8, 12, 17, 21, 25, 30, 34, 41, 47}. List the values of mid and a[mid] in order.

Cevabı göster
mid 4 (21), mid 7 (34); found at index 7 after 2 probes

low = 0, high = 9 → mid = 4, a[4] = 21 < 34, low = 5. low = 5, high = 9 → mid = 7, a[7] = 34: bulundu. Doğrusal arama 8 karşılaştırma yapardı.

Soru 4orta

Same array, search for 5 (not present). Give each low, high, mid triple and the final values of low and high.

Cevabı göster
(0,9,4), (0,3,1), (0,0,0); stops with low = 1, high = 0; returns -1

21 > 5 → high = 3. mid 1: 8 > 5 → high = 0. mid 0: 3 < 5 → low = 1. low > high, döngü biter. Son low değeri (1), 5'in eklenmesi gereken yerdir. Java'nın Arrays.binarySearch bu yüzden −(low) − 1 = −2 döner.

Soru 5orta

What is the maximum number of probes (comparisons with a[mid]) binary search needs on a sorted array of 1000 elements? Of 1 000 000?

Cevabı göster
10 and 20

En kötü durum ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1. log⁡21000≈9.97\log_2 1000 \approx 9.97 → 10. log⁡2106≈19.93\log_2 10^6 \approx 19.93 → 20. Program her hedefi deneyerek aynı sayıları buldu. Doğrusal arama aynı dizilerde 1000 ve 1 000 000 karşılaştırma yapabilir.

Soru 6orta

Write an iterative static int binarySearch(int[] a, int x) that returns the index of x in the sorted array a, or -1.

Cevabı göster
low=0, high=n-1; while (low <= high) { mid; compare; move low or high }; return -1

static int binarySearch(int[] a, int x) {
    int low = 0, high = a.length - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (a[mid] == x) return mid;
        else if (a[mid] < x) low = mid + 1;   // sağ yarı
        else high = mid - 1;                  // sol yarı
    }
    return -1;
}
Üç kritik ayrıntı: koşul <= (tek elemanlı aralık da kontrol edilmeli), mid + 1 / mid - 1 (yoksa sonsuz döngü), ve taşmayan mid formülü.

Soru 7zor

Write a recursive static int binarySearch(int[] a, int x, int low, int high).

Cevabı göster
Base case low > high → -1; otherwise compare a[mid] and recurse on one half.

static int binarySearch(int[] a, int x, int low, int high) {
    if (low > high) return -1;                       // boş aralık
    int mid = low + (high - low) / 2;
    if (a[mid] == x) return mid;
    if (a[mid] < x) return binarySearch(a, x, mid + 1, high);
    return binarySearch(a, x, low, mid - 1);
}
İlk çağrı binarySearch(a, x, 0, a.length - 1). Bağıntı T(n) = T(n/2) + 1 → O(log n) süre, O(log n) yığın. Yukarıdaki dizide 12 için 3 probe, 50 için 4 probe.

Soru 8zor

This version uses while (low < high). On the array {1, 3, 5}, which searches fail even though the value is present?

int low = 0, high = a.length - 1;
while (low < high) {
    int mid = (low + high) / 2;
    if (a[mid] == x) return mid;
    else if (a[mid] < x) low = mid + 1;
    else high = mid - 1;
}
return -1;
Cevabı göster
1 and 5 (only 3 is found)

3 ilk mid'de bulunur. 1 için: mid 1 (3 > 1) → high = 0; low = high = 0 olunca 0 < 0 yanlış, döngü a[0]'a bakmadan biter: −1. 5 için aynı şekilde low = high = 2'de durur. Tek elemanlı aralık hiç kontrol edilmiyor. Doğrusu low <= high.

Soru 9orta

Why is mid = low + (high - low) / 2 preferred over mid = (low + high) / 2? What does the second give for low = 1 500 000 000, high = 2 000 000 000?

Cevabı göster
low + high overflows int; it gives -397483648

İki büyük int'in toplamı int sınırını (yaklaşık 2.147 milyar) aşar ve negatife sarar: (1.5e9 + 2e9) / 2 = −397 483 648. Diziye negatif indeksle erişim çöker. low + (high - low) / 2 asla taşmaz: 1 750 000 000.

Soru 10orta

Interpolation search estimates pos = low + (x - a[low]) * (high - low) / (a[high] - a[low]). For {10, 20, ..., 100} (10 elements) and x = 70, what is the first pos, and how many probes are needed?

Cevabı göster
pos = 6, 1 probe

pos = 0 + (70 − 10) · 9 / (100 − 10) = 540 / 90 = 6. a[6] = 70: ilk denemede bulundu. Değerler düzgün dağılmışsa ortalama O(log log n). Ama {1, 2, ..., 9, 1000} gibi çarpık dağılımda 9'u bulmak 9 probe sürdü (ikili arama 3): en kötü durum O(n).

Soru 11zor

Binary search is (incorrectly) run on the unsorted array {9, 2, 7, 4, 1, 8, 3} looking for 7. What does it return?

Cevabı göster
-1 (7 is at index 2 but is never examined)

mid 3: a[3] = 4 < 7 → sağa git (low = 4). mid 5: 8 > 7 → high = 4. mid 4: 1 < 7 → low = 5. Biter: −1. İkili arama 'sol tarafta hepsi küçük' varsayımına dayanır; sırasız dizide bazı aramalar şans eseri tutsa da (2 ve 8 bulundu) sonuç güvenilmezdir.

Soru 12zor

The sorted array is {2, 4, 4, 4, 7}. Which index does the standard binary search return for 4? How would you change it to always return the first occurrence?

Cevabı göster
2; on a match record ans = mid and continue with high = mid - 1

İlk mid = 2 ve a[2] = 4: hemen döner. Standart ikili arama tekrarlı değerlerde herhangi bir eşleşmeyi verir. İlk konum için eşleşmede durma, solda aramaya devam et:

int low = 0, high = a.length - 1, ans = -1;
while (low <= high) {
    int mid = low + (high - low) / 2;
    if (a[mid] == x) { ans = mid; high = mid - 1; }
    else if (a[mid] < x) low = mid + 1;
    else high = mid - 1;
}
return ans;
Bu dizide 1 döner. Hâlâ O(log n).

Doğrulama: Tüm arama metotları verify/SEN2211/W12.java içinde JDK ile çalıştırıldı (java W12.java). İz tabloları programın her adımda yazdırdığı low/high/mid satırlarından, karşılaştırma (probe) sayıları sayaçtan alındı. En kötü durum sayısı n = 10, 100, 1000 için olası her hedef denenerek (10⁶ için uçlardaki 4000 hedef denenerek) bulundu ve ⌊log₂ n⌋ + 1 ile karşılaştırıldı.

Deftere not kartı

DOĞRUSAL (linear): sırasız dizide de çalışır; best 1, worst n, ort (n+1)/2 → O(n) İKİLİ (binary): SADECE SIRALI dizide; her adımda aralık yarıya → O(log n) low = 0, high = n-1; while (low <= high) (<= ŞART) mid = low + (high - low) / 2 ((low+high)/2 int taşabilir) a[mid] < x → low = mid + 1 a[mid] > x → high = mid - 1 (±1 ŞART, yoksa sonsuz döngü) Bulunamazsa döngü low = high + 1 ile biter; low = eklenecek yer En kötü probe: floor(log2 n) + 1 n=10→4 100→7 1000→10 10⁶→20 Özyinelemeli: taban low > high → -1; T(n)=T(n/2)+1, yığın O(log n) Tekrarlarda herhangi birini bulur; ilk konum için eşleşince high = mid - 1 ile devam İNTERPOLASYON: pos = low + (x - a[low])*(high - low)/(a[high] - a[low]) düzgün dağılım ort O(log log n), çarpık dağılım worst O(n) Java: Arrays.binarySearch(a, x) yoksa -(eklenecek yer) - 1