SEN1002 · 13. hafta · Searching and Sorting (Selection, Insertion, Merge Sort)

Arama ve sıralama: linear, binary, selection, insertion, merge sort

İki arama ve üç sıralama algoritması, her geçişten sonra dizinin hali ve Big-O karşılaştırması. Sınavda 'pass 2'den sonra dizi ne olur' soruları gelir.

linear searchbinary searchselection sortinsertion sortmerge sortpassswapdivide and conquerBig O notationbest caseworst casecomparisonArrays.sortArrays.binarySearch

Arama (searching) ve sıralama (sorting), algoritma analizinin ilk durağı. Bu hafta iki arama ve üç sıralama algoritmasını adım adım izliyorsun. Sınavda tipik soru: “bu diziye selection sort uygulanınca ikinci geçişten sonra dizi ne olur”, “binary search hangi indekslere bakar”, ve “Big-O’su nedir, neden”. Kodları ezberlemek yerine her algoritmanın tek cümlelik fikrini bil; kod o fikirden çıkar.

Altın kurallar:

  1. Binary search sadece sıralı dizide çalışır. Her adımda aralığı yarıya böler.
  2. Selection sort: kalanın en küçüğünü bul, başa koy.
  3. Insertion sort: sıradaki elemanı soldaki sıralı kısımda yerine kaydır.
  4. Merge sort: ikiye böl, iki yarıyı sırala, birleştir.
  5. Big-O: linear O(n)O(n), binary O(log⁡n)O(\log n), selection ve insertion O(n2)O(n^2), merge O(nlog⁡n)O(n \log n).

Arama

Diziyi baştan sona tek tek gez, aranan değeri görünce indeksini döndür. Dizi sıralı olmak zorunda değil.

static int linearSearch(int[] a, int key) {
    for (int i = 0; i < a.length; i++)
        if (a[i] == key) return i;
    return -1;
}
int[] data = {42, 7, 19, 3, 25, 7};

Karşılaştırma sayılarını da sayarak üç arama:

1 comps=2
4 comps=5
-1 comps=6

(7 aranınca ilk 7 bulunur, indeks 1; 25 için 5 karşılaştırma; 99 yok, dizinin hepsine bakıldı.)

Dizi sıralıysa ortadaki elemana bak. Aranan daha büyükse sol yarıyı, daha küçükse sağ yarıyı tamamen eleyebilirsin. Geçen hafta özyinelemeli yazdık; döngülü sürümü:

static int binarySearch(int[] a, int key) {
    int low = 0, high = a.length - 1;
    while (low <= high) {
        int mid = (low + high) / 2;
        if (a[mid] == key) return mid;
        if (a[mid] < key) low = mid + 1;    // sağ yarı
        else high = mid - 1;                // sol yarı
    }
    return -1;
}
int[] sorted = {2, 5, 9, 14, 20, 27, 33, 41, 48, 56, 63};
binarySearch(sorted, 41);
binarySearch(sorted, 4);
  low=0 high=10 mid=5 a[mid]=27
  low=6 high=10 mid=8 a[mid]=48
  low=6 high=7 mid=6 a[mid]=33
  low=7 high=7 mid=7 a[mid]=41
index 7 comps=4
  low=0 high=10 mid=5 a[mid]=27
  low=0 high=4 mid=2 a[mid]=9
  low=0 high=1 mid=0 a[mid]=2
  low=1 high=1 mid=1 a[mid]=5
index -1 comps=4

4 aranırken son adımda low = 1, high = 1, a[1] = 5 > 4 olduğu için high = 0 olur; low > high ve döngü biter: −1.

TUZAK · low < high. Koşulu < yazarsan aralık tek elemana indiğinde o eleman hiç kontrol edilmez. {2, 5, 9} dizisinde bu hatalı sürüm 5’i buldu ama 9 ve 2 için −1 döndü.

Java’nın hazır sürümü: Arrays.binarySearch(sorted, 27) → 5. O da dizinin sıralı olmasını bekler; sıralı değilse sonuç anlamsızdır.

Sıralama

Üç algoritmayı aynı dizi üzerinde izleyeceğiz: {29, 10, 14, 37, 13}.

Selection sort

