Bölüm 20: İleri Sıralama Algoritmaları

C ile Veri Yapıları ve Algoritmalar
Quick sortOrtalama durumÖzyinelemesizMedyan-3qsortMerge sortHeap sortRadix sort
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Hızlı sıralama

Bölümleme (partition) değişmezini açıklamak; bol() ve özyinelemeyi adım adım izlemek.

Analiz

En iyi n lg n, en kötü n², ortalama ≈ 1,39 n lg n sonuçlarını yineleme bağıntılarından elde etmek.

İyileştirmeler

Özyinelemesiz sürüm (O(lg n) yığın), medyan-3, küçük parçada eklemeli; C'de qsort.

Birleştirme sıralaması

merge işlemini, yığın çerçevelerini ve bağlı (indisli) sürümü izlemek; O(n) ek bellek.

Heap sort

Aşağıdan yukarıya O(n)'de heap kurup yerinde O(n lg n) sıralamak.

Basamağa göre

LSD radix sort'u kovalı bağlı listelerle uygulamak ve karmaşıklığını yorumlamak.

Hızlı Sıralama

Hızlı (Çabuk) Sıralama: Quick Sort

  • 1962'de C. A. R. (Tony) Hoare tarafından geliştirildi.
  • Böl ve fethet (divide and conquer): problem doğrudan çözülecek kadar küçülene dek özyinelemeli olarak alt problemlere bölünür.
  • Ortalama n lg n ile orantılı zaman; en kötü durumda n². Yalnızca küçük bir yardımcı yığın kullanır.
  • Kararlı değildir; yine de bilinen en hızlı genel amaçlı algoritmalardan biridir. Standart C kitaplığında qsort vardır.
int bol(int a[], int L, int R);

void quicksort1(int a[], int L, int R) {
    if (R <= L) return;
    int i = bol(a, L, R);
    quicksort1(a, L, i - 1);
    quicksort1(a, i + 1, R);
}
Hocadan “Bu kod, Sedgewick'in kitabından alınmıştır. Oldukça güzel yazılmış bir kod. (Sedgewick, Knuth'un doktora öğrencisidir; quick sort üzerinde fazlaca çalışmıştır.)”

Hızlı Sıralama

bol(): Bölümleme (Partition)

Lsol uç≤ vküçük/eşit?taranmadı≥ vbüyük/eşitva[R]i →← j
int bol(int a[], int L, int R) {
    int i = L - 1, j = R, v = a[R]; // pivot a[R]
    for (;;) {
        while (kucuk(a[++i], v))
            ;
        while (kucuk(v, a[--j]))
            if (j == L) break;
        if (i >= j) break;
        SWAP(a[i], a[j]);
    }
    SWAP(a[i], a[R]);
    return i;
}
  • [2] Pivot (partitionary element) v = a[R].
  • [4] i soldan, v'den büyük ya da eşit ilk öğede durur. Sınır sınaması gerekmez: en kötü i = R'de a[R] = v durdurur.
  • [6–7] j sağdan, v'den küçük ya da eşit öğede durur; bu döngüde taşmayı j == L önler.
  • [9] İki öğe yanlış taraftadır → takas. [8] i ile j buluşunca döngü biter; [11] pivot a[i]'ye: solu ≤ v ≤ sağı.

Hızlı Sıralama · Kodu adım adım çalıştır

Kod İzleme: bol(a, 0, 9)

037115224333449511651777872948▲ jv48

Pivot v = a[9] = 48. i = L − 1 = −1, j = R = 9: taramalar ++i ve --j ile başlayacak.

037115224333449511651777872948▲ i▲ jv48

a[0] = 37 < 48 → i ilerlemeye devam.

037115224333449511651777872948▲ i▲ j≤ vv48

a[1] = 15 < 48 → i ilerlemeye devam.

037115224333449511651777872948▲ i▲ j≤ vv48

a[2] = 24 < 48 → i ilerlemeye devam.

037115224333449511651777872948▲ i▲ j≤ vv48

a[3] = 33 < 48 → i ilerlemeye devam.

037115224333449511651777872948▲ i▲ j≤ vv48

a[4] = 49 ≥ 48 → i = 4'te durdu (sağ tarafa ait bir öğe).

037115224333449511651777872948▲ i▲ j≤ vv48

a[8] = 72 > 48 → j sola ilerliyor.

037115224333449511651777872948▲ i▲ j≤ v≥ vv48

a[7] = 77 > 48 → j sola ilerliyor.

037115224333449511651777872948▲ i▲ j≤ v≥ vv48

a[6] = 51 > 48 → j sola ilerliyor.

037115224333449511651777872948▲ i▲ j≤ v≥ vv48

a[5] = 11 ≤ 48 → j = 5'te durdu.

037115224333411549651777872948▲ i▲ j≤ v≥ vv48

i < j: a[4] ile a[5] yanlış tarafta → takas: [37, 15, 24, 33, 11, 49, 51, 77, 72, 48].

037115224333411549651777872948▲ i j≤ v≥ vv48

a[5] = 49 ≥ 48 → i = 5'te durdu (sağ tarafa ait bir öğe).

037115224333411549651777872948▲ i▲ j≤ v≥ vv48

a[4] = 11 ≤ 48 → j = 4'te durdu.

037115224333411549651777872948▲ i▲ j≤ v≥ vv48

i = 5 ≥ j = 4: indisler çaprazlandı → döngüden çık.

037115224333411548651777872949▲ i▲ j≤ 48≥ 48v48

Pivot a[5]'e taşındı ve 5 döndürüldü. Veriler sıralansaydı 48 tam burada olurdu.

int bol(int a[], int L, int R) {
    int i = L - 1, j = R, v = a[R]; // pivot a[R]
    for (;;) {
        while (kucuk(a[++i], v))
            ;
        while (kucuk(v, a[--j]))
            if (j == L) break;
        if (i >= j) break;
        SWAP(a[i], a[j]);
    }
    SWAP(a[i], a[R]);
    return i;
}

Hızlı Sıralama · Kodu adım adım çalıştır

Kod İzleme: quicksort1(a, 0, 9) — Çağrı Yığını

037115224333411548651777872949▲ L▲ R

bol(0, 9): pivot 48 → konum 5. Sol a[0..4], sağ a[6..9] ayrı sıralanacak.

011115224333437548651777872949▲ L▲ R

bol(0, 4): pivot 11 → konum 0. Sol a[0..-1], sağ a[1..4] ayrı sıralanacak.

011115224333437548651777872949

quicksort1(0, -1): boş aralık → dön.

011115224333437548651777872949▲ L▲ R

bol(1, 4): pivot 37 → konum 4. Sol a[1..3], sağ a[5..4] ayrı sıralanacak.

011115224333437548651777872949▲ L▲ R

bol(1, 3): pivot 33 → konum 3. Sol a[1..2], sağ a[4..3] ayrı sıralanacak.

011115224333437548651777872949▲ L▲ R

bol(1, 2): pivot 24 → konum 2. Sol a[1..1], sağ a[3..2] ayrı sıralanacak.

011115224333437548651777872949▲ L

quicksort1(1, 1): tek öğe → zaten yerinde, dön.

011115224333437548651777872949

quicksort1(3, 2): boş aralık → dön.

011115224333437548651777872949

quicksort1(4, 3): boş aralık → dön.

011115224333437548651777872949

quicksort1(5, 4): boş aralık → dön.

011115224333437548649777872951▲ L▲ R

bol(6, 9): pivot 49 → konum 6. Sol a[6..5], sağ a[7..9] ayrı sıralanacak.

011115224333437548649777872951

quicksort1(6, 5): boş aralık → dön.

011115224333437548649751872977▲ L▲ R

bol(7, 9): pivot 51 → konum 7. Sol a[7..6], sağ a[8..9] ayrı sıralanacak.

011115224333437548649751872977

quicksort1(7, 6): boş aralık → dön.

011115224333437548649751872977▲ L▲ R

bol(8, 9): pivot 77 → konum 9. Sol a[8..8], sağ a[10..9] ayrı sıralanacak.

011115224333437548649751872977▲ L

quicksort1(8, 8): tek öğe → zaten yerinde, dön.

011115224333437548649751872977

quicksort1(10, 9): boş aralık → dön.

int bol(int a[], int L, int R);

void quicksort1(int a[], int L, int R) {
    if (R <= L) return;
    int i = bol(a, L, R);
    quicksort1(a, L, i - 1);
    quicksort1(a, i + 1, R);
}

