SEN2212 · 1. hafta · Introduction and Sorting Algorithms.

Sıralama algoritmaları: analiz, alt sınır, counting ve radix sort

Merge sort, quicksort ve heapsort'un adım adım izi ve Big-O analizi, karşılaştırmalı sıralamanın n log n alt sınırı, karşılaştırmasız counting ve radix sort.

merge sortquicksortpivotpartitionheapsortstable sortin-placedecision treelower boundcounting sortradix sortLSD

SEN2211’in sonunda bubble, selection ve insertion sort gibi basit O(n2)O(n^2) sıralamaları gördün. Bu derste soru seviyesi bir basamak yukarı çıkıyor. Sınavda üç tip soru gelir: iz sür (merge’den önceki yarılar ne, ilk partition’dan sonra dizi ne, buildHeap’ten sonra heap ne), analiz et (en iyi, ortalama, en kötü durum, kaç karşılaştırma), karşılaştır (hangisi stabil, hangisi yerinde, ne zaman hangisi).

Altın kurallar:

  1. Merge sort her durumda O(nlog⁡n)O(n \log n), ama O(n)O(n) ek bellek ister.
  2. Quicksort ortalamada en hızlısı, ama kötü pivotla O(n2)O(n^2) olur.
  3. Heapsort her durumda O(nlog⁡n)O(n \log n) ve yerinde, ama stabil değil.
  4. Karşılaştırma yapan hiçbir sıralama en kötü durumda Ω(nlog⁡n)\Omega(n \log n)‘den iyi olamaz.
  5. Counting ve radix sort bu sınırı aşar, çünkü karşılaştırma yapmaz; anahtarlara ek varsayım koyar.

Merge sort

Böl ve yönet (divide and conquer). Diziyi ortadan ikiye böl, iki yarıyı özyinelemeli sırala, sonra iki sıralı yarıyı tek geçişte birleştir (merge). Merge adımı her iki listenin başına bakar, küçüğü çıktıya yazar.

static void mergeSort(int[] a, int lo, int hi) {
    if (lo >= hi) return;          // 0 ya da 1 eleman: zaten sıralı
    int mid = (lo + hi) / 2;
    mergeSort(a, lo, mid);
    mergeSort(a, mid + 1, hi);
    merge(a, lo, mid, hi);         // O(hi - lo + 1)
}

[42, 17, 8, 93, 25, 61, 4, 30] için merge çağrılarının sırası (her satır bir merge sonrası o aralığın durumu):

merge [0..1] -> [17, 42]
merge [2..3] -> [8, 93]
merge [0..3] -> [8, 17, 42, 93]
merge [4..5] -> [25, 61]
merge [6..7] -> [4, 30]
merge [4..7] -> [4, 25, 30, 61]
merge [0..7] -> [4, 8, 17, 25, 30, 42, 61, 93]
toplam karşılaştırma: 17

Dikkat et: sol yarı tamamen bitmeden sağ yarıya geçilmez. “İkinci merge hangisi?” sorusunda cevap [2..3], [4..5] değil.

Analiz

Yineleme bağıntısı (recurrence):

T(n)=2T(n/2)+cn,T(1)=cT(n) = 2T(n/2) + cn, \qquad T(1) = c

Özyineleme ağacının her seviyesinde toplam iş cncn, seviye sayısı log⁡2n\log_2 n. Sonuç T(n)=O(nlog⁡n)T(n) = O(n \log n). Bu süre girdiden bağımsız: sıralı, ters sıralı, rastgele hepsinde aynı. Ek bellek O(n)O(n), çünkü merge geçici dizi kullanır.

Quicksort

Bir pivot seç. Diziyi pivottan küçükler sola, büyükler sağa gelecek şekilde böl (partition). Pivot artık son yerinde. İki tarafı özyinelemeli sırala. Birleştirme adımı yok, iş partition’da.

Bu sayfada Lomuto şemasını kullanıyoruz: pivot son eleman, i küçükler bölgesinin sonunu tutar.