Fikir: dizinin sıralanmamış kısmındaki en küçük elemanı bul, o kısmın başıyla yer değiştir (swap). Her geçişte (pass) baştan bir eleman daha kesin yerine oturur.

static void selectionSort(int[] a) {
    for (int i = 0; i < a.length - 1; i++) {
        int min = i;
        for (int j = i + 1; j < a.length; j++)
            if (a[j] < a[min]) min = j;
        int tmp = a[i]; a[i] = a[min]; a[min] = tmp;   // swap
    }
}

Her geçişten sonra dizi:

  pass 1: [10, 29, 14, 37, 13]
  pass 2: [10, 13, 14, 37, 29]
  pass 3: [10, 13, 14, 37, 29]
  pass 4: [10, 13, 14, 29, 37]
comps=10
pass 1: [29 10 14 37 13]  en küçük 10 (indeks 1) ↔ indeks 0   → [10 | 29 14 37 13]
pass 2: [10 | 29 14 37 13] en küçük 13 (indeks 4) ↔ indeks 1  → [10 13 | 14 37 29]
pass 3: en küçük 14 zaten indeks 2'de                         → [10 13 14 | 37 29]
pass 4: en küçük 29 (indeks 4) ↔ indeks 3                     → [10 13 14 29 | 37]

Karşılaştırma sayısı her girdide aynı: (n−1)+(n−2)+⋯+1=n(n−1)2(n-1) + (n-2) + \dots + 1 = \frac{n(n-1)}{2}. n = 5 için 10. Bu yüzden selection sort her durumda O(n2)O(n^2).

Insertion sort

Fikir: kartları elinde sıralar gibi. Soldaki kısım her zaman sıralı. Sıradaki elemanı (key) al, kendisinden büyük olanları birer sağa kaydır, açılan boşluğa key’i koy.

static void insertionSort(int[] a) {
    for (int i = 1; i < a.length; i++) {
        int key = a[i];
        int j = i - 1;
        while (j >= 0 && a[j] > key) {
            a[j + 1] = a[j];     // sağa kaydır
            j--;
        }
        a[j + 1] = key;
    }
}
  pass 1: [10, 29, 14, 37, 13]
  pass 2: [10, 14, 29, 37, 13]
  pass 3: [10, 14, 29, 37, 13]
  pass 4: [10, 13, 14, 29, 37]
comps=8

İki algoritmanın geçişlerini karşılaştır: selection sort’ta baştaki elemanlar kesin yerindedir (pass 2’den sonra 10 ve 13 bir daha değişmez). Insertion sort’ta baştaki kısım sıralıdır ama kesin değildir: pass 3’te [10, 14, 29, 37] sıralı ama 13 daha sonra araya girdi.

Insertion sort’un karşılaştırma sayısı girdiye bağlı:

GirdiInsertionSelection
{1, 2, 3, 4, 5} (sıralı)410
{5, 4, 3, 2, 1} (ters)1010
{29, 10, 14, 37, 13}810

Sıralı girdide insertion sort her eleman için bir karşılaştırma yapıp durur: en iyi durum O(n)O(n). Ters sıralıda her eleman başa kadar kayar: en kötü durum O(n2)O(n^2).

Merge sort

Fikir: böl ve yönet (divide and conquer). Diziyi ikiye böl, iki yarıyı özyinelemeli olarak sırala, sonra iki sıralı yarıyı tek sıralı diziye birleştir (merge). Tek elemanlı dizi zaten sıralıdır: base case.

static void mergeSort(int[] a, int low, int high) {
    if (low >= high) return;                 // 0 ya da 1 eleman
    int mid = (low + high) / 2;
    mergeSort(a, low, mid);                  // sol yarı
    mergeSort(a, mid + 1, high);             // sağ yarı
    merge(a, low, mid, high);                // birleştir
}
static void merge(int[] a, int low, int mid, int high) {
    int[] tmp = new int[high - low + 1];
    int i = low, j = mid + 1, k = 0;
    while (i <= mid && j <= high)
        tmp[k++] = (a[i] <= a[j]) ? a[i++] : a[j++];   // küçük olanı al
    while (i <= mid) tmp[k++] = a[i++];                // kalanlar
    while (j <= high) tmp[k++] = a[j++];
    System.arraycopy(tmp, 0, a, low, tmp.length);
}