Hızlı Sıralama

Değerlendirme: Neden Hızlı?

Doğru konum + küçülen problem

Kabarcık ve seçimli de her adımda bir öğeyi doğru konuma koyar; ama sonraki adımda alt problem n − 1 kalır. Hızlı sıralama hem pivotu yerleştirir hem de problemi iki küçük parçaya böler.

Özyineleme ağacı

quicksort1(a, 0, 9) izinde 10 öğe için 17 çağrı: her iç çağrı bir pivotu kesin yerine koydu (izde yeşil hücreler).

Hocadan “Denilebilir ki, kabarcık ya da seçimli sıralamada da her adımda bir öğe için doğru konum bulunuyordu. Evet! Onlar da buluyor, ama problemi küçülterek sonraki adıma katkı yapamıyorlar.”
Hocadan “Quick sort’la ilgili üç-dört algoritma yazacağım. O yüzden bunun anlaşılması önemli. […] Küçük kodlar defalarca, farklı şekillerde yazılmalı.”

Hızlı Sıralama · Okuma Parçası

Okuma Parçası: Algoritma ve Bilgisayar

Hocadan “— Hocam! Adam bu quick sort'u 1960'larda nerde denemiş, nasıl test etmiş?
Algoritma kelimesi nerden geliyor? 8. yüzyılda el-Harezmî'nin 4GB'lık laptopu mu vardı? ☺”
Hocadan “Algoritma demek; hiç bilmeyen adam onu alacak, adım adım işletecek ve doğru sonucu bulacak, demek. Kaldı ki, 1960'larda bilgisayar zaten vardı. Fakat bizim dersimiz, bilgisayardan bağımsızdır. Zaten bir algoritmanın makalesinin çıkması yeterlidir.”
El-Harezmî (780–850)

Bağdat'taki Beytü'l-Hikme'de çalışan matematikçi ve gökbilimci. Birinci ve ikinci dereceden denklemlerin sistematik çözümünü veren el-Kitâbü'l-Muhtasar fî Hisâbi'l-Cebr ve'l-Mukâbele kitabının yazarı.

Kelimelerin kökeni

“Cebir/algebra” bu kitaptaki el-cebr yönteminden; “algoritma” ise adının Latince biçimi Algoritmi'den gelir. Onluk konumsal sayı sistemini Batı'ya tanıtan eserleriyle bilinir.

Hızlı Sıralama

Quick Sort: En Kötü ve En İyi Durum

En kötü: sıralı dizi

Pivot a[R] her seferinde en büyüktür, yerinde kalır; sonraki çağrı bir eksik boyutla yapılır:
n + (n−1) + … + 1 = n(n+1)/2 = O(n²)

En iyi: tam ortadan bölme

Her adımda i ve j toplam n adım ilerler, n/2'lik iki alt problem kalır:
Cn = 2Cn/2 + n = 2kCn/2k + k·n = n lg n

Hocadan “Şimdi “Bunun neresi hızlı?” diye düşünülecek.”
En iyi durum n lg n; optimal sıralama sınırı n lg n − 1,44n idi. Yani katsayı yine 1, ama 1,44n kadar fazlalık var.

Hızlı Sıralama · Sayısal doğrulama

Sayım: Sıralı ve Rastgele Girdide quicksort1

nsıralı: karşılaştırmaderinlikrastgele: karşılaştırmaderinlikn lg n
1063936533
1005.148997219664
1000501.49899912.831259.966

bol() içindeki karşılaştırmalar Python simülasyonuyla sayıldı (rastgele girdi: tohum 20).

Sıralı girdi

Karşılaştırma ≈ n²/2 ve özyineleme derinliği n − 1: C'de büyük n için yığın taşması riski.

Rastgele girdi

Karşılaştırma n lg n mertebesinde, derinlik yalnızca birkaç lg n.

Hızlı Sıralama

Ortalama Durum Analizi: Yineleme Bağıntısı

Cn = (n + 1) + (1/n) Σk=1n (Ck−1 + Cn−k),   C0 = C1 = 0

  • Bölme noktası k, 1 ≤ k ≤ n arasında eşit olasılıklı: n! dizilişin hepsinde bol() çağrılınca frekanslar eşit çıkar.
  • n + 1: bol()'da pivotla yapılan karşılaştırmalar; k'da bölünürse (k−1) ve (n−k) öğeli iki alt dizi kalır.
  • İki toplam aynı terimleri içerir: Σ(Ck−1 + Cn−k) = 2 Σk=0n−1 Ck ⇒ n·Cn = n(n+1) + 2 Σ Ck−1 … (1)
Hocadan “Yazı tura atılıyor, kaç kez yazı geleceği araştırılıyor. 10 kez, 100 kez, 1000 kez, hatta 1 milyon kez atıldı. Biri dedi ki; ½'ye yakınsar! İşte, nerede yoğunlaşacağını ortalama durum analizi yaparak saptarız.”
Hocadan ““Bana göre, k'nın hepsinde olma ihtimali eşittir.” Bana göresi, sana göresi yok bu işin! Matematiği var bu işin.”

Hızlı Sıralama