static int partition(int[] a, int lo, int hi) {
    int p = a[hi], i = lo - 1;
    for (int j = lo; j < hi; j++)
        if (a[j] < p) { i++; swap(a, i, j); }
    swap(a, i + 1, hi);            // pivot yerine
    return i + 1;
}

[29, 71, 12, 55, 8, 40, 33] üzerinde tam iz:

partition [0..6] pivot=33 -> [29,12,8,33,71,40,55]
partition [0..2] pivot=8  -> [8,12,29,33,71,40,55]
partition [1..2] pivot=29 -> [8,12,29,33,71,40,55]
partition [4..6] pivot=55 -> [8,12,29,33,40,55,71]

İlk partition’da 29, 12, 8 pivottan küçük olduğu için öne toplandı; 33 indeks 3’e oturdu ve bir daha hiç yer değiştirmedi.

Analiz

DurumNe olurSüre
En iyiPivot hep ortanca, iki eşit parçaO(nlog⁡n)O(n \log n)
OrtalamaRastgele girdiO(nlog⁡n)O(n \log n), sabiti küçük
En kötüPivot hep en küçük ya da en büyükO(n2)O(n^2)

En kötü durumda bağıntı T(n)=T(n−1)+cnT(n) = T(n-1) + cn olur, toplam n(n−1)/2n(n-1)/2 karşılaştırma. Son eleman pivot olduğunda zaten sıralı dizi tam bu durumu üretir. Pratik çözümler: rastgele pivot ya da median-of-three (ilk, orta, son elemanın ortancası). Küçük alt dizilerde (n < 10 civarı) insertion sort’a geçmek de yaygın.

TUZAK · “quicksort her zaman n log n”. Değil. Ortalama ve en iyi durum O(nlog⁡n)O(n \log n), en kötü O(n2)O(n^2). Sınavda “worst case” soruluyorsa cevap O(n2)O(n^2).

Heapsort

Heap’leri 7. haftada ayrıntılı göreceğiz. Şimdilik bilmen gereken: max-heap, her düğümün çocuklarından büyük ya da eşit olduğu tam ikili ağaç (complete binary tree) ve dizide saklanır. 0 indeksli dizide i’nin çocukları 2i+1 ve 2i+2.

Heapsort iki aşamalı:

  1. buildHeap: son iç düğümden (n/2 - 1) köke kadar her düğümü aşağı süz (percolate down). Süre O(n)O(n).
  2. n - 1 kez deleteMax: kökü (en büyük) heap’in son elemanıyla takas et, heap boyutunu 1 azalt, yeni kökü aşağı süz. Her biri O(log⁡n)O(\log n).

[6, 3, 9, 1, 8, 2] için:

buildHeap   -> [9, 8, 6, 1, 3, 2]
deleteMax 1 -> [8, 3, 6, 1, 2 | 9]
deleteMax 2 -> [6, 3, 2, 1 | 8, 9]
deleteMax 3 -> [3, 1, 2 | 6, 8, 9]
deleteMax 4 -> [2, 1 | 3, 6, 8, 9]
deleteMax 5 -> [1 | 2, 3, 6, 8, 9]

| işaretinin sağı sıralı bölge. Max-heap kullanınca dizi artan sırada biter, çünkü her adımda en büyük sona yazılır. Toplam süre her durumda O(nlog⁡n)O(n \log n), ek bellek O(1)O(1).

Karşılaştırmalı sıralamanın alt sınırı

Sadece a[i] < a[j] gibi karşılaştırmalarla çalışan her algoritma bir karar ağacı (decision tree) ile gösterilebilir. Her iç düğüm bir karşılaştırma, her yaprak girdinin bir sıralaması (permutation).

  • n farklı eleman için n!n! olası sıra var. Algoritma hepsini ayırt etmeli: en az n!n! yaprak.
  • Yüksekliği hh olan ikili ağaçta en fazla 2h2^h yaprak olur.
  • 2h≥n!⇒h≥log⁡2(n!)2^h \ge n! \Rightarrow h \ge \log_2(n!).
  • Stirling yaklaşımıyla log⁡2(n!)=Θ(nlog⁡n)\log_2(n!) = \Theta(n \log n).

