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.
SEN2211’in sonunda bubble, selection ve insertion sort gibi basit 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:
- Merge sort her durumda , ama ek bellek ister.
- Quicksort ortalamada en hızlısı, ama kötü pivotla olur.
- Heapsort her durumda ve yerinde, ama stabil değil.
- Karşılaştırma yapan hiçbir sıralama en kötü durumda ‘den iyi olamaz.
- 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):
Özyineleme ağacının her seviyesinde toplam iş , seviye sayısı . Sonuç . Bu süre girdiden bağımsız: sıralı, ters sıralı, rastgele hepsinde aynı. Ek bellek , çü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
| Durum | Ne olur | Süre |
|---|---|---|
| En iyi | Pivot hep ortanca, iki eşit parça | |
| Ortalama | Rastgele girdi | , sabiti küçük |
| En kötü | Pivot hep en küçük ya da en büyük |
En kötü durumda bağıntı olur, toplam 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 , en kötü . Sınavda “worst case” soruluyorsa cevap .
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ı:
- buildHeap: son iç düğümden (
n/2 - 1) köke kadar her düğümü aşağı süz (percolate down). Süre . - 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 .
[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 , ek bellek .
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 olası sıra var. Algoritma hepsini ayırt etmeli: en az yaprak.
- Yüksekliği olan ikili ağaçta en fazla yaprak olur.
- .
- Stirling yaklaşımıyla .
Ağacın yüksekliği en kötü durumdaki karşılaştırma sayısıdır. Sonuç:
| n | n! | en az karşılaştırma | |
|---|---|---|---|
| 3 | 6 | 2.585 | 3 |
| 4 | 24 | 4.585 | 5 |
| 5 | 120 | 6.907 | 7 |
| 8 | 40320 | 15.299 | 16 |
| 10 | 3628800 | 21.791 | 22 |
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 , bellek . 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 . d ve b sabitse lineer.
Özet tablo
| Algoritma | En iyi | Ortalama | En kötü | Ek bellek | Stabil |
|---|---|---|---|---|---|
| Insertion | evet | ||||
| Merge | evet | ||||
| Quick | hayır | ||||
| Heap | hayır | ||||
| Counting | evet | ||||
| Radix | evet |
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Quicksort en kötü durumda da | ; sıralı girdi + uç pivot |
| Merge sort yerinde (in-place) | Standart sürüm geçici dizi ister |
| Heapsort stabil | Değil; kök ile son eleman takası sırayı bozar |
| buildHeap | ; toplam deleteMax’lerden gelir |
| Alt sınır her sıralama için geçerli | Sadece karşılaştırmalı sıralamalar için; radix/counting muaf |
| Radix sort’a en yüksek basamaktan başlanır | LSD sürümü en düşük basamaktan başlar |
| Counting sort’ta soldan sağa yerleştirmek de stabil | Prefix’i azaltarak yerleştiriyorsan sağdan sola gezmelisin |
| Merge sort izinde sol ve sağ yarı dönüşümlü işlenir | Sol yarı tamamen bitmeden sağa geçilmez |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
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
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].
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
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.
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
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.
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
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: . Çözüm: rastgele pivot ya da median-of-three.
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
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].
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
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].
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
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 yaprağı olur: , .
Tam sayı olmalı: en az 7 karşılaştırma.
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[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.
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
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].
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 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.
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
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.
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
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ı.