Ortalama Durum: Çıkarma Hilesi ve Sonuç

  • (1)'de n yerine n − 1: (n−1)Cn−1 = n(n−1) + 2 Σk=1n−1 Ck−1 … (2)
  • (1) − (2): n·Cn − (n−1)Cn−1 = 2n + 2Cn−1 ⇒ n·Cn = (n+1)Cn−1 + 2n
  • (n(n+1)'e bölünce) Cn/(n+1) = Cn−1/n + 2/(n+1): artık iterasyonla açılabilir.
  • Açınca Cn ≈ 2(n+1)(1/3 + 1/4 + … + 1/(n+1)) ≤ 2(n+1) ln n.

Cn ≈ 2n ln n = 2 ln 2 · n lg n ≈ 1,39 n lg n

nCn (bağıntıdan)1,39 n lg noran
1037,146,20,80
100780,5923,50,85
100012.318,613.852,40,89

Tablo: bağıntı Python ile tam hesaplandı. Oran yavaşça 1'e yaklaşır (alt dereceli −cn terimi).

Hocadan “Kitap, bunu yerine koymayla çözüyor. Biz daha akıllıca bir taktikle çözeceğiz.”

Hızlı Sıralamayı İyileştirmek

Özyinelemesiz Quick Sort (quicksort2)

void quicksort2(int a[], int L, int R) {
  yigit_baslat(); yigita_at(L, R);
  while (!yigit_bosmu()) {
    L = yigit_al(); R = yigit_al();
    if (R <= L) continue;
    int i = bol(a, L, R);
    if (i - L > R - i) {       // büyük parça önce
      yigita_at(L, i - 1); yigita_at(i + 1, R);
    } else {
      yigita_at(i + 1, R); yigita_at(L, i - 1);
    }
  }
}
#define YMAX 64     /* > 2·lg n */
static int yigit[YMAX], ust;
void yigit_baslat(void) { ust = 0; }
int yigit_bosmu(void) { return ust == 0; }
void yigit_ekle(int x) { yigit[ust++] = x; }
int yigit_al(void) { return yigit[--ust]; }
#define yigita_at(A, B) \
  do { yigit_ekle(B); yigit_ekle(A); } while (0)
  • Amaç: sistem yığını yerine kendi yığınımızı kullanmak. yigita_at(A,B) önce B'yi sonra A'yı iter; böylece ilk alınan L olur.
  • [7–11] Büyük parça önce itilir, küçük parça sonra: son eklenen önce alındığından hep küçük parça işlenir → yığın derinliği O(lg n)'de kalır (en çok ≈ lg n çift).
  • Bellek bağıntısı Tn = Tn/2 + 2 ⇒ Tn = 2 lg n (ikili aramadaki gibi).

Hızlı Sıralamayı İyileştirmek · Kodu adım adım çalıştır

Kod İzleme: quicksort2(a, 0, 6) — Açık Yığın

04172236415563yığın (üst sağda)60

Yığına ilk problem (0, 6) itildi: önce R = 6, sonra L = 0.

01122336445567▲ L▲ Ryığın (üst sağda)6310

(0, 6): bol → pivot 3 konum 2. sağ (3,6) büyük/eşit → önce o, sonra sol (0,1) itildi; küçük parça üstte.

01122336445567▲ L▲ Ryığın (üst sağda)630012

(0, 1): bol → pivot 2 konum 1. sol (0,0) büyük → önce o, sonra sağ (2,1) itildi; küçük parça üstte.

01122336445567yığın (üst sağda)6300

(2, 1) alındı: boş aralık → continue.

01122336445567▲ Lyığın (üst sağda)63

(0, 0) alındı: tek öğe → continue.

01122336445567▲ L▲ Ryığın (üst sağda)5367

(3, 6): bol → pivot 7 konum 6. sol (3,5) büyük → önce o, sonra sağ (7,6) itildi; küçük parça üstte.

01122336445567yığın (üst sağda)53

(7, 6) alındı: boş aralık → continue.

01122334455667▲ L▲ Ryığın (üst sağda)5533

(3, 5): bol → pivot 5 konum 4. sağ (5,5) büyük/eşit → önce o, sonra sol (3,3) itildi; küçük parça üstte.

01122334455667▲ Lyığın (üst sağda)55

(3, 3) alındı: tek öğe → continue.

01122334455667▲ Lyığın (üst sağda)boş

(5, 5) alındı: tek öğe → continue.

01122334455667yığın (üst sağda)boş

Yığın boş → bitti: [1, 2, 3, 4, 5, 6, 7]. Yığında en çok 3 (L, R) çifti bekledi.

void quicksort2(int a[], int L, int R) {
  yigit_baslat(); yigita_at(L, R);
  while (!yigit_bosmu()) {
    L = yigit_al(); R = yigit_al();
    if (R <= L) continue;
    int i = bol(a, L, R);
    if (i - L > R - i) {       // büyük parça önce
      yigita_at(L, i - 1); yigita_at(i + 1, R);
    } else {
      yigita_at(i + 1, R); yigita_at(L, i - 1);
    }
  }
}

Hızlı Sıralamayı İyileştirmek

Geliştirilmiş Quick Sort: Medyan-3 + Eklemeli

#define M 10    /* bu boydan küçük parça kalsın */
void quicksort3(int a[], int L, int R) {
    if (R - L <= M) return;
    SWAP(a[(L + R) / 2], a[R - 1]);
    if (a[R-1] < a[L]) SWAP(a[R-1], a[L]); // a[L] <= a[R-1]
    if (a[R] < a[L]) SWAP(a[R], a[L]);     // a[L] <= a[R]
    if (a[R] < a[R-1]) SWAP(a[R], a[R-1]); // a[R-1] <= a[R]
    int i = bol(a, L + 1, R - 1);
    quicksort3(a, L, i - 1);
    quicksort3(a, i + 1, R);
}
void sirala(int a[], int L, int R) {
    quicksort3(a, L, R);
    insertion(a, L, R);   /* kalan küçük parçalar */
}
if (kucuk(A, B)) SWAP(A, B) “A < B ise takas” demektir; argümanlar (a[L], a[R-1]) sırasıyla verilirse büyüğü a[L]'ye atar ve bölümlemeyi bozar (rastgele dizilerle denendi: pivotun solunda ondan büyük öğe kalıyor). Bu yüzden argümanlar ters sırada verilir: sonuçta a[L] ≤ a[R−1] ≤ a[R].
Hocadan “C'deki standart kütüphane fonksiyonu olan qsort() muhtemelen budur.”

Hızlı Sıralamayı İyileştirmek

Neden Medyan-3 ve Küçük Parça Eşiği?

Medyan-3 (median-of-three)

a[L], ortadaki öğe ve a[R] sıraya konur; ortancası a[R−1]'e alınıp pivot olur. Quick sort en kötü performansını, insertion en iyi performansını sıralı veride gösteriyordu: sıralı girdideki n² durumu bozulur.

Doğal bekçiler

a[L] ≤ pivot ≤ a[R] olduğundan bölümleme L+1..R−1 üzerinde yapılır; uç öğeler taramaları durdurur.

Eşik M

R − L ≤ M olan parçalar sıralanmadan bırakılır; sirala() sonunda tek bir insertion geçişi hepsini neredeyse doğrusal sürede düzeltir. 5 ≤ M ≤ 25 için ≈ %10 kazanç.

Sınırı

Medyan-3 sıralı/ters sıralı girdiyi kurtarır; ama özel olarak kurulmuş kötü girdiler hâlâ n² yapabilir → çare introsort.

Hocadan “Asla n² ile karşı karşıya bırakmaz. Analizi biraz ağır…”

Hızlı Sıralamayı İyileştirmek · Kodu adım adım çalıştır

Kod İzleme: Medyan-3 Hazırlığı, quicksort3(a, 0, 8)

081325314952677684▲ L▲ R

R − L = 8 > M = 3 (izde M = 3 alındı) → parça büyük, bölümlenecek.

081325314652677984▲ L▲ R−1▲ R

Ortadaki öğe a[4] ile a[R−1] = a[7] takaslandı: aday pivot 9 artık a[7]'de.

081325314652677984▲ L▲ R−1▲ R

a[7] = 9 ≥ a[0] = 8 → takas yok.

041325314652677988▲ L▲ R−1▲ R

a[8] = 4 < a[0] = 8 → takas.

041325314652677889▲ L▲ R−1▲ R

a[8] = 8 < a[7] = 9 → takas.

041325314652677889▲ L▲ i▲ R

a[L] = 4 ≤ pivot 8 ≤ a[R] = 9. bol(a, 1, 7) → pivot konum 7; solu ≤ 8 ≤ sağı.

041325314652677889

Sağ a[8..8] hemen döner. Sol a[0..6]'da R − L = 6 > M: medyan-3 yinelenir, bol(a, 1, 5) → 3 ve dizi [1, 3, 2, 4, 6, 5, 7, 8, 9] olur. Kalan parçalar (R − L = 2 ≤ M) sıralanmadan döner; sirala() içindeki insertion bitirir.

void quicksort3(int a[], int L, int R) {
    if (R - L <= M) return;
    SWAP(a[(L + R) / 2], a[R - 1]);
    if (a[R-1] < a[L]) SWAP(a[R-1], a[L]); // a[L] <= a[R-1]
    if (a[R] < a[L]) SWAP(a[R], a[L]);     // a[L] <= a[R]
    if (a[R] < a[R-1]) SWAP(a[R], a[R-1]); // a[R-1] <= a[R]
    int i = bol(a, L + 1, R - 1);
    quicksort3(a, L, i - 1);
    quicksort3(a, i + 1, R);
}

Okuma Parçası

Okuma Parçası: Zorunlu Stajlar

Hocadan “Stajlar, öyle zannediyorum ki, imkânsızlıktan dolayı konmuş olmalı. Eskiden program yazılır, laboratuvara getirilip kartlara deldirilirdi. Bir kişiye günde bir, en çok iki kez derleme sırası gelirdi. Şimdi günde kaç kez derleme imkânı var? 50 mi, 100 mü?”
Hocadan “Şimdi ise durum farklı… Bizim öğrenciyi şunu yap, bunu yap diye ayak işlerinde kullanıyorlar. Klavyeyle toprak kazdırmak gibi… Bizim piyasayla işimiz yok ki! Bizim için önemli olan, bilim adamı yetiştirmek…”

C Kitaplığı ve Hibrit · Kodu adım adım çalıştır

Kod İzleme: qsort ve Karşılaştırma Fonksiyonu

037115224333449

Dizi hazır; sizeof a / sizeof a[0] = 20 / 4 = 5 öğe (int 4 bayt varsayımıyla).

037115224333449

qsort'a dizinin adresi, öğe sayısı, öğe boyu ve karşılaştırma fonksiyonunun adresi verilir. qsort öğe türünü bilmez; void * ile çalışır.

037115224333449

Örnek çağrı artan(&37, &15) → 1: pozitif = ilk öğe sonra gelmeli. (qsort'un hangi çiftleri hangi sırayla karşılaştıracağı standartta belirtilmez.)

037115224333449

Örnek çağrı artan(&15, &24) → -1: negatif = ilk öğe önce gelmeli. (qsort'un hangi çiftleri hangi sırayla karşılaştıracağı standartta belirtilmez.)

037115224333449

Örnek çağrı artan(&24, &24) → 0: sıfır = eşit (göreli sıra garanti değil). (qsort'un hangi çiftleri hangi sırayla karşılaştıracağı standartta belirtilmez.)

015124233337449

qsort döndü: dizi artan sırada.

015124233337449

printf: a[0] = 15.

015124233337449

printf: a[1] = 24.

015124233337449

printf: a[2] = 33.

015124233337449

printf: a[3] = 37.

015124233337449

printf: a[4] = 49.

int artan(const void *p, const void *q) {
    int x = *(const int *)p;
    int y = *(const int *)q;
    return (x > y) - (x < y);  // -1, 0, +1
}
int main(void) {
    int a[] = {37, 15, 24, 33, 49};
    size_t n = sizeof a / sizeof a[0];
    qsort(a, n, sizeof a[0], artan);
    for (size_t i = 0; i < n; i++)
        printf("%d ", a[i]);
    return 0;
}

C Kitaplığı ve Hibrit

qsort ile Kayıt (struct) Sıralamak

typedef struct { char ad[20]; int not_; } ogrenci;

int ada_gore(const void *p, const void *q) {
    const ogrenci *x = p, *y = q;
    return strcmp(x->ad, y->ad);        // alfabetik
}
int nota_gore_azalan(const void *p, const void *q) {
    const ogrenci *x = p, *y = q;
    return (y->not_ > x->not_) - (y->not_ < x->not_);
}
/* qsort(s, n, sizeof s[0], ada_gore); */
Sözleşme

Karşılaştırıcı negatif / sıfır / pozitif döndürür ve tutarlı olmalıdır (a < b ve b < c ise a < c).

Tuzak: taşma

return x - y; kısa ama INT_MIN ile 1 gibi değerlerde taşar; (x > y) - (x < y) güvenlidir.

Kararlılık yok

Standart, qsort'un kararlı olmasını ya da belirli bir algoritmayı kullanmasını istemez; eşitlerde sıra önemliyse ikincil anahtar eklenir.

C Kitaplığı ve Hibrit

Hibrit Sıralamalar: Algoritmaları Birleştirmek

Quick + eklemeli

Küçük parçalarda özyinelemenin maliyeti baskındır; M'den küçük parçalar eklemeliyle bitirilir (quicksort3 + sirala).

Introsort (Musser, 1997)

Hızlı sıralama ile başlar; özyineleme derinliği ≈ 2⌊lg n⌋'i aşarsa o parçaya heap sort uygular → en kötü durum O(n lg n). Küçük parçalar eklemeliyle. C++ std::sort gerçeklemelerinde yaygındır.

Timsort (Peters, 2002)

Birleştirme + eklemeli sıralama; veride hazır sıralı “koşuları” (run) bulup birleştirir. Kararlı; Python'un sort'u ve Java'nın nesne dizileri için Arrays.sort'u.

Ortak fikir

Büyük ölçekte iyi asimptotik davranış (n lg n) + küçük ölçekte düşük sabit (eklemeli) + en kötü durum sigortası.

Birleştirme Sıralaması

Birleştirme Sıralaması (Merge Sort)

  1. Liste 0 ya da 1 öğeliyse zaten sıralıdır.
  2. Değilse liste olabildiğince eşit iki parçaya ayrılır.
  3. Her parça merge sort özyinelemeli uygulanarak sıralanır.
  4. Kendi içinde sıralı iki alt liste tek sıralı listede birleştirilir.
  • 1945, John von Neumann; böl ve fethet.
  • Fikir 1: küçük listeyi sıralamak daha az adım ister.
  • Fikir 2: sıralı iki listeden sıralı liste elde etmek, sırasız ikisinden elde etmekten ucuzdur.
void mergesort(int a[], int b[], int L, int R) {
    if (R <= L) return;
    int m = (R + L) / 2;
    mergesort(a, b, L, m);
    mergesort(a, b, m + 1, R);
    merge(a, b, L, m, R);
}
void merge(int a[], int b[], int L, int m, int R) {
    int i, j, k;
    for (i = m + 1; i > L; i--) b[i - 1] = a[i - 1];
    for (j = m; j < R; j++) b[R + m - j] = a[j + 1];
    for (k = L; k <= R; k++)
        if (kucuk(b[j], b[i]))
            a[k] = b[j--];
        else
            a[k] = b[i++];
}

Yardımcı b[] dizisi parametre olarak geçirilir (a ile aynı boyda); tanımlanmadan kullanılamaz.

Birleştirme Sıralaması · Kodu adım adım çalıştır

Kod İzleme: merge(a, b, 0, 2, 6)

ab0315273445566100315273456▲ isol yarı → | ← sağ yarı (ters)

1. döngü: sol yarı a[0..2] olduğu gibi b[0..2]'ye kopyalandı; döngü i = L = 0 ile çıkar.

ab031527344556610031527310465564▲ i▲ jsol yarı → | ← sağ yarı (ters)

2. döngü: sağ yarı b[3..6]'ya ters sırada kopyalandı → b iki uçtan ortaya doğru artan (bitonik). Döngü j = R = 6 ile çıkar.

ab031527344556610▲ k031527310465564▲ i▲ jsol yarı → | ← sağ yarı (ters)

b[j] = 4 ≥ b[i] = 3 → a[0] = b[0]; i++.

ab031427344556610▲ k031527310465564▲ i▲ jsol yarı → | ← sağ yarı (ters)

b[j] = 4 < b[i] = 5 → a[1] = b[6]; j--.

ab031425344556610▲ k031527310465564▲ i▲ jsol yarı → | ← sağ yarı (ters)

b[j] = 5 ≥ b[i] = 5 → a[2] = b[1]; i++.

ab031425354556610▲ k031527310465564▲ i▲ jsol yarı → | ← sağ yarı (ters)

b[j] = 5 < b[i] = 7 → a[3] = b[5]; j--.

ab031425354656610▲ k031527310465564▲ i▲ jsol yarı → | ← sağ yarı (ters)

b[j] = 6 < b[i] = 7 → a[4] = b[4]; j--.

ab031425354657610▲ k031527310465564▲ i jsol yarı → | ← sağ yarı (ters)

b[j] = 10 ≥ b[i] = 7 → a[5] = b[2]; i++.

ab031425354657610▲ k031527310465564▲ i▲ jsol yarı → | ← sağ yarı (ters)

b[j] = 10 ≥ b[i] = 10 → a[6] = b[3]; i++.

ab031425354657610031527310465564▲ i▲ jsol yarı → | ← sağ yarı (ters)

Bitti: a = [3, 4, 5, 5, 6, 7, 10]. Sonda i = 4, j = 3: her iki indis ortada buluştu; sınır sınaması gerekmedi. Eşit 5'lerde soldaki (b[i]) önce alındı.

void merge(int a[], int b[], int L, int m, int R) {
    int i, j, k;
    for (i = m + 1; i > L; i--) b[i - 1] = a[i - 1];
    for (j = m; j < R; j++) b[R + m - j] = a[j + 1];
    for (k = L; k <= R; k++)
        if (kucuk(b[j], b[i]))
            a[k] = b[j--];
        else
            a[k] = b[i++];
}

Birleştirme Sıralaması tablosu · Kodu adım adım çalıştır

Kod İzleme: mergesort — 9 merge Çağrısı

089167259362423593624720828960

mergesort(a, b, 0, 9): önce sol yarı tamamen sıralanır; ilk merge ancak en derindeki (0, 1) çağrısında olur.

067189259362423593624720828960solsağ

merge #1: a[0..0] ile a[1..1] birleşti → a[0..1] sıralı.

059167289362423593624720828960solsağ

merge #2: a[0..1] ile a[2..2] birleşti → a[0..2] sıralı.

059167289323462593624720828960solsağ

merge #3: a[3..3] ile a[4..4] birleşti → a[3..4] sıralı.

023159262367489593624720828960solsağ

merge #4: a[0..2] ile a[3..4] birleşti → a[0..4] sıralı.

023159262367489524693720828960solsağ

merge #5: a[5..5] ile a[6..6] birleşti → a[5..6] sıralı.

023159262367489520624793828960solsağ

merge #6: a[5..6] ile a[7..7] birleşti → a[5..7] sıralı.

023159262367489520624793828960solsağ

merge #7: a[8..8] ile a[9..9] birleşti → a[8..9] sıralı.

023159262367489520624728860993solsağ

merge #8: a[5..7] ile a[8..9] birleşti → a[5..9] sıralı.

020123224328459560662767889993solsağ

merge #9: a[0..4] ile a[5..9] birleşti → a[0..9] sıralı.

void mergesort(int a[], int b[], int L, int R) {
    if (R <= L) return;
    int m = (R + L) / 2;
    mergesort(a, b, L, m);
    mergesort(a, b, m + 1, R);
    merge(a, b, L, m, R);
}

Birleştirme Sıralaması

Yığın Çerçevesi (Stack Activation Frame) Ağacı

0,9,4#90,4,2#40,2,1#20,1,0#10,01,12,23,4,3#33,34,45,9,7#85,7,6#65,6,5#55,56,67,78,9,8#78,89,9

Kutu: L, R, m · turuncu: merge çağrısı yapan çerçeve, #k: kaçıncı merge. Soldan derine iner, dönüşte birleştirir.

Hocadan “Veri Yapıları 1 dersinde ‘Bu mantığa merge diyoruz.’ diye hep söylemişimdir.”

Birleştirme Sıralaması

Soru: İki mergesort Satırının Yeri Değişirse?

Soru: Yordamdaki iki mergesort satırının yerleri değiştirilirse nasıl bir tablo oluşur, gözlemleyiniz.
Cevap: önce sağ yarılar birleşir; sonuç aynı, ara tablolar farklı
mergearalıkdizi
#1(8,9)89 67 59 62 23 93 24 20 28 60
#2(5,6)89 67 59 62 23 24 93 20 28 60
#3(5,7)89 67 59 62 23 20 24 93 28 60
#4(5,9)89 67 59 62 23 20 24 28 60 93
#5(3,4)89 67 59 23 62 20 24 28 60 93
#6(0,1)67 89 59 23 62 20 24 28 60 93
#7(0,2)59 67 89 23 62 20 24 28 60 93
#8(0,4)23 59 62 67 89 20 24 28 60 93
#9(0,9)20 23 24 28 59 60 62 67 89 93

Birleştirme Sıralaması

Merge Sort: Zaman ve Alan Karmaşıklığı

an = a⌈n/2⌉ + a⌊n/2⌋ + n,   a1 = 0

n = 2k: an = 2an/2 + n ⇒ an = Θ(n lg n)

  • 16 öğe 8 + 8, 17 öğe 8 + 9 bölünür; merge n adım sürer.
  • Çağrı sayısı Tn = 2Tn/2 + 1 ⇒ 2n − 1 (10 öğede 19 çağrı, 9'u merge yapar).
  • Dizi durumundan bağımsız: en iyi = ortalama = en kötü = n lg n.
  • Dezavantaj: O(n) ek bellek. Aşmak maliyeti artırır; ek bellek istenmiyorsa heap sort öne çıkar.
  • Sıralı parçaları sırayla birleştirdiği için disk üzerindeki dış sıralamanın temelidir.
Kararlılık

Klasik merge (eşitte soldakini alan) kararlıdır (quick ve heap sort kararlı değildir). Buradaki ters kopyalı (bitonik) merge kararlı değildir: [2a, 2b, 2c, 2d] → [2a, 2b, 2d, 2c] (Python ile denendi).

Hocadan “Mademki girdiyi ortadan ikiye bölmek marifet, işte bu da ortadan bölüyor. Ama öncekilerde pivot bir adımda doğru konumuna yerleşiyordu; bunda ise yerleşmiyor. Sıralı iki diziden ‘sıralı olma’ avantajını kullanarak yeni bir sıralı dizi elde ediyor.”

Birleştirme Sıralaması

Alfabetik Verileri Sıralamak: Bağlı Merge Sort

typedef struct {
    int anahtar;   /* + büyük veri alanları */
    int bag;       /* sonraki öğenin indisi, 0 = son */
} eleman;

int mergesort(eleman a[], int L, int R) {
    if (R <= L) return L;          /* tek öğeli liste */
    int m = (R + L) / 2;
    int sag = mergesort(a, m + 1, R);
    int sol = mergesort(a, L, m);
    return merge(a, sol, sag);     /* baş indisi */
}

merge() iki sıralı listeyi bag alanları üzerinden birleştirip baş indisini döndürür.

  • Büyük kayıtlar (ör. alfabetik bilgiler) kopyalanırsa zaman alıcıdır. Çözüm: bag alanıyla dizi içinde bağlı liste kurmak; yalnız indisler değişir.
  • Veriler a[1..n]'dedir; a[0] merge sırasında geçici baş (takas alanı). Başta tüm bag alanları 0.
  • mergesort, sıralanmış alt listenin baş indisini döndürür.
  • Sık hatalar: void yordamdan değer döndürmek; else dalında i2 yerine i1; a[0] yerine a[c].bag döndürmek.
  • C'de argümanların değerlendirilme sırası belirsizdir; burada sıra açıkça yazıldı.

Birleştirme Sıralaması · Kodu adım adım çalıştır

Kod İzleme: Bağlı merge (#9)

anahtarbag[0]–4[1]712[2]775[3]1220[4]381[5]1193[6]419[7]226[8]1150[9]438[10]137▲ i1▲ i2▲ sonuc

merge #9: birinci liste (baş 4) 38→71→77→119→122; ikinci liste (baş 10) 13→22→41→43→115. sonuc = 0 (a[0] geçici baş).

anahtarbag[0]–10[1]712[2]775[3]1220[4]381[5]1193[6]419[7]226[8]1150[9]438[10]137▲ i1▲ i2▲ sonuc

38 > 13 → a[0].bag = 10; sonuc = 10; i2 = 7.

anahtarbag[0]–10[1]712[2]775[3]1220[4]381[5]1193[6]419[7]226[8]1150[9]438[10]137▲ i1▲ i2▲ sonuc

38 > 22 → a[10].bag = 7; sonuc = 7; i2 = 6.

anahtarbag[0]–10[1]712[2]775[3]1220[4]381[5]1193[6]419[7]224[8]1150[9]438[10]137▲ i1▲ i2▲ sonuc

38 ≤ 41 → a[7].bag = 4; sonuc = 4; i1 = 1.

anahtarbag[0]–10[1]712[2]775[3]1220[4]386[5]1193[6]419[7]224[8]1150[9]438[10]137▲ i1▲ i2▲ sonuc

71 > 41 → a[4].bag = 6; sonuc = 6; i2 = 9.

anahtarbag[0]–10[1]712[2]775[3]1220[4]386[5]1193[6]419[7]224[8]1150[9]438[10]137▲ i1▲ i2▲ sonuc

71 > 43 → a[6].bag = 9; sonuc = 9; i2 = 8.

anahtarbag[0]–10[1]712[2]775[3]1220[4]386[5]1193[6]419[7]224[8]1150[9]431[10]137▲ i1▲ i2▲ sonuc

71 ≤ 115 → a[9].bag = 1; sonuc = 1; i1 = 2.

anahtarbag[0]–10[1]712[2]775[3]1220[4]386[5]1193[6]419[7]224[8]1150[9]431[10]137▲ i1▲ i2▲ sonuc

77 ≤ 115 → a[1].bag = 2; sonuc = 2; i1 = 5.

anahtarbag[0]–10[1]712[2]778[3]1220[4]386[5]1193[6]419[7]224[8]1150[9]431[10]137▲ i1▲ i2▲ sonuc

119 > 115 → a[2].bag = 8; sonuc = 8; i2 = 0.

anahtarbag[0]–10[1]712[2]778[3]1220[4]386[5]1193[6]419[7]224[8]1155[9]431[10]137▲ sonuc

i2 = 0 (ikinci liste bitti) → a[8].bag = i1 = 5; baş a[0].bag = 10 döner: 13→22→38→41→43→71→77→115→119→122.

int merge(eleman a[], int birinci, int ikinci) {
    int sonuc = 0;              // a[0]: geçici baş
    int i1 = birinci, i2 = ikinci;
    while (i1 && i2)
        if (a[i1].anahtar <= a[i2].anahtar) {
            a[sonuc].bag = i1; sonuc = i1;
            i1 = a[i1].bag;
        } else {
            a[sonuc].bag = i2; sonuc = i2;
            i2 = a[i2].bag;
        }
    a[sonuc].bag = i1 ? i1 : i2;
    return a[0].bag;
}

Birleştirme Sıralaması

Bağlı Merge Sort: bag Alanlarının Değişimi

#[1][2][3][4][5][6][7][8][9][10]dönüşL, R
anahtar71771223811941221154313
#10000000009109,10
#2000000600976,7
#3000008600976,8
#40000096087106,10
#5000509608744,5
#6200509608711,2
#7230509608711,3
#8250139608741,5
#92806394517101,10

Kalın: o merge'de değişen bag. Sıralı liste: 13 → 22 → 38 → 41 → 43 → 71 → 77 → 115 → 119 → 122.

Sınav Soruları

Sınav Sorusu

Aşağıdaki uyarlamayla eleman dizisi a[1..9] anahtara göre sıralanıyor. (i) Özyinelemeli çağrıların yığın çerçevelerini çizin; merge çağrılmışsa dönüş değerini yazın. (ii) İşletim tablosunu merge numaraları, dönüş değerleri ve her adımdaki bag alanlarıyla doldurun.

int mergesort(eleman a[], int L, int R) {
    int m;
    if (R <= L) return L;
    m = (R + L) / 2;
    return merge(a, mergesort(a, m + 1, R),
                    mergesort(a, L, m));
}

Anahtarlar: [1] ?, [2] ?, [3] 18, [4] −5, [5] 27, [6] 11, [7] ?, [8] 83, [9] 30. Soru kâğıdındaki “?” yerine burada 42, 7, 55 alındı. merge() bağlı merge sort’taki gibidir; basılı sürümün else dalında i1 yazıyor (yazım hatası; doğrusu i2).

Sınav Soruları

Sınav Sorusu (3. soru): Çözüm

Çözüm

Argümanlar soldan sağa değerlendirilirse (önce sağ yarı) çağrı sırası: (1,9) → (6,9) → (8,9) → (9,9) → (8,8) → (6,7) → (7,7) → (6,6) → (1,5) …

#[1][2][3][4][5][6][7][8][9]dön.L,R,m
#100000000898,9,8
#200000700866,7,6
#300000980766,9,7
#400050980744,5,4
#501050980721,2,1
#603150980721,3,2
#703521980741,5,3
#876529380141,9,5

Sonuç listesi (baş 4): -5 → 7 → 11 → 18 → 27 → 30 → 42 → 55 → 83. Tablo simülasyonla üretildi.

Heap Sort

Heap Sort

void heapify(int a[], int kok, int n) {
    int t = a[kok], j;
    for (j = 2 * kok; j <= n; j *= 2) {
        if (j < n && a[j] < a[j + 1]) j++; // büyük
        if (t >= a[j]) break;   // t buraya sığar
        a[j / 2] = a[j];        // çocuğu yukarı al
    }
    a[j / 2] = t;
}
void heapsort(int a[], int n) {     /* a[1..n] */
    int i;
    for (i = n / 2; i >= 1; i--)     /* heap kur */
        heapify(a, i, n);
    for (i = n - 1; i >= 1; i--) {   /* sırala */
        SWAP(a[1], a[i + 1]);    /* en büyüğü sona */
        heapify(a, 1, i);            /* heap'i düzelt */
    }
}
  • Karmaşıklık merge sort gibi O(n lg n); ama fazladan bellek kullanmaz (yerinde).
  • Dizi a[1..n] (0 kullanılmaz) tam ikili ağaç gibi düşünülür: a[i]'nin çocukları a[2i], a[2i+1].
  • heapify(a, kok, n): kok'ün sol ve sağ alt ağaçları zaten max-heap iken kök değerine uygun yeri arar.
  • Yapraklar atlanır: ⌊n/2⌋, ⌊n/2⌋−1, …, 1 düğümleri için sondan başa heapify → max-heap.
  • Sonra kökteki en büyük sona taşınır, kalan i öğe için heap düzeltilir.
  • for başlığında tanımlanan j ve i döngü dışında kullanılamaz (C99'da kapsam dışı).

Heap Sort · Kodu adım adım çalıştır

Kod İzleme: Sıralama Aşaması — SWAP + heapify(a, 1, i)

7716125934841951162671581951017726135944851961172681591105heap bölgesi a[1..10]

Girdi [26, 5, 77, 1, 61, 11, 59, 15, 48, 19] (Horowitz örneği), i = 5…1 için heapify ile aşağıdan yukarıya bu max-heap’e çevrildi (toplam 5 kaydırma). “Tamam hocam! Onu anladık da, bu dizi ne zaman sıralanacak?” — Şimdi: kökteki en büyük (77) her turda sona taşınır, kalan heap düzeltilir.

5161259348419511626715819771015261359448519611726815911077heap bölgesi a[1..9]

i = 9: SWAP(a[1], a[10]) → en büyük 77 sona; kökte 5 heap'i bozuyor.

6114825931541951162675819771016124835941551961172685911077heap bölgesi a[1..9] · sıralı kısım yeşil

heapify(a, 1, 9): 5 aşağı indi (a[8]); yeni kök 61 = kalanların en büyüğü.

1148259315419511626758619771011248359415519611726859611077heap bölgesi a[1..8] · sıralı kısım yeşil

i = 8: SWAP(a[1], a[9]) → en büyük 61 sona; kökte 1 heap'i bozuyor.

5914822631541951161758619771015924832641551961171859611077heap bölgesi a[1..8] · sıralı kısım yeşil

heapify(a, 1, 8): 1 aşağı indi (a[7]); yeni kök 59 = kalanların en büyüğü.

5148226315419511617598619771015248326415519611718599611077heap bölgesi a[1..7] · sıralı kısım yeşil

i = 7: SWAP(a[1], a[8]) → en büyük 59 sona; kökte 5 heap'i bozuyor.

4811922631545511617598619771014821932641555611718599611077heap bölgesi a[1..7] · sıralı kısım yeşil

heapify(a, 1, 7): 5 aşağı indi (a[5]); yeni kök 48 = kalanların en büyüğü.

1119226315455116487598619771011219326415556117488599611077heap bölgesi a[1..6] · sıralı kısım yeşil

i = 6: SWAP(a[1], a[7]) → en büyük 48 sona; kökte 1 heap'i bozuyor.

2611921131545516487598619771012621931141555617488599611077heap bölgesi a[1..6] · sıralı kısım yeşil

heapify(a, 1, 6): 1 aşağı indi (a[6]); yeni kök 26 = kalanların en büyüğü.

1119211315455266487598619771011219311415556267488599611077heap bölgesi a[1..5] · sıralı kısım yeşil

i = 5: SWAP(a[1], a[6]) → en büyük 26 sona; kökte 1 heap'i bozuyor.

1911521131455266487598619771011921531141556267488599611077heap bölgesi a[1..5] · sıralı kısım yeşil

heapify(a, 1, 5): 1 aşağı indi (a[4]); yeni kök 19 = kalanların en büyüğü.

5115211314195266487598619771015215311415196267488599611077heap bölgesi a[1..4] · sıralı kısım yeşil

i = 4: SWAP(a[1], a[5]) → en büyük 19 sona; kökte 5 heap'i bozuyor.

1515211314195266487598619771011525311415196267488599611077heap bölgesi a[1..4] · sıralı kısım yeşil

heapify(a, 1, 4): 5 aşağı indi (a[2]); yeni kök 15 = kalanların en büyüğü.

1152113154195266487598619771011253114155196267488599611077heap bölgesi a[1..3] · sıralı kısım yeşil

i = 3: SWAP(a[1], a[4]) → en büyük 15 sona; kökte 1 heap'i bozuyor.

1115213154195266487598619771011125314155196267488599611077heap bölgesi a[1..3] · sıralı kısım yeşil

heapify(a, 1, 3): 1 aşağı indi (a[3]); yeni kök 11 = kalanların en büyüğü.

1152113154195266487598619771011253114155196267488599611077heap bölgesi a[1..2] · sıralı kısım yeşil

i = 2: SWAP(a[1], a[3]) → en büyük 11 sona; kökte 1 heap'i bozuyor.

5112113154195266487598619771015213114155196267488599611077heap bölgesi a[1..2] · sıralı kısım yeşil

heapify(a, 1, 2): 1 aşağı indi (a[2]); yeni kök 5 = kalanların en büyüğü.

1152113154195266487598619771011253114155196267488599611077heap bölgesi a[1..1] · sıralı kısım yeşil

i = 1: SWAP(a[1], a[2]) → en büyük 5 sona; kökte kalan tek öğe (1) zaten bir heap'tir.

1152113154195266487598619771011253114155196267488599611077heap bölgesi a[1..1] · sıralı kısım yeşil

heapify(a, 1, 1): j = 2 > 1, döngüye girilmez; 1 yerinde kalır. Dizi sıralı: [1, 5, 11, 15, 19, 26, 48, 59, 61, 77].

void heapify(int a[], int kok, int n) {
    int t = a[kok], j;
    for (j = 2 * kok; j <= n; j *= 2) {
        if (j < n && a[j] < a[j + 1]) j++; // büyük
        if (t >= a[j]) break;   // t buraya sığar
        a[j / 2] = a[j];        // çocuğu yukarı al
    }
    a[j / 2] = t;
}
    for (i = n - 1; i >= 1; i--) {  /* sırala */
        SWAP(a[1], a[i + 1]);
        heapify(a, 1, i);
    }

Heap Sort

Heap Sort: Zaman Karmaşıklığı

  • Kaba analiz: heapify en çok lg n kaydırma yapar, n/2 kez çağrılır → heap kurma n lg n.
  • İnce analiz: k = düzey sayısı ≈ lg(n+1); i. düzeyde en çok 2i−1 düğüm, her biri en çok (k − i) kayar:

Σi=1k 2i−1(k − i) = 2k − k − 1 ≤ 2n = O(n)

  • Sağdaki Σ i·2i−1 terimi, geometrik seri S(y) = (yk+1 − 1)/(y − 1)'in türevinde y = 2 konarak bulunur.
  • Sıralama aşaması: n − 1 kez heapify(a, 1, i) → O(n lg n). Toplam O(n lg n), en kötü durumda da; ek bellek O(1); kararlı değil.
  • “≤ 2n” sınırı O(n) demektir, o(n) değil (2n ile sınırlı olmak küçük-o değildir).
kΣ 2i−1(k−i)2k−k−1n = 2k−1
2113
3447
4111115
6575763
10101310131023
Hocadan “Kruskal gerçeklenirken öğeler tek tek min-heap'e eklenerek heap oluşturulmuştu. O şekilde gerçekten heap oluşur. Ama öğelerin bulunduğu diziyi, onun yarısı kadar bir maliyetle min-heap hâline getirdik.”

Heap Sort

İleri Heapsort Algoritmaları ve Veriyi Tanımak

Ne zaman?

50.000'den az veri için heapsort makul bir algoritmadır (≈ 2n lg n karşılaştırma). Daha büyük veride karşılaştırma sayısını azaltan bottom-up heapsort ve kuramsal olarak daha da iyi sürümler vardır.

Bottom-up fikri

Kök boşluğu, t ile karşılaştırılmadan büyük çocuklar boyunca yaprağa kadar indirilir; sonra t yukarı doğru yerleştirilir. Düzey başına iki karşılaştırma yerine çoğunlukla bir.

Hocadan “Counting sort örneğinde olduğu gibi, sıralanacak verilere ait bazı özellikleri biliyorsak, ona göre bir yaklaşım seçebiliriz. Bu bağlamda shell sort ve package sort'un birlikte kullanımları var. Temel birkaç tanesi bilinmeli.”

Basamağa Göre Sıralama

Basamağa Göre Sıralama (Radix Sort)

  • Birden çok anahtara göre sıralama: ör. kişileri önce soyadına, soyadı aynı olanları ada göre.
  • Anahtarlar K0, K1, …, Kr−1: K0 en anlamlı (MSD), Kr−1 en az anlamlı (LSD). Örnekte soyadı en anlamlı.
  • LSD radix sort: önce en az anlamlı basamağa göre, sonra sırayla daha anlamlılara göre; en son MSD'ye göre sıralanır.
  • Her geçiş kararlı olmalı: önceki geçişin düzeni eşit basamaklarda korunur.
  • Sayıları birbiriyle karşılaştırmaz → karşılaştırmasız (non-comparison) sıralama.
#define MAX_DIGIT 3
#define RADIX 10
typedef struct oge *oge_gost;
struct oge {
    int deger[MAX_DIGIT];  /* 179 → {1, 7, 9} */
    oge_gost bag;
};

K0: yüzler (MSD) · K1: onlar · K2: birler (LSD). [0, 999] aralığı. typedef struct oge {…}; gibi tür adı verilmeyen bir typedef anlamsızdır.

Basamağa Göre Sıralama · Kodu adım adım çalıştır

Kod İzleme: radixsort — i = 2 (birler)

p179208306093859984055009271033on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]on[9]

Geçiş i = 2 (birler basamağı): 10 kova boşaltıldı.

p208306093859984055009271033on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]on[9]179

179: deger[2] = 9 → kova 9'un arkasına eklendi (geliş sırası korunur → kararlı).

p306093859984055009271033on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]208on[9]179

208: deger[2] = 8 → kova 8'in arkasına eklendi (geliş sırası korunur → kararlı).

p093859984055009271033on[0]on[1]on[2]on[3]on[4]on[5]on[6]306on[7]on[8]208on[9]179

306: deger[2] = 6 → kova 6'nın arkasına eklendi (geliş sırası korunur → kararlı).

p859984055009271033on[0]on[1]on[2]on[3]093on[4]on[5]on[6]306on[7]on[8]208on[9]179

093: deger[2] = 3 → kova 3'ün arkasına eklendi (geliş sırası korunur → kararlı).

p984055009271033on[0]on[1]on[2]on[3]093on[4]on[5]on[6]306on[7]on[8]208on[9]179859

859: deger[2] = 9 → kova 9'un arkasına eklendi (geliş sırası korunur → kararlı).

p055009271033on[0]on[1]on[2]on[3]093on[4]984on[5]on[6]306on[7]on[8]208on[9]179859

984: deger[2] = 4 → kova 4'ün arkasına eklendi (geliş sırası korunur → kararlı).

p009271033on[0]on[1]on[2]on[3]093on[4]984on[5]055on[6]306on[7]on[8]208on[9]179859

055: deger[2] = 5 → kova 5'in arkasına eklendi (geliş sırası korunur → kararlı).

p271033on[0]on[1]on[2]on[3]093on[4]984on[5]055on[6]306on[7]on[8]208on[9]179859009

009: deger[2] = 9 → kova 9'un arkasına eklendi (geliş sırası korunur → kararlı).

p033on[0]on[1]271on[2]on[3]093on[4]984on[5]055on[6]306on[7]on[8]208on[9]179859009

271: deger[2] = 1 → kova 1'in arkasına eklendi (geliş sırası korunur → kararlı).

pNULLon[0]on[1]271on[2]on[3]093033on[4]984on[5]055on[6]306on[7]on[8]208on[9]179859009

033: deger[2] = 3 → kova 3'ün arkasına eklendi (geliş sırası korunur → kararlı).

p271093033984055306208179859009on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]on[9]

Toplama: kovalar 9'dan 0'a gezilip her kova listenin önüne bağlanır → küçük kova önde: 271 093 033 984 055 306 208 179 859 009.

for (int i = MAX_DIGIT - 1; i >= 0; i--) {
    for (int j = 0; j < RADIX; j++)
        on[j] = arka[j] = NULL;
    for (; p; p = p->bag) {     // dağıt
        int b = p->deger[i];
        if (!on[b]) on[b] = p;
        else arka[b]->bag = p;
        arka[b] = p;
    }
    for (int j = RADIX - 1; j >= 0; j--) // topla
        if (on[j]) { arka[j]->bag = p; p = on[j]; }
}
return p;

Basamağa Göre Sıralama · Kodu adım adım çalıştır

Kod İzleme (devam): i = 1 ve i = 0

p271093033984055306208179859009on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]on[9]

Geçiş i = 1 (onlar basamağı): 10 kova boşaltıldı.

pNULLon[0]306208009on[1]on[2]on[3]033on[4]on[5]055859on[6]on[7]271179on[8]984on[9]093

Dağıtım: her öğe onlar basamağına göre kovasının arkasına.

p306208009033055859271179984093on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]on[9]

Toplama: kovalar 9'dan 0'a gezilip her kova listenin önüne bağlanır → küçük kova önde: 306 208 009 033 055 859 271 179 984 093.

p306208009033055859271179984093on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]on[9]