Ağacın yüksekliği en kötü durumdaki karşılaştırma sayısıdır. Sonuç:

worst-case comparisons≥⌈log⁡2n!⌉=Ω(nlog⁡n)\text{worst-case comparisons} \ge \lceil \log_2 n! \rceil = \Omega(n \log n)

nn!log⁡2n!\log_2 n!en az karşılaştırma
362.5853
4244.5855
51206.9077
84032015.29916
10362880021.79122

Bu yüzden merge sort ve heapsort asimptotik olarak optimal karşılaştırmalı sıralamalardır.

Counting sort

Anahtarlar 0..k arasında tam sayıysa, karşılaştırmadan sıralayabilirsin. Her değerin kaç kez geçtiğini say, prefix toplamla her değerin çıktıdaki konumunu bul, girdiyi sağdan sola gezerek yerleştir.

[2, 5, 3, 0, 2, 3, 0, 3], k = 5:

count  = [2, 0, 2, 3, 0, 1]
prefix = [2, 2, 4, 7, 7, 8]
output = [0, 0, 2, 2, 3, 3, 3, 5]

prefix[3] = 7 demek: 3’lerin sonuncusu çıktının 6. indeksine (7 - 1) gider. Her yerleştirmede sayaç bir azalır. Sağdan sola gezmek eşit anahtarların sırasını korur: sıralama stabil. Süre O(n+k)O(n + k), bellek O(n+k)O(n + k). k çok büyükse (örn. 32 bitlik sayılar) pratik değil.

Radix sort

Çok basamaklı sayıları en düşük basamaktan (LSD, least significant digit) başlayarak, her basamak için stabil bir kova/counting geçişiyle sırala.

[329, 457, 657, 839, 436, 720, 355]:

birler  -> [720, 355, 436, 457, 657, 329, 839]
onlar   -> [720, 329, 436, 839, 355, 457, 657]
yüzler  -> [329, 355, 436, 457, 657, 720, 839]

İkinci geçişte 720 ile 329’un onlar basamağı 2, ikisi aynı kovada. Birinci geçişteki sıralarını (720 önce) korudular. Her geçiş stabil olmak zorunda, yoksa önceki basamakların emeği kaybolur.

Süre: d basamak, taban b için O(d(n+b))O(d(n + b)). d ve b sabitse lineer.

Özet tablo

AlgoritmaEn iyiOrtalamaEn kötüEk bellekStabil
InsertionO(n)O(n)O(n2)O(n^2)O(n2)O(n^2)O(1)O(1)evet
MergeO(nlog⁡n)O(n \log n)O(nlog⁡n)O(n \log n)O(nlog⁡n)O(n \log n)O(n)O(n)evet
QuickO(nlog⁡n)O(n \log n)O(nlog⁡n)O(n \log n)O(n2)O(n^2)O(log⁡n)O(\log n)hayır
HeapO(nlog⁡n)O(n \log n)O(nlog⁡n)O(n \log n)O(nlog⁡n)O(n \log n)O(1)O(1)hayır
CountingO(n+k)O(n+k)O(n+k)O(n+k)O(n+k)O(n+k)O(n+k)O(n+k)evet
RadixO(d(n+b))O(d(n+b))O(d(n+b))O(d(n+b))O(d(n+b))O(d(n+b))O(n+b)O(n+b)evet

Sık yapılan hatalar