{38, 27, 43, 3, 9, 82, 10} için her merge’den sonra o aralığın hali:

  merge [0..1]: [27, 38]
  merge [2..3]: [3, 43]
  merge [0..3]: [3, 27, 38, 43]
  merge [4..5]: [9, 82]
  merge [4..6]: [9, 10, 82]
  merge [0..6]: [3, 9, 10, 27, 38, 43, 82]
[3, 9, 10, 27, 38, 43, 82] comps=14
                 [38 27 43 3 9 82 10]
            [38 27 43 3]          [9 82 10]
         [38 27]   [43 3]       [9 82]   [10]
        [38] [27] [43] [3]     [9] [82]
         └─┬─┘     └─┬─┘        └─┬─┘
        [27 38]   [3 43]       [9 82]
           └────┬────┘            └──┬──┘
        [3 27 38 43]            [9 10 82]
                └──────────┬──────────┘
              [3 9 10 27 38 43 82]

Bölme log⁡2n\log_2 n seviye sürer, her seviyede toplam n eleman birleştirilir: O(nlog⁡n)O(n \log n), girdiden bağımsız. Bedeli: geçici dizi için ek O(n)O(n) bellek.

Big-O karşılaştırması

AlgoritmaEn iyiOrtalamaEn kötüEk bellekNot
Linear searchO(1)O(1)O(n)O(n)O(n)O(n)yokSırasız dizide de çalışır
Binary searchO(1)O(1)O(log⁡n)O(\log n)O(log⁡n)O(\log n)yokSıralı dizi şart
Selection sortO(n2)O(n^2)O(n2)O(n^2)O(n2)O(n^2)yokEn fazla n − 1 swap
Insertion sortO(n)O(n)O(n2)O(n^2)O(n2)O(n^2)yokNeredeyse sıralı veride hızlı, stable
Merge sortO(nlog⁡n)O(n \log n)O(nlog⁡n)O(n \log n)O(nlog⁡n)O(n \log n)O(n)O(n)Büyük veride tercih

Teorinin pratikteki karşılığı: ters sıralı dizilerde sayılan karşılaştırmalar.

nSelection sortMerge sort
10004995004932
2000199900010864
4000799800023728

n her iki katına çıktığında selection sort’un işi dört katına çıkıyor (n2n^2), merge sort’unki iki kattan biraz fazla (nlog⁡nn \log n).

İPUCU · gerçek hayatta. Kendi sıralamanı yazmazsın: Arrays.sort(dizi) ve Collections.sort(liste) hazır ve O(nlog⁡n)O(n \log n). Sınavda ise algoritmayı elle izlemen ve yazman istenir; hazır metot kabul edilmez.

Sık yapılan hatalar

Yanlış düşünceDoğrusu
Binary search her dizide çalışırSıralı dizi şart
while (low < high) yeterli<= olmalı, yoksa tek kalan eleman atlanır
Selection sort sıralı dizide hızlıdırKarşılaştırma sayısı her zaman n(n−1)/2
Insertion sort ile selection sort’un pass’leri aynıSelection baştan kesinleştirir; insertion baştaki kısmı sıralı tutar ama araya eleman girebilir
Merge sort önce sağ yarıyı birleştirirSol yarı tamamen biter, sonra sağ
Merge sort ek bellek istemezGeçici dizi: O(n)
O(n log n) ile O(n²) arasında küçük fark varn = 4000’de 23728’e karşı 7998000
n geçiş gerekirn − 1 geçiş yeter, son eleman kendiliğinden yerinde

Kendini test et

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

Soru 1orta

Show the array after each pass of selection sort (smallest to the front).

int[] a = {29, 10, 14, 37, 13};
Cevabı göster
[10,29,14,37,13] / [10,13,14,37,29] / [10,13,14,37,29] / [10,13,14,29,37]

Her geçişte sıralanmamış kısmın en küçüğü bulunur ve kısmın başıyla yer değiştirir. Pass 1: en küçük 10, 29 ile değişir. Pass 2: kalan [29,14,37,13] içinde en küçük 13, 29 ile değişir. Pass 3: kalan [14,37,29], en küçük 14 zaten yerinde (kendisiyle değişir, dizi aynı). Pass 4: [37,29] → 29 öne. n = 5 için 4 geçiş.

Soru 2orta