Geçiş i = 0 (yüzler basamağı): 10 kova boşaltıldı.

pNULLon[0]009033055093on[1]179on[2]208271on[3]306on[4]on[5]on[6]on[7]on[8]859on[9]984

Dağıtım: her öğe yüzler basamağına göre kovasının arkasına.

p009033055093179208271306859984on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]on[9]

Toplama: kovalar 9'dan 0'a gezilip her kova listenin önüne bağlanır → küçük kova önde: 009 033 055 093 179 208 271 306 859 984.

p009033055093179208271306859984on[0]on[1]on[2]on[3]on[4]on[5]on[6]on[7]on[8]on[9]

Üç geçiş sonunda liste sıralı; baş işaretçisi döndürülür.

for (int i = MAX_DIGIT - 1; i >= 0; i--) {
    for (int j = 0; j < RADIX; j++)
        on[j] = arka[j] = NULL;
    for (; p; p = p->bag) {     // dağıt
        int b = p->deger[i];
        if (!on[b]) on[b] = p;
        else arka[b]->bag = p;
        arka[b] = p;
    }
    for (int j = RADIX - 1; j >= 0; j--) // topla
        if (on[j]) { arka[j]->bag = p; p = on[j]; }
}
return p;

Basamağa Göre Sıralama

Basamağa Göre Sıralama: Analiz