Yanlış düşünceDoğrusu
Quicksort en kötü durumda da O(nlog⁡n)O(n \log n)O(n2)O(n^2); sıralı girdi + uç pivot
Merge sort yerinde (in-place)Standart sürüm O(n)O(n) geçici dizi ister
Heapsort stabilDeğil; kök ile son eleman takası sırayı bozar
buildHeap O(nlog⁡n)O(n \log n)O(n)O(n); toplam O(nlog⁡n)O(n \log n) deleteMax’lerden gelir
Alt sınır her sıralama için geçerliSadece karşılaştırmalı sıralamalar için; radix/counting muaf
Radix sort’a en yüksek basamaktan başlanırLSD sürümü en düşük basamaktan başlar
Counting sort’ta soldan sağa yerleştirmek de stabilPrefix’i azaltarak yerleştiriyorsan sağdan sola gezmelisin
Merge sort izinde sol ve sağ yarı dönüşümlü işlenirSol yarı tamamen bitmeden sağa geçilmez

Kendini test et

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

Soru 1kolay

Merge sort (top-down, mid = (lo + hi) / 2, left half is [lo..mid]) is applied to [55, 12, 70, 3, 41, 28]. What are the two sorted halves just before the final merge?

Cevabı göster
[12, 55, 70] and [3, 28, 41]

n = 6, mid = (0+5)/2 = 2. Sol yarı [0..2] = 55, 12, 70; sağ yarı [3..5] = 3, 41, 28.
Sol: [55,12] birleşir → [12,55], sonra 70 ile → [12,55,70].
Sağ: [3,41] → [3,41], sonra 28 ile → [3,28,41].
Son merge bu iki diziyi birleştirir: [3,12,28,41,55,70].

Soru 2orta

How many element comparisons does the standard merge step make when merging [1, 3, 5, 7] with [2, 4, 6, 8]? And [1, 2, 3, 4] with [5, 6, 7, 8]?

Cevabı göster
7 and 4

Birinci durumda elemanlar sırayla iki listeden gelir, bir liste ancak son elemanda biter: n1 + n2 - 1 = 7 karşılaştırma.
İkinci durumda soldaki 4 eleman 5 ile karşılaştırılıp tek tek çıkar (4 karşılaştırma), sol liste biter, sağdakiler karşılaştırmasız kopyalanır.
Kural: merge en az min(n1, n2), en fazla n1 + n2 - 1 karşılaştırma yapar.

Soru 3orta

Quicksort uses the last element as pivot and the Lomuto partition scheme. Show the array after the first partition of [14, 52, 7, 33, 61, 20, 25] and give the pivot's final index.

Cevabı göster
[14, 7, 20, 25, 61, 52, 33], index 3

Pivot = 25, i = -1. j soldan sağa gezer, a[j] < 25 ise i++ ve a[i] ile a[j] takas.
j=0: 14 < 25 → i=0 (yerinde). j=1: 52 değil. j=2: 7 < 25 → i=1, 52 ile 7 takas: [14,7,52,33,61,20,25].
j=3: 33 değil. j=4: 61 değil. j=5: 20 < 25 → i=2, 52 ile 20 takas: [14,7,20,33,61,52,25].
Son olarak a[i+1]=a[3] ile pivot takas: [14,7,20,25,61,52,33]. Pivot indeksi 3, toplam 6 karşılaştırma.

Soru 4orta

Quicksort with last-element pivot (Lomuto) is run on the already sorted array [1, 2, 3, 4, 5, 6, 7, 8]. How many element comparisons are made in total, and what is the complexity class?

Cevabı göster
28, O(n^2)

Sıralı dizide pivot hep en büyük eleman. Her partition sadece pivotu ayırır, bir taraf boş kalır.
Karşılaştırmalar: 7 + 6 + 5 + 4 + 3 + 2 + 1 = 28 = n(n-1)/2.
Bu quicksort'un en kötü durumu: O(n2)O(n^2). Çözüm: rastgele pivot ya da median-of-three.

Soru 5zor

Heapsort (max-heap, 0-indexed array) first runs buildHeap on [5, 13, 2, 25, 7, 17, 20, 8, 4]. What is the array after buildHeap?

Cevabı göster
[25, 13, 20, 8, 7, 17, 2, 5, 4]