Show the array after each pass of insertion sort.

int[] a = {29, 10, 14, 37, 13};
Cevabı göster
[10,29,14,37,13] / [10,14,29,37,13] / [10,14,29,37,13] / [10,13,14,29,37]

Her geçişte bir sonraki eleman, soldaki sıralı kısımda doğru yerine kaydırılarak yerleşir. Pass 1: 10, 29'un önüne. Pass 2: 14, 29'un önüne (10'dan büyük). Pass 3: 37 zaten en büyük, değişmez. Pass 4: 13 üç elemanı sağa kaydırıp 10'dan sonraya girer.

Soru 3orta

Binary search for 41 in {2, 5, 9, 14, 20, 27, 33, 41, 48, 56, 63} with mid = (low + high) / 2. List the mid values checked.

Cevabı göster
5, 8, 6, 7 (bulundu, indeks 7)

(0,10) → mid 5, a[5] = 27 < 41, low = 6. (6,10) → mid 8, a[8] = 48 > 41, high = 7. (6,7) → mid 6, a[6] = 33 < 41, low = 7. (7,7) → mid 7, a[7] = 41: bulundu. 4 karşılaştırma; 11 elemanlı dizide en fazla 4 gerekir.

Soru 4kolay

How many comparisons does linear search make to find 25 in {42, 7, 19, 3, 25, 7}, and to search for 99?

Cevabı göster
5 ve 6

Linear search baştan tek tek bakar: 42, 7, 19, 3, 25 → 5. karşılaştırmada bulur (indeks 4). Olmayan bir değer için dizinin hepsine bakar: 6 karşılaştırma, −1 döner. 7 aransaydı ilk 7'yi (indeks 1) bulup dururdu: 2 karşılaştırma.

Soru 5kolay

What is the Big-O (worst case) of each algorithm? linear search, binary search, selection sort, insertion sort, merge sort

Cevabı göster
O(n), O(log n), O(n²), O(n²), O(n log n)

Linear her elemana bakabilir: O(n)O(n). Binary her adımda aralığı yarıya indirir: O(log⁡n)O(\log n). Selection ve insertion iç içe iki döngü: O(n2)O(n^2). Merge sort log⁡n\log n seviyede bölünür, her seviyede n eleman birleşir: O(nlog⁡n)O(n \log n).

Soru 6orta

Insertion sort makes 4 comparisons on {1, 2, 3, 4, 5} but 10 on {5, 4, 3, 2, 1}. Selection sort makes 10 on both. Why?

Cevabı göster
Insertion sort'un en iyi durumu O(n), selection sort her girdide O(n²)

Insertion sort sıralı girdide her eleman için tek bir karşılaştırma yapıp durur: n − 1 = 4. Ters sıralı girdide her eleman en başa kadar kayar: 1 + 2 + 3 + 4 = 10. Selection sort en küçüğü bulmak için kalan kısmın hepsine her zaman bakar: n(n − 1)/2 = 10, girdiye bakmaksızın.

Soru 7zor

Merge sort sorts {38, 27, 43, 3, 9, 82, 10} with mid = (low + high) / 2, left half first. What is the result of the third merge?

Cevabı göster
[3, 27, 38, 43]

Merge'ler sırası: [0..1] → [27, 38], [2..3] → [3, 43], sonra bu ikisi [0..3] → [3, 27, 38, 43]. Sol yarı tamamen bitmeden sağ yarıya geçilmez. Sonra [4..5] → [9, 82], [4..6] → [9, 10, 82], en son [0..6] → [3, 9, 10, 27, 38, 43, 82].

Soru 8orta

Find the bug: the search below misses some elements of {2, 5, 9}.

static int search(int[] a, int key) {
    int low = 0, high = a.length - 1;
    while (low < high) {
        int mid = (low + high) / 2;
        if (a[mid] == key) return mid;
        if (a[mid] < key) low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}
Cevabı göster
Koşul low <= high olmalı; 9 ve 2 için -1 döner

Aralık tek elemana inince (low == high) döngü o elemanı kontrol etmeden biter. Denemede 5 bulundu (1) ama 9 ve 2 için −1 döndü. Doğrusu while (low <= high).

Soru 9kolay

Which statement is TRUE?

  1. Binary search works on any array
  2. Selection sort does fewer comparisons on an already sorted array
  3. Merge sort needs an extra array proportional to n
  4. Insertion sort is O(n log n) in the worst case
Cevabı göster
Merge sort needs an extra array proportional to n

merge adımı iki yarıyı geçici bir diziye birleştirir: ek bellek O(n)O(n). Binary search sadece sıralı dizide çalışır. Selection sort'un karşılaştırma sayısı girdiden bağımsızdır. Insertion sort'un en kötü durumu O(n2)O(n^2).

Soru 10orta

For n = 1000, 2000 and 4000 (reverse-ordered input), selection sort made 499500, 1999000 and 7998000 comparisons; merge sort made 4932, 10864 and 23728. What happens to each when n doubles?

Cevabı göster
Selection yaklaşık 4 katına çıkar, merge sort 2 katından biraz fazla

O(n2)O(n^2): n iki katına çıkınca (2n)2=4n2(2n)^2 = 4n^2, dört kat (499500 → 1999000 → 7998000). O(nlog⁡n)O(n \log n): 2nlog⁡(2n)2n \log(2n), iki kattan biraz fazla (4932 → 10864 → 23728). n büyüdükçe fark uçurum olur; bu yüzden büyük veride merge sort (ve Arrays.sort) kullanılır.

Soru 11orta

How many comparisons does binary search need at most for a sorted array of 1,000,000 elements?

Cevabı göster
20

Her karşılaştırma aralığı yarıya böler. 220=1048576≥1062^{20} = 1048576 \geq 10^6, yani en fazla 20 adım. Linear search aynı dizide en kötü 1.000.000 karşılaştırma yapar.

Soru 12zor

Write a method sortByLength(String[] words) that sorts the array by word length (shortest first) using insertion sort. Words with equal length must keep their original order. {"kiwi", "fig", "banana", "apple", "date"} becomes [fig, kiwi, date, apple, banana].

Cevabı göster
Insertion sort, karşılaştırma words[j].length() > key.length()

Insertion sort kararlıdır (stable): eşit elemanları kaydırmadığı için sıraları korunur. Bunun için karşılaştırma > olmalı, >= değil:

public static void sortByLength(String[] words) {
    for (int i = 1; i < words.length; i++) {
        String key = words[i];
        int j = i - 1;
        while (j >= 0 && words[j].length() > key.length()) {
            words[j + 1] = words[j];   // sağa kaydır
            j--;
        }
        words[j + 1] = key;
    }
}
kiwi ve date ikisi de 4 harfli; kiwi önce geldiği için sonuçta da önce: [fig, kiwi, date, apple, banana].

Doğrulama: Bütün izler, karşılaştırma sayıları ve çıktılar JDK 26 ile çalıştırıldı (java verify/SEN1002/W13.java, W13b.java). Büyüme tablosu aynı dosyada n = 1000, 2000, 4000 için sayılan karşılaştırmalardır.

Deftere not kartı

Linear search: baştan sona, sırasız dizide de çalışır. O(n). Bulamazsa -1. Binary search: SIRALI dizi şart. mid=(low+high)/2; key büyükse low=mid+1, küçükse high=mid-1. Döngü koşulu while (low <= high) (< yazarsan tek kalan eleman kontrol edilmez). O(log n). 1.000.000 eleman → en fazla 20 adım (2^20 ≈ 10^6). Selection: her geçişte kalanın EN KÜÇÜĞÜNÜ bul, başa swap. n-1 geçiş. Her girdide n(n-1)/2 karş. O(n²). Insertion: sıradaki elemanı soldaki sıralı kısma KAYDIRARAK yerleştir. En iyi O(n) (sıralı), en kötü O(n²). Merge: ikiye böl, iki yarıyı özyinelemeli sırala, birleştir. O(n log n) her durumda, ek O(n) bellek. Merge sırası: sol yarı tamamen biter, sonra sağ yarı, sonra ikisi birleşir. n iki katı: O(n²) → 4 kat, O(n log n) → 2 kattan biraz fazla, O(log n) → +1 adım. Pass sorusu: selection → başta en küçükler kesinleşir; insertion → baştaki kısım sıralı ama kesin değil. Hazır: Arrays.sort(a), Arrays.binarySearch(a, key) (sıralı dizi), Collections.sort(list).