O(MAX_DIGIT · (RADIX + n + RADIX)) = O(MAX_DIGIT · (RADIX + n))

  • Örnekte O(3 · (10 + n)): doğrusal görünür ama saymalı sıralama gibi özel bir durumdur: basamak sayısı ve taban sabit kabul edilmiştir.
  • Veriler (a) pozitif, (b) tam sayı, (c) küçük aralıkta olmalı. Aralık büyürse yine sıralar ama karmaşıklık hızla artar.
  • Ek bellek: 2·RADIX işaretçi (liste yerinde yeniden bağlanır).
Hocadan “Sadece tam sayıları sıralayabilmesi bir dezavantajdır. Kayan noktalı sayı türleri için uyarlanabilir, ama bu uyarlamayı yapana kadar diğer algoritmalar sıralamış olur.”
geçişliste
başlangıç179 208 306 093 859 984 055 009 271 033
i = 2271 093 033 984 055 306 208 179 859 009
i = 1306 208 009 033 055 859 271 179 984 093
i = 0009 033 055 093 179 208 271 306 859 984

Sınav Soruları

Sınav Sorusu: Değiştirilmiş Radix

Bir öğrenci, yordamın önce en anlamlı basamağı ele alması gerektiğini düşünüp dış döngüyü for (i = 0; i <= MAX_DIGIT-1; i++), toplama döngüsünü for (j = 0; j <= RADIX-1; j++) yapmıştır. Aşağıdaki liste için yordamı işletin, her adımda listeyi yazın ve sonucu yorumlayın.