n = 9, son iç düğüm n/2 - 1 = 3. i = 3, 2, 1, 0 için percolate down:
i=3 (25): çocuklar 8, 4. 25 büyük, değişmez.
i=2 (2): çocuklar 17, 20 → 20 ile yer değişir: [5,13,20,25,7,17,2,8,4].
i=1 (13): çocuklar 25, 7 → 25 yukarı, 13 indeks 3'e iner; çocukları 8, 4, durur: [5,25,20,13,7,17,2,8,4].
i=0 (5): çocuklar 25, 20 → 25 yukarı; 5 indeks 1'de, çocuklar 13, 7 → 13 yukarı; 5 indeks 3'te, çocuklar 8, 4 → 8 yukarı; 5 indeks 7'de durur.
Sonuç: [25,13,20,8,7,17,2,5,4].

Soru 6zor

Continue the previous question: after buildHeap gives [25, 13, 20, 8, 7, 17, 2, 5, 4], heapsort performs one deleteMax (swap root with the last heap element, shrink heap, percolate down). What is the array now?

Cevabı göster
[20, 13, 17, 8, 7, 4, 2, 5, 25]

25 ile son eleman 4 takas edilir: [4,13,20,8,7,17,2,5,25]. Heap boyutu 8, 25 artık sıralı bölgede.
4 kökten iner: çocuklar 13, 20 → 20 yukarı. 4 indeks 2'de, çocuklar 17, 2 → 17 yukarı. 4 indeks 5'te, çocuğu yok (11 > 7).
Sonuç: [20,13,17,8,7,4,2,5,25].

Soru 7orta

Using the decision-tree argument, what is the minimum number of comparisons any comparison-based algorithm needs in the worst case to sort 5 distinct elements?

Cevabı göster
7

5 elemanın 5! = 120 farklı sırası var. Karar ağacının her yaprağı bir sıra, yani en az 120 yaprak gerekir.
Yüksekliği h olan ikili ağacın en fazla 2h2^h yaprağı olur: 2h≥1202^h \ge 120, h≥log⁡2120≈6.907h \ge \log_2 120 \approx 6.907.
Tam sayı olmalı: en az 7 karşılaştırma.

Soru 8kolay

Counting sort is applied to [3, 1, 4, 1, 5, 2, 3, 0] with keys in 0..5. Give the count array and the prefix-sum (cumulative) array.

Cevabı göster
count = [1, 2, 1, 2, 1, 1], prefix = [1, 3, 4, 6, 7, 8]

count[v] = v değerinin kaç kez geçtiği: 0 bir kez, 1 iki kez, 2 bir kez, 3 iki kez, 4 bir kez, 5 bir kez.
Prefix: her hücreye soldakilerin toplamı eklenir: 1, 1+2=3, 3+1=4, 4+2=6, 6+1=7, 7+1=8.
prefix[v] = v değerinin çıktıdaki son konumu + 1. Sağdan sola yerleştirince sonuç [0,1,1,2,3,3,4,5] ve sıralama stabil.

Soru 9orta

LSD radix sort (base 10, stable bucket pass per digit) is applied to [170, 45, 75, 90, 802, 24, 2, 66]. What is the array after the first pass and after the second pass?

Cevabı göster
Pass 1: [170, 90, 802, 2, 24, 45, 75, 66]; Pass 2: [802, 2, 24, 45, 66, 170, 75, 90]

Geçiş 1 (birler basamağı): 0 → 170, 90; 2 → 802, 2; 4 → 24; 5 → 45, 75; 6 → 66. Aynı kovadakiler giriş sırasını korur.
Geçiş 2 (onlar basamağı, 1. geçişin çıktısı üzerinden): 0 → 802, 2; 2 → 24; 4 → 45; 6 → 66; 7 → 170, 75; 9 → 90.
Geçiş 3 (yüzler) sonunda: [2,24,45,66,75,90,170,802].

Soru 10kolay