p → 346 180 008 930 023 451 670 098 203 007

Çözüm
geçişliste
i = 0 (yüzler)930 670 451 346 203 180 008 023 098 007
i = 1 (onlar)098 180 670 451 346 930 023 203 008 007
i = 2 (birler)098 008 007 346 023 203 451 180 670 930

1. değişiklik: birler basamağı en anlamlı basamak gibi davranır; sayılar ters okunursa sıralıdır (098, 008, 007 → 890, 800, 700 …). 2. değişiklik: kovalar ters sırada öne bağlandığı için sıra büyükten küçüğe döner. (Tablo simülasyonla üretildi.)

Laboratuvar · C kodu adım adım

Laboratuvar: İleri Sıralamalar

Özet

Karmaşıklık Özeti ve Karşılaştırma

algoritmaen iyiortalamaen kötüek bellekkararlıyerinde
Seçimlin²n²n²O(1)hayırevet
Eklemelinn²n²O(1)evetevet
Kabarcık (erken dur.)nn²n²O(1)evetevet
Shell (Knuth)n lg n—n3/2O(1)hayırevet
Hızlı (quick)n lg n1,39 n lg nn²O(lg n) yığın*hayırevet
Birleştirme (merge)n lg nn lg nn lg nO(n)evet**hayır
Heap sortn lg nn lg nn lg nO(1)hayırevet
Saymalı (counting)n + kn + kn + kO(n + k)evethayır
Basamak (radix, d basamak)d(n + r)d(n + r)d(n + r)O(r)***evet—

* Küçük parça önce işlenirse; aksi hâlde en kötü O(n). ** Klasik merge ile. *** Bağlı liste üzerinde. Karşılaştırmalı sıralamada alt sınır ⌈lg n!⌉ ≈ n lg n − 1,44 n.

Özet

Hangi Durumda Hangisi?

Genel amaçlı, bellekte

Quick sort (medyan-3 + küçük parçada eklemeli) ya da kitaplık qsort.

En kötü durum garantisi

Heap sort (yerinde) ya da introsort.

Kararlılık / bağlı liste / disk

Merge sort: kararlı, sıralı parçaları birleştirir, dış sıralamanın temeli.

Büyük kayıtlar

Kayıtlar değil indisler/bağlar sıralanır (bağlı merge sort).

Sabit uzunluklu tam sayı/anahtar

Radix: d geçiş × O(n + r).

Küçük aralık

Saymalı sıralama.

Özet

Sık Yapılan Hatalar

1bol()'da j döngüsünde j == L sınamasını unutmak: pivot en küçükse dizi başından taşar.
2Sıralı girdide pivot hep a[R]: derinlik n olur, yığın taşar. Çare: medyan-3 ya da rastgele pivot.
3Özyinelemesiz sürümde parçaları rastgele sırada itmek: yığın O(n)'e büyür.
4merge sort'ta b[]'yi her çağrıda malloc'lamak (yavaş) ya da hiç ayırmamak.
5heapify'da 1 tabanlı indisle 0 tabanlı diziyi karıştırmak: çocuklar 2i, 2i+1 ancak a[1..n]'de doğru.
6qsort karşılaştırıcısında return x - y;: taşma; ya da tutarsız (geçişsiz) karşılaştırıcı.