Which of merge sort, quicksort and heapsort (standard array versions) is stable, and which one needs O(n) extra memory?

Cevabı göster
Merge sort; merge sort

Merge sort eşit elemanlarda soldakini önce alır (a[i] <= a[j]), bu yüzden stabil. Ama merge için n boyutlu geçici dizi ister: O(n) ek bellek.
Quicksort ve heapsort uzak elemanları takas eder, eşitlerin sırası bozulabilir: stabil değiller. İkisi de yerinde (in-place) çalışır; quicksort özyineleme yığını için ortalama O(log n) kullanır.

Soru 11orta

Radix sort sorts n integers that each have d digits in base b. What is its running time? When is it O(n)?

Cevabı göster
O(d(n + b)); when d and b are constants (or b = O(n) and d constant)

Her basamak için bir counting/bucket geçişi yapılır: O(n + b). d basamak için O(d(n + b)).
d sabitse (örn. 32 bitlik sayılar, sabit uzunlukta anahtar) ve b en fazla n mertebesindeyse süre O(n) olur.
Bu n log n alt sınırını bozmaz, çünkü radix sort karşılaştırma yapmaz.

Soru 12zor

Write a Java method static void merge(int[] a, int lo, int mid, int hi) that merges the sorted ranges a[lo..mid] and a[mid+1..hi] into one sorted range, keeping the sort stable.

Cevabı göster
Two indices + temp array, take from left when a[i] <= a[j], copy back

Fikir: iki işaretçi i (sol) ve j (sağ). Küçük olanı geçici diziye al. Eşitlikte soldakini al (stabil). Biri bitince kalanını kopyala, sonra geçici diziyi geri yaz.

static void merge(int[] a, int lo, int mid, int hi) {
    int[] tmp = new int[hi - lo + 1];
    int i = lo, j = mid + 1, k = 0;
    while (i <= mid && j <= hi) {
        if (a[i] <= a[j]) tmp[k++] = a[i++];
        else tmp[k++] = a[j++];
    }
    while (i <= mid) tmp[k++] = a[i++];
    while (j <= hi) tmp[k++] = a[j++];
    for (k = 0; k < tmp.length; k++) a[lo + k] = tmp[k];
}
Süre O(hi - lo + 1). < yazarsan sıralama yine doğru olur ama stabil olmaz.

Doğrulama: Tüm izler, karşılaştırma sayıları, heap dizileri, counting/radix geçişleri ve log2(n!) değerleri Java ile hesaplandı (verify/SEN2212/W01.java, java W01.java). merge ve quickSort metotları 1000 rastgele dizide Arrays.sort ile karşılaştırıldı.

Deftere not kartı

Merge sort: böl, sırala, birleştir. HER durumda O(n log n). Stabil. O(n) ek bellek. T(n) = 2T(n/2) + O(n) → O(n log n). Merge: en az min(n1,n2), en çok n1+n2-1 karşılaştırma. Quicksort: partition, pivot doğru yerine oturur. Ortalama O(n log n), en kötü O(n^2). En kötü: sıralı dizi + ilk/son eleman pivot → n(n-1)/2 karşılaştırma. Çare: rastgele / median-of-three. Lomuto: i=lo-1, a[j]<p ise i++ swap; sonunda pivot a[i+1]'e. Heapsort: buildHeap O(n) + n kez deleteMax O(log n) → O(n log n) her durumda. Yerinde, stabil değil. Max-heap 0 indeksli: çocuklar 2i+1, 2i+2; son iç düğüm n/2-1. Alt sınır: karşılaştırmalı sıralama en kötü ceil(log2 n!) = Omega(n log n). 5 eleman → 7. Counting sort: O(n + k), anahtar aralığı küçükse. Sağdan sola yerleştir → stabil. Radix (LSD): en sağ basamaktan başla, her geçiş STABİL olmalı. O(d(n+b)). Stabil: merge, insertion, counting, radix. Stabil değil: quick, heap, selection.