Özet

Alıştırmalar

  1. [5, 9, 1, 7, 3] için bol(a, 0, 4) sonucu diziyi ve dönüş değerini yazın.
  2. Quick sort n = 8 sıralı dizide kaç bol() çağrısı yapar? Özyineleme derinliği nedir?
  3. Merge sort 16 öğe için kaç merge çağrısı yapar? En çok kaç karşılaştırma?
  4. [4, 10, 3, 5, 1] (a[1..5]) için heap kurma sonrası diziyi yazın.
  5. Radix sort 170, 45, 75, 90, 802, 24, 2, 66 listesinde birler geçişinden sonraki sırayı yazın.
  6. Heap sort neden kararlı değildir? Küçük bir örnek verin.
Cevapları göster
  1. v = 3. i = 0'da (5), j = 2'de (1) durur → takas: [1, 9, 5, 7, 3]; sonra i = 1 (9), j = 0 (1): çaprazlandı → a[1] ↔ a[4]: [1, 3, 5, 7, 9], dönüş 1.
  2. Pivot hep en büyük: boyut 8, 7, …, 2 için 7 bol() çağrısı; özyineleme derinliği n − 1 = 7.
  3. 15 merge çağrısı (n − 1). Bu merge her öğe için tam bir karşılaştırma yapar: lg 16 = 4 düzey × 16 = 64.
  4. [10, 5, 3, 4, 1].
  5. 170 090 802 002 024 045 075 066 (kararlı dağıtım).
  6. [2a, 2b, 1]: kök 2a sona taşınır → [1, 2b, 2a] gibi eşitlerin sırası değişebilir; uzak takaslar sırayı korumaz.

Özet

Bölüm Özeti

Quick sort

Pivotu yerine koyar ve problemi böler: en iyi n lg n, ortalama 1,39 n lg n, en kötü n².

İyileştirmeler

Küçük parça önce (yığın lg n), medyan-3, küçük parçada eklemeli; qsort ve introsort.

Merge sort

Her durumda n lg n, O(n) ek bellek; bağlı sürüm büyük kayıtları kopyalamaz.

Heap sort

Heap kurma O(n), sıralama O(n lg n); yerinde ama kararsız.

Radix sort

Kararlı kovalarla LSD'den MSD'ye: O(d(n + r)); pozitif tam sayılar ve küçük aralık.

Analiz araçları

Yineleme bağıntıları, iterasyon, çıkarma hilesi, seri türevi — dersin bütün analizleri burada buluştu.

Özet

Kaynaklar

  • R. Sedgewick, Algorithms in C, Kısım 1–4, Bölüm 7–10 (quicksort, mergesort, priority queues/heapsort, radix sorting).
  • E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, Bölüm 7.
  • T. H. Cormen vd., Introduction to Algorithms, Bölüm 6–8; D. Musser, “Introspective Sorting and Selection Algorithms”, 1997.
  • ISO/IEC 9899 (C standardı), qsort tanımı (§7.22.5.2).

Son

Bölüm 20: İleri Sıralama Algoritmaları

“Ustada kalırsa bu öksüz yapı, / Onu sürdürmeyen çırak utansın!” — N. F. K.

Dersin sonu · Ders sayfası
1 / 1 C ile Veri Yapıları