Bölüm 19: Temel Sıralama Algoritmaları

C ile Veri Yapıları ve Algoritmalar
KararlılıkSeçimliEklemeliOrtalama durumKabarcıkSaymalıShell · KovaAlt sınır
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Kavramlar

Kayıt, anahtar, iç/dış sıralama, kararlılık ve yerinde sıralamayı ayırt etmek.

Üç temel algoritma

Seçimli, eklemeli ve kabarcık sıralamasını C'de yazıp adım adım izlemek.

Analiz

Karşılaştırma/değiştirme sayısını; en iyi, en kötü ve ortalama durumu hesaplamak.

Karşılaştırmasız sıralama

Saymalı (counting) ve kova (bucket) sıralamanın ne zaman O(n) olduğunu açıklamak.

Shell sıralama

Aralıklı eklemeli sıralamanın neden eklemeliden hızlı olduğunu göstermek.

Alt sınır

Karar ağacıyla karşılaştırmalı sıralamanın Ω(n lg n) sınırını kanıtlamak.

Sıralama Kavramları

Sıralama: Kayıt, Anahtar, İç/Dış

  • Alanların (fields) oluşturduğu kayıtlar (records) bellekte tutulunca liste, bellek dışında tutulunca kütük (file) adını alır.
  • Kaydı biricik (unique) olarak tanıtan alan(lar) anahtardır (key); sıralama anahtara göre yapılır.
  • İç sıralama (internal): verinin tamamı bellekte. Dış sıralama (external): disk/teyp gibi yardımcı bellek kullanılır; disk erişim sayısı belirleyicidir.
kayıt (record) = alanlar (fields)no (anahtar)adsoyadort.1042AyşeYılmaz3.10977CanDemir2.81310EceKaya3.6bellekte: liste · bellek dışında: kütük (file)

Anahtar: öğrenci no

Hocadan “Biz, iç sıralamayla ilgileneceğiz. Bu arada dış sıralama için de bir temel vermiş olacağız.”

Sıralama Kavramları

Kararlılık (Stability) ve Yerinde (In-place) Sıralama

girdi3a1b3c2d1ekararlı1b1e2d3a3cseçimli1b1e2d3c3a3a, 3c'nin önündeydi; seçimli sıralama sonunda 3c öne geçti
Kararlı (stable)

Eşit anahtarlı kayıtların göreli sırası korunur. Önce ada, sonra soyada göre kararlı sıralanırsa soyadı aynı olanlar ad sırasında kalır (basamak sıralamanın temeli).

Yerinde (in-place)

Girdinin dışında yalnızca O(1) (bazı tanımlarda O(lg n)) ek bellek kullanır. Seçimli, eklemeli, kabarcık yerindedir; saymalı sıralama değildir (c ve b dizileri).

Sıralama Kavramları

Ortak Altyapı: kucuk ve SWAP

#define N 15
#define kucuk(A, B) ((A) < (B))
#define SWAP(A, B) do { int t_ = (A); (A) = (B); (B) = t_; } while (0)
int main(void) {
    int *a = malloc(N * sizeof *a);
    if (a == NULL) return 1;
    for (int i = 0; i < N; i++)
        a[i] = rand() % 50;
    selection(a, 0, N - 1);   /* sıralama yordamına çağrı */
    for (int i = 0; i < N; i++) printf("%d ", a[i]);
    free(a);
    return 0;
}
Makro tuzağı

Makroyu {int t = A; A=B; B=t;} biçiminde yazmak if (…) SWAP(…); else içinde derleme hatası verir; do { … } while (0) bu tuzağı kapatır. if (kucuk(A, B)) SWAP(A, B); kalıbı A < B ise takas eder (karşılaştır-takas).

Seçimli Sıralama

Seçimli Sıralama (Selection Sort)

void selection(int a[], int L, int R) {
    for (int i = L; i < R; i++) {
        int min = i;
        for (int j = i + 1; j <= R; j++)
            if (kucuk(a[j], a[min]))
                min = j;
        SWAP(a[i], a[min]);
    }
}
  1. Listedeki en küçük değerli öğe bulunur.
  2. Bu öğe ilk konumdaki öğeyle yer değiştirir.
  3. Aynı adımlar 2. öğeden başlayarak kalan alt liste için yinelenir.
Değişmez

Her dış tur sonunda a[L..i] sıralı ve dizinin en küçük öğelerini içerir.

Neden sona kadar?

En küçüğü seçmek için kalan alt liste kesinlikle sonuna kadar taranmalıdır; veri sıralı olsa bile.

Seçimli Sıralama · Kodu adım adım çalıştır

Kod İzleme: selection(a, 0, 4)

029110214337413▲ i min

Tur i = 0: kalan alt liste a[0..4]. Başlangıçta en küçüğün indisi min = 0 (a[0] = 29).

029110214337413▲ i▲ j min

a[1] = 10 < a[0] = 29 → yeni en küçük: min = 1.

029110214337413▲ i▲ j▲ min

a[2] = 14 ≥ a[1] = 10 → min değişmez.

029110214337413▲ i▲ j▲ min

a[3] = 37 ≥ a[1] = 10 → min değişmez.

029110214337413▲ i▲ j▲ min

a[4] = 13 ≥ a[1] = 10 → min değişmez.

010129214337413▲ i

SWAP(a[0], a[1]): 10 yerine oturdu; a[0..0] artık sıralı.

010129214337413▲ i min

Tur i = 1: kalan alt liste a[1..4]. Başlangıçta en küçüğün indisi min = 1 (a[1] = 29).

010129214337413▲ i▲ j min

a[2] = 14 < a[1] = 29 → yeni en küçük: min = 2.

010129214337413▲ i▲ j▲ min

a[3] = 37 ≥ a[2] = 14 → min değişmez.

010129214337413▲ i▲ j min

a[4] = 13 < a[2] = 14 → yeni en küçük: min = 4.

010113214337429▲ i

SWAP(a[1], a[4]): 13 yerine oturdu; a[0..1] artık sıralı.

010113214337429▲ i min

Tur i = 2: kalan alt liste a[2..4]. Başlangıçta en küçüğün indisi min = 2 (a[2] = 14).

010113214337429▲ i min▲ j

a[3] = 37 ≥ a[2] = 14 → min değişmez.

010113214337429▲ i min▲ j

a[4] = 29 ≥ a[2] = 14 → min değişmez.

010113214337429▲ i

min = i olduğundan takas kendisiyle yapılır (değişiklik yok); a[0..2] sıralı.

010113214337429▲ i min

Tur i = 3: kalan alt liste a[3..4]. Başlangıçta en küçüğün indisi min = 3 (a[3] = 37).

010113214337429▲ i▲ j min

a[4] = 29 < a[3] = 37 → yeni en küçük: min = 4.

010113214329437▲ i

SWAP(a[3], a[4]): 29 yerine oturdu; a[0..3] artık sıralı.

void selection(int a[], int L, int R) {
    for (int i = L; i < R; i++) {
        int min = i;
        for (int j = i + 1; j <= R; j++)
            if (kucuk(a[j], a[min]))
                min = j;
        SWAP(a[i], a[min]);
    }
}

Seçimli Sıralama

Seçimli Sıralamanın Analizi

(n−1) + (n−2) + … + 2 + 1 = n(n−1)/2 ≈ n²/2

  • Karşılaştırma sayısı girdiden bağımsızdır: en iyi = ortalama = en kötü ≈ n²/2.
  • Değiştirme (takas) sayısı n − 1: son öğeye gelindiğinde o zaten doğru yerdedir.
  • Karmaşıklık O(n²); ek bellek O(1) (yerinde).
  • Uzak takaslar yüzünden kararlı değildir (önceki 3a/3c örneği).
nkarşılaştırmatakas
5104
10459
100495099
1000499500999
Takas pahalıysa (büyük kayıtlar, flash bellek yazımı) az takas yapması seçimliyi değerli kılar.

Eklemeli Sıralama

Eklemeli Sıralama (Insertion Sort): İki Sürüm

void insertion(int a[], int L, int R) {
    for (int i = L + 1; i <= R; i++) {
        int v = a[i];      // yerleştirilecek
        int j = i - 1;     // a[L..i-1] sıralı
        while (j >= L && a[j] > v) {
            a[j + 1] = a[j]; // sağa kaydır
            j--;
        }
        a[j + 1] = v;
    }
}
void insertion1(int a[], int L, int R) {
    for (int i = R; i > L; i--) // bekçi
        if (a[i] < a[i-1]) SWAP(a[i], a[i-1]);
    for (int i = L + 2; i <= R; i++) {
        int v = a[i], j = i;
        while (kucuk(v, a[j - 1])) {
            a[j] = a[j - 1];
            j--;
        }
        a[j] = v;
    }
}
  • Her turda girdiden bir öğe alınır ve sıralı ön ek içinde uygun konuma eklenir (iskambil kâğıdı dizer gibi).
  • Sağdaki insertion1, soldakinin ilerlemiş hâlidir: önce en küçüğü a[L]'ye taşır; bu bekçi (sentinel) sayesinde iç döngüde j > L sınaması gerekmez.
  • Koşulu j >= 0 yazmak yanlıştır; alt dizi L > 0'dan başlarsa sınırı aşar, doğrusu j >= L.

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

Kod İzleme: insertion(a, 0, 4)

071293541▲ i▲ jv2

i = 1: v = a[1] = 2 bir kenara alındı; a[0..0] sıralı, v için yer aranacak.

017293541▲ iv2

a[0] = 7 > v = 2 → 7 bir sağa kaydı (a[1]); j = -1.

0217293541v–

j = −1, dizinin başına gelindi → döngü biter; a[0] = 2. Sıralı ön ek artık a[0..1].

021723541▲ i▲ jv9

i = 2: v = a[2] = 9 bir kenara alındı; a[0..1] sıralı, v için yer aranacak.

0217293541v–

a[1] = 7 ≤ v → döngü biter; a[2] = 9. Sıralı ön ek artık a[0..2].

021729341▲ i▲ jv5

i = 3: v = a[3] = 5 bir kenara alındı; a[0..2] sıralı, v için yer aranacak.

021723941▲ i▲ jv5

a[2] = 9 > v = 5 → 9 bir sağa kaydı (a[3]); j = 1.

021273941▲ i▲ jv5

a[1] = 7 > v = 5 → 7 bir sağa kaydı (a[2]); j = 0.

0215273941v–

a[0] = 2 ≤ v → döngü biter; a[1] = 5. Sıralı ön ek artık a[0..3].

021527394▲ i▲ jv1

i = 4: v = a[4] = 1 bir kenara alındı; a[0..3] sıralı, v için yer aranacak.

021527349▲ i▲ jv1

a[3] = 9 > v = 1 → 9 bir sağa kaydı (a[4]); j = 2.

021523749▲ i▲ jv1

a[2] = 7 > v = 1 → 7 bir sağa kaydı (a[3]); j = 1.

021253749▲ i▲ jv1

a[1] = 5 > v = 1 → 5 bir sağa kaydı (a[2]); j = 0.

012253749▲ iv1

a[0] = 2 > v = 1 → 2 bir sağa kaydı (a[1]); j = -1.

0112253749v–

j = −1, dizinin başına gelindi → döngü biter; a[0] = 1. Sıralı ön ek artık a[0..4].

void insertion(int a[], int L, int R) {
    for (int i = L + 1; i <= R; i++) {
        int v = a[i];      // yerleştirilecek
        int j = i - 1;     // a[L..i-1] sıralı
        while (j >= L && a[j] > v) {
            a[j + 1] = a[j]; // sağa kaydır
            j--;
        }
        a[j + 1] = v;
    }
}

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

Kod İzleme: insertion1(a, 0, 3) (bekçili)

04132132▲ iv–

a[3] = 2 ≥ a[2] = 1 → takas yok. En küçük öğe sola doğru ilerliyor.

04112332▲ iv–

a[2] = 1 < a[1] = 3 → takas. En küçük öğe sola doğru ilerliyor.

01142332▲ iv–

a[1] = 1 < a[0] = 4 → takas. a[0] = 1 artık en küçük: bekçi hazır.

0114232▲ i▲ jv3

i = 2: v = 3. a[0..1] sıralı.

0112432▲ i▲ jv3

v = 3 < a[1] = 4 → kaydır; j = 1. Sınır sınaması yok: bekçi a[0] durduracak.

01132432v–

a[0] = 1 ≤ v → dur; a[1] = 3.

0113243▲ i▲ jv2

i = 3: v = 2. a[0..2] sıralı.

0113234▲ i▲ jv2

v = 2 < a[2] = 4 → kaydır; j = 2. Sınır sınaması yok: bekçi a[0] durduracak.

0112334▲ i▲ jv2

v = 2 < a[1] = 3 → kaydır; j = 1. Sınır sınaması yok: bekçi a[0] durduracak.

01122334v–

a[0] = 1 ≤ v → dur; a[1] = 2.

void insertion1(int a[], int L, int R) {
    for (int i = R; i > L; i--) // bekçi
        if (a[i] < a[i-1]) SWAP(a[i], a[i-1]);
    for (int i = L + 2; i <= R; i++) {
        int v = a[i], j = i;
        while (kucuk(v, a[j - 1])) {
            a[j] = a[j - 1];
            j--;
        }
        a[j] = v;
    }
}

Eklemeli Sıralama

Eklemeli Sıralama: En İyi ve En Kötü Durum

en iyi: sıralı12345her i için 1 karşılaştırmaen kötü: ters sıralı54321i. öğe i kez kaydırılır
En iyi: Ω(n)

Girdi sıralıysa seçilen öğe yalnızca sıralı listenin en sağındaki değerle karşılaştırılır, daha büyük görülünce hemen yerine konur: toplam n − 1 karşılaştırma.

En kötü: O(n²)

Girdi ters sıralıysa her öğe sıralı listenin tümüyle karşılaştırılıp en sola eklenir; sıralı dizi her turda 1 sağa kayar: 1 + 2 + … + (n−1) = n(n−1)/2.

Eklemeli sıralama kararlıdır (a[j] > v katı eşitsizlik: eşit öğe atlanmaz) ve yerindedir. Sıralıya yakın veride neredeyse doğrusaldır.

Eklemeli Sıralama

Ortalama Durum Analizi: j = 3 İçin Olası Durumlar

0316283100316283101 karşılaştırma

Durum 1: 8 > 10 yanlış → 1 karşılaştırma.

03162837031627382 karşılaştırma

Durum 2: 8 > 7 doğru, 6 > 7 yanlış → 2 karşılaştırma.

03162835031526383 karşılaştırma

Durum 3: 8 > 5 doğru, 6 > 5 doğru, 3 > 5 yanlış → 3 karşılaştırma.

03162832021326383 karşılaştırmaj = −1 olunca j ≥ 0 yanlış: bu sınama sayılmaz

Durum 4: 8 > 2 doğru, 6 > 2 doğru, 3 > 2 doğru; j = −1 olunca j >= 0 yanlış, a[j] > v hiç sınanmaz — sondan bir önceki durumla aynı sayıda → 3 karşılaştırma.

j öğesi j + 1 konumdan birine yerleşir: durumların 1'inde 1, 1'inde 2, 2'sinde 3 karşılaştırma. Olasılıklar eşit; yalnız sonuncu “3 karşılaştırma” sonucunu iki kez verir. Ortalama: (1 + 2 + 3 + 3)/4 = 2,25.

Eklemeli Sıralama

Ortalama Durum: Beklenen Karşılaştırma Sayısı

E(Cj) = (1/(j+1))·[2j + Σk=1j−1 k] = (j+2)/2 − 1/(j+1)

E(Y) = Σj=1N−1 E(Cj) = (N² + 3N)/4 − HN ≈ N²/4

  • Cj: j. öğeyi yerleştirirken yapılan karşılaştırma sayısı, bir rastlantı değişkeni; E(Cj) beklenen değeri.
  • HN = 1 + 1/2 + … + 1/N harmonik sayı: HN = ln N + O(1).
  • Ortalamada ≈ N²/4 karşılaştırma ve ≈ N²/4 yarı değişim (takas değil kaydırma) → O(n²).
NN!tüm permütasyonlarformülN²/4
362,6672,6672,25
4244,9174,9174,00
51207,7177,7176,25
672011,05011,0509,00
7504014,90714,90712,25

Tablo: N! dizilişin hepsi Python ile sayıldı; formülle birebir aynı.

Hocadan “En iyi ve en kötü durumu biliyoruz; ikisinin ortalamasını alınca ortalama durumu analiz etmiş olur muyuz? Hayır! Ortalama durum analizinde, örnekler uzayındaki durumların tamamını gözlememiz gerekir.”

Kabarcık Sıralaması

Kabarcık Sıralaması (Bubble Sort)

void bubble(int a[], int L, int R) {
    int i, j;
    for (i = L; i < R; i++)
        for (j = R; j > i; j--)
            if (a[j] < a[j-1]) SWAP(a[j], a[j-1]);
}
Hocadan “Dizi sıralıysa, hemen çıkacak şekilde daha dikkatli kodlanabilir. Bir döngü boyunca hiç takas yapılmamışsa sonraki döngüye girmeye gerek yoktur.”
0712293541

Başlangıç: [7, 2, 9, 5, 1]. Her tur sağdan sola komşuları karşılaştırır.

0117223945

Tur i = 0: en küçük kalan değer 1 kabarcık gibi a[0]'a yükseldi → [1, 7, 2, 9, 5].

0112273549

Tur i = 1: en küçük kalan değer 2 kabarcık gibi a[1]'e yükseldi → [1, 2, 7, 5, 9].

0112253749

Tur i = 2: en küçük kalan değer 5 kabarcık gibi a[2]'ye yükseldi → [1, 2, 5, 7, 9].

0112253749

Tur i = 3: takas yok; kalanların en küçüğü 7 zaten a[3]'te → [1, 2, 5, 7, 9].

Kabarcık Sıralaması · Kodu adım adım çalıştır

Kod İzleme: Erken Durmalı bubble2(a, 0, 4)

0211233544▲ i

Tur i = 0 başlıyor; takas bayrağı sıfırlandı.

0211233445▲ j−1▲ j

a[4] < a[3] → takas: [2, 1, 3, 4, 5]; takas = 1.

0211233445▲ j−1▲ j

a[3] = 4 ≥ a[2] = 3 → takas yok.

0211233445▲ j−1▲ j

a[2] = 3 ≥ a[1] = 1 → takas yok.

0112233445▲ j−1▲ j

a[1] < a[0] → takas: [1, 2, 3, 4, 5]; takas = 1.

0112233445

takas = 1 → dizi henüz sıralı sayılamaz, sonraki tura geç.

0112233445▲ i

Tur i = 1 başlıyor; takas bayrağı sıfırlandı.

0112233445▲ j−1▲ j

a[4] = 5 ≥ a[3] = 4 → takas yok.

0112233445▲ j−1▲ j

a[3] = 4 ≥ a[2] = 3 → takas yok.

0112233445▲ j−1▲ j

a[2] = 3 ≥ a[1] = 2 → takas yok.

0112233445

Bu turda hiç takas olmadı → dizi sıralı; erken çıkış. 2 tur boşuna yapılmadı.

void bubble2(int a[], int L, int R) {
    for (int i = L; i < R; i++) {
        int takas = 0;      // bu turda takas oldu mu?
        for (int j = R; j > i; j--)
            if (kucuk(a[j], a[j - 1])) {
                SWAP(a[j], a[j - 1]);
                takas = 1;
            }
        if (!takas) return; // sıralı: erken çık
    }
}

Kabarcık Sıralaması

Kabarcık Sıralamasının Analizi

  • Karşılaştırma: her durumda n(n−1)/2 ≈ n²/2 (erken durma yoksa).
  • Değiştirme: en kötü durumda (ters sıralı) n²/2; ortalamada ters çift (inversion) sayısı kadar, yani ≈ n²/4.
  • Karmaşıklık O(n²); erken durmalı sürüm sıralı girdide O(n).
  • Yalnız komşular takaslanır ve eşitler takaslanmaz → kararlı, yerinde.
nkarş.takas (en kötü)takas (ort.)
4663
510105
615157,5
7212110,5

Ortalama sütunu: n! dizilişin tamamı sayıldı. Bazı kaynaklar (ör. Sedgewick) ortalama için de n²/2 değiştirme verir; sayım ortalamanın n²/4 olduğunu gösteriyor.

Kabarcık Sıralaması

Ters Çift (Inversion): Ortak Ölçü

0712293541a =7 ters çift

Ters çift: i < j iken a[i] > a[j] olan çift. [7, 2, 9, 5, 1] → (7,2), (7,5), (7,1), (2,1), (9,5), (9,1), (5,1)

  • Komşu takası (kabarcık) ya da bir konum kaydırma (eklemeli) tam olarak bir ters çifti giderir.
  • Bu yüzden ikisinde de takas/kaydırma sayısı = ters çift sayısı: bu dizide 7 (izlerde de 7 kaydırma vardı).
  • Ters sıralı dizide n(n−1)/2 ters çift; rastgele dizide ortalama n(n−1)/4 (her çift ½ olasılıkla ters).
  • Komşu öğelerle çalışan her algoritma ortalamada Ω(n²): uzak takas yapan Shell ve hızlı sıralama bu duvarı aşar.

Saymalı Sıralama

Saymalı Sıralama (Counting Sort)

int counting_sort(const int a[], int b[],
                  int r, int k) {
    int *c = malloc((k + 1) * sizeof *c);
    if (c == NULL) return -1;
    for (int i = 0; i <= k; i++) c[i] = 0;  // L1
    for (int j = 0; j <= r; j++) c[a[j]]++; // L2
    for (int i = 1; i <= k; i++)            // L3
        c[i] += c[i - 1];
    for (int j = r; j >= 0; j--) {          // L4
        b[c[a[j]] - 1] = a[j];
        c[a[j]]--;
    }
    free(c);
    return 0;
}
adanlamı
a[]girdi dizisi
b[]sıralı çıktı dizisi
c[]yardımcı sayaç dizisi (k+1 öğe)
ra[0..r] sıralanır (r+1 öğe)
kdeğerler 0…k aralığında
  • 1954'te Harold H. Seward. Aralığı bilinen tam sayıları sıralar.
  • Değerler birbiriyle karşılaştırılmaz → non-comparison sort (kova sıralama da bu sınıftadır).
  • int c[K] derleme zamanında sabit boyut ister; burada malloc + kontrol + free.

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

Kod İzleme: counting_sort — Sayma (L1–L3)

a03102639445361075b01234567c00102030405060708090100

L1: c[0..10] sıfırlandı. Her değerden kaç tane olduğu burada sayılacak.

a03102639445361075▲ jb01234567c00102031405060708090100

L2: a[0] = 3 → c[3]++ = 1.

a03102639445361075▲ jb01234567c01102031405060708090100

L2: a[1] = 0 → c[0]++ = 1.

a03102639445361075▲ jb01234567c01102031405061708090100

L2: a[2] = 6 → c[6]++ = 1.

a03102639445361075▲ jb01234567c01102031405061708091100

L2: a[3] = 9 → c[9]++ = 1.

a03102639445361075▲ jb01234567c01102031415061708091100

L2: a[4] = 4 → c[4]++ = 1.

a03102639445361075▲ jb01234567c01102032415061708091100

L2: a[5] = 3 → c[3]++ = 2. (İkinci 3!)

a03102639445361075▲ jb01234567c01102032415061708091101

L2: a[6] = 10 → c[10]++ = 1.

a03102639445361075▲ jb01234567c01102032415161708091101

L2: a[7] = 5 → c[5]++ = 1.

a03102639445361075b01234567c01112032415161708091101▲ i

L3: c[1] += c[0] → 1: “≤ 1 olan 1 öğe var”.

a03102639445361075b01234567c01112132415161708091101▲ i

L3: c[2] += c[1] → 1: “≤ 2 olan 1 öğe var”.

a03102639445361075b01234567c01112133415161708091101▲ i

L3: c[3] += c[2] → 3: “≤ 3 olan 3 öğe var”. 3 gelirse 2. konuma.

a03102639445361075b01234567c01112133445161708091101▲ i

L3: c[4] += c[3] → 4: “≤ 4 olan 4 öğe var”. 4 gelirse 3. konuma.

a03102639445361075b01234567c01112133445561708091101▲ i

L3: c[5] += c[4] → 5: “≤ 5 olan 5 öğe var”. 5 gelirse 4. konuma.

a03102639445361075b01234567c01112133445566708091101▲ i

L3: c[6] += c[5] → 6: “≤ 6 olan 6 öğe var”. 6 gelirse 5. konuma.

a03102639445361075b01234567c01112133445566768091101▲ i

L3: c[7] += c[6] → 6: “≤ 7 olan 6 öğe var”.

a03102639445361075b01234567c01112133445566768691101▲ i

L3: c[8] += c[7] → 6: “≤ 8 olan 6 öğe var”.

a03102639445361075b01234567c01112133445566768697101▲ i

L3: c[9] += c[8] → 7: “≤ 9 olan 7 öğe var”. 9 gelirse 6. konuma.

a03102639445361075b01234567c01112133445566768697108▲ i

L3: c[10] += c[9] → 8: “≤ 10 olan 8 öğe var”. 10 gelirse 7. konuma.

    for (int i = 0; i <= k; i++) c[i] = 0;  // L1
    for (int j = 0; j <= r; j++) c[a[j]]++; // L2
    for (int i = 1; i <= k; i++)            // L3
        c[i] += c[i - 1];

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

Kod İzleme (devam): L4 ile b Dizisine Yerleştirme

a03102639445361075b01234567c01112133445566768697108

L3 sonu: c[x] = “x'ten küçük ya da eşit öğe sayısı”. Ör. c[9] = 7 → 9 gelirse 7 − 1 = 6. konuma.

a03102639445361075▲ jb012345567c01112133445466768697108

L4: a[7] = 5, c[5] = 5 → b[4] = 5; c[5]-- = 4.

a03102639445361075▲ jb01234556710c01112133445466768697107

L4: a[6] = 10, c[10] = 8 → b[7] = 10; c[10]-- = 7.

a03102639445361075▲ jb012334556710c01112132445466768697107

L4: a[5] = 3, c[3] = 3 → b[2] = 3; c[3]-- = 2 (aynı değerli bir sonraki öğe bir sola yerleşecek).

a03102639445361075▲ jb0123344556710c01112132435466768697107

L4: a[4] = 4, c[4] = 4 → b[3] = 4; c[4]-- = 3.

a03102639445361075▲ jb01233445569710c01112132435466768696107

L4: a[3] = 9, c[9] = 7 → b[6] = 9; c[9]-- = 6.

a03102639445361075▲ jb012334455669710c01112132435465768696107

L4: a[2] = 6, c[6] = 6 → b[5] = 6; c[6]-- = 5.

a03102639445361075▲ jb0012334455669710c00112132435465768696107

L4: a[1] = 0, c[0] = 1 → b[0] = 0; c[0]-- = 0.

a03102639445361075▲ jb00132334455669710c00112131435465768696107

L4: a[0] = 3, c[3] = 2 → b[1] = 3; c[3]-- = 1 (aynı değerli bir sonraki öğe bir sola yerleşecek).

a03102639445361075b00132334455669710c00112131435465768696107

Sonuç b = [0, 3, 3, 4, 5, 6, 9, 10]. Sağdan sola gezildiği için iki 3'ün göreli sırası korundu: kararlı.

    for (int j = r; j >= 0; j--) {          // L4
        b[c[a[j]] - 1] = a[j];
        c[a[j]]--;
    }

Saymalı Sıralama

Saymalı Sıralama: Analiz ve Sınırlar

döngüişmaliyet
L1c'yi sıfırlaO(k)
L2sayO(n)
L3kümülatif toplamO(k)
L4yerleştirO(n)
  • Toplam O(n + k); k = O(n) ise O(n). Ek bellek O(n + k): yerinde değil.
  • L4 sağdan sola ilerlediği için kararlı — basamağa göre sıralamada (radix sort) bu özellik şart.
Hocadan “Doğrusal zamanda sıralama yapmak için veri aralığını bilmeliyiz. 10 değer sıralanacaksa aralık 0 ile 1000 arasında değişiyorsa n³'lük algoritma olur… Dolayısıyla aralığın çok küçük olması lazımdır.”
Hocadan “İlk geçişte frekansları sayıyoruz, ikinci geçişte sıralıyoruz. Hızlı transpoz alma tek bellekle olur demiştik ya, işte bu o…”

Shell ve Kova Sıralama · Kodu adım adım çalıştır

Kod İzleme: shell_sort(a, 8)

h = 4035133242310414519627744

Aralık h = 4: birbirinden 4 uzak öğeler ayrı ayrı eklemeli sıralanır.

h = 4 · a[4] zinciri: 0, 4014133242310435519627744▲ i

i = 4: v = 14; 1 kez 4 geri kaydırıldı, a[0] = 14.

h = 4 · a[5] zinciri: 1, 5014119242310435533627744▲ i

i = 5: v = 19; 1 kez 4 geri kaydırıldı, a[1] = 19.

h = 4 · a[6] zinciri: 2, 6014119227310435533642744▲ i

i = 6: v = 27; 1 kez 4 geri kaydırıldı, a[2] = 27.

h = 4 · a[7] zinciri: 3, 7014119227310435533642744▲ i

i = 7: v = 44; a[3] ≤ 44, yerinde kaldı.

h = 2014119227310435533642744

Aralık h = 2: birbirinden 2 uzak öğeler ayrı ayrı eklemeli sıralanır.

h = 2 · a[2] zinciri: 0, 2014119227310435533642744▲ i

i = 2: v = 27; a[0] ≤ 27, yerinde kaldı.

h = 2 · a[3] zinciri: 1, 3014110227319435533642744▲ i

i = 3: v = 10; 1 kez 2 geri kaydırıldı, a[1] = 10.

h = 2 · a[4] zinciri: 0, 2, 4014110227319435533642744▲ i

i = 4: v = 35; a[2] ≤ 35, yerinde kaldı.

h = 2 · a[5] zinciri: 1, 3, 5014110227319435533642744▲ i

i = 5: v = 33; a[3] ≤ 33, yerinde kaldı.

h = 2 · a[6] zinciri: 0, 2, 4, 6014110227319435533642744▲ i

i = 6: v = 42; a[4] ≤ 42, yerinde kaldı.

h = 2 · a[7] zinciri: 1, 3, 5, 7014110227319435533642744▲ i

i = 7: v = 44; a[5] ≤ 44, yerinde kaldı.

h = 1014110227319435533642744

Son tur h = 1: sıradan eklemeli sıralama — ama dizi artık neredeyse sıralı.

h = 1 · a[1] zinciri: 0, 1010114227319435533642744▲ i

i = 1: v = 10; 1 kez 1 geri kaydırıldı, a[0] = 10.

h = 1 · a[2] zinciri: 0, 1, 2010114227319435533642744▲ i

i = 2: v = 27; a[1] ≤ 27, yerinde kaldı.

h = 1 · a[3] zinciri: 0, 1, 2, 3010114219327435533642744▲ i

i = 3: v = 19; 1 kez 1 geri kaydırıldı, a[2] = 19.

h = 1 · a[4] zinciri: 0, 1, 2, 3, 4010114219327435533642744▲ i

i = 4: v = 35; a[3] ≤ 35, yerinde kaldı.

h = 1 · a[5] zinciri: 0, 1, 2, 3, 4, 5010114219327433535642744▲ i

i = 5: v = 33; 1 kez 1 geri kaydırıldı, a[4] = 33.

h = 1 · a[6] zinciri: 0, 1, 2, 3, 4, 5, 6010114219327433535642744▲ i

i = 6: v = 42; a[5] ≤ 42, yerinde kaldı.

h = 1 · a[7] zinciri: 0, 1, 2, 3, 4, 5, 6, 7010114219327433535642744▲ i

i = 7: v = 44; a[6] ≤ 44, yerinde kaldı.

void shell_sort(int a[], int n) {
    for (int h = n / 2; h > 0; h /= 2) // aralık
        for (int i = h; i < n; i++) {
            int v = a[i], j = i;
            while (j >= h && a[j - h] > v) {
                a[j] = a[j - h];    // h geri kaydır
                j -= h;
            }
            a[j] = v;
        }
}

Shell ve Kova Sıralama

Shell Sıralama (Shell Sort, 1959)

  • Eklemeli sıralamanın zayıflığı: öğe bir seferde yalnız bir konum ilerler; ters sıralıda O(n²).
  • Shell: önce uzak öğeler (aralık h) sıralanır, h küçültülür; son tur h = 1 ile biter, bu yüzden sonuç her zaman doğru.
  • Karmaşıklık aralık dizisine bağlıdır: h = n/2, n/4, … (Shell) için en kötü O(n²); Knuth'un 1, 4, 13, 40, … dizisi için O(n3/2).
  • Yerinde; ama uzak takaslar yüzünden kararlı değildir.
Birlikte kullanım

Veriye ait özellikler biliniyorsa shell sort ve paket (kova) sıralama birlikte kullanılabilir.

Uygulamada

Kısa kod + iyi pratik hız: gömülü sistemlerde ve küçük/orta dizilerde tercih edilir.

Shell ve Kova Sıralama

Kova Sıralama (Bucket Sort)

girdi.78.17.39.26.72.94.21.12.23.680123456789kova

Girdi [0, 1) aralığında 10 sayı; 10 kova, her kova 0,1 genişliğinde.

girdi.78.17.39.26.72.94.21.12.23.6801.17.122.26.21.233.39456.687.78.7289.94kova

Dağıt: x değeri ⌊10·x⌋ numaralı kovaya eklenir (ör. .78 → 7, .17 → 1). O(n).

girdi.78.17.39.26.72.94.21.12.23.6801.12.172.21.23.263.39456.687.72.7889.94kova

Her kova kendi içinde (eklemeli sıralamayla) sıralanır: kovalar küçük olduğundan ucuz.

girdi.78.17.39.26.72.94.21.12.23.6801.12.172.21.23.263.39456.687.72.7889.94çıktı.12.17.21.23.26.39.68.72.78.94kova

Kovalar sırayla birleştirilir → sıralı çıktı. Düzgün dağılımda ortalama O(n).

Girdi aralıkta düzgün dağılmışsa kova başına beklenen öğe sayısı O(1) → ortalama O(n) (CLRS). Tüm öğeler aynı kovaya düşerse en kötü durum, kova içi sıralamanın maliyetidir (eklemeliyle O(n²)). Ek bellek O(n + kova sayısı).

Shell ve Kova Sıralama

Kova Sıralama: C ile Gerçekleme

typedef struct kdugum { double x; struct kdugum *bag; } kdugum;

int bucket_sort(double a[], int n) {        /* 0 <= a[i] < 1 */
    kdugum **kova = calloc(n, sizeof *kova);
    kdugum *havuz = malloc(n * sizeof *havuz);
    if (!kova || !havuz) { free(kova); free(havuz); return -1; }
    for (int i = 0; i < n; i++) {           /* dağıt + sıralı ekle */
        kdugum **p = &kova[(int)(a[i] * n)];
        while (*p && (*p)->x <= a[i]) p = &(*p)->bag;
        havuz[i].x = a[i]; havuz[i].bag = *p; *p = &havuz[i];
    }
    int k = 0;                              /* kovaları birleştir */
    for (int b = 0; b < n; b++)
        for (kdugum *q = kova[b]; q; q = q->bag) a[k++] = q->x;
    free(kova); free(havuz);
    return 0;
}
  • n kova; a[i] değeri ⌊a[i]·n⌋ numaralı bağlı listeye sıralı eklenir (kova içi eklemeli sıralama).
  • <= sayesinde eşitler sona eklenir → kararlı. Düğümler tek bir havuz dizisinden alınır: n ayrı malloc yerine iki ayırma, tek free çifti.

Optimal Sıralama Zamanı

Optimal Sıralama Zamanı: Karar Ağacı

evethayırevethayırevethayırevethayırevethayıra<bb<ca<ca<b<ca<cb<a<cb<ca<c<bc<a<bb<c<ac<b<a

Sınamalar elips, kararlar (sıralamalar) dikdörtgen. n = 3 için yaprak sayısı 3! = 6.

evethayırevethayırevethayırevethayırevethayıra<bb<ca<ca<b<ca<cb<a<cb<ca<c<bc<a<bb<c<ac<b<a

Girdi a = 5, b = 8, c = 2. Kök: a < b? 5 < 8 → evet.

evethayırevethayırevethayırevethayırevethayıra<bb<ca<ca<b<ca<cb<a<cb<ca<c<bc<a<bb<c<ac<b<a

b < c? 8 < 2 → hayır: c, b'den önce gelmeli.

evethayırevethayırevethayırevethayırevethayıra<bb<ca<ca<b<ca<cb<a<cb<ca<c<bc<a<bb<c<ac<b<a

a < c? 5 < 2 → hayır.

evethayırevethayırevethayırevethayırevethayıra<bb<ca<ca<b<ca<cb<a<cb<ca<c<bc<a<bb<c<ac<b<a

Yaprak c < a < b → 2, 5, 8. Bu girdi için 3 karşılaştırma = yolun uzunluğu.

Karşılaştırmalı her sıralama algoritması bir karar ağacıdır: her iç düğüm bir kucuk() çağrısı, her yaprak girdinin bir dizilişi. En kötü durumdaki karşılaştırma sayısı = ağacın yüksekliği.

Optimal Sıralama Zamanı

Teorem: Karşılaştırmalı Sıralamanın Alt Sınırı

Teorem. n farklı öğeyi sıralayan her karar ağacının derinliği (düzey sayısı) en az ⌈log₂(n!)⌉ + 1'dir; yani en kötü durumda en az ⌈lg(n!)⌉ karşılaştırma gerekir.
  • Yaprak sayısı en az n! (her diziliş bir yaprak).
  • Yüksekliği h olan ikili ağacın en çok 2h yaprağı vardır: 2h ≥ n! ⇒ h ≥ lg(n!).
  • Stirling: ⌈lg(n!)⌉ = n lg n − n lg e + Θ(lg n) ≈ n lg n − 1,4427 n.
  • Sonuç: karşılaştırmalı sıralama Ω(n lg n); saymalı/kova/basamak sıralama karşılaştırma yapmadığı için bu sınıra takılmaz.
n⌈lg n!⌉n lg n − 1,44nn(n−1)/2
330,43
574,410
102218,845
206257,6190
100525520,14950
Hocadan “Bundan sonra yazacağımız algoritmaları karşılaştırmak için bir mihenk taşı verdik.”

Laboratuvar · C kodu adım adım

Laboratuvar: Temel Sıralamalar

Özet

Aynı Girdide Sayım (n = 1000)

algoritmasıralı: karş. / taşımaters: karş. / taşımarastgele: karş. / taşıma
Seçimli499.500 / 999499.500 / 999499.500 / 999
Eklemeli999 / 0499.500 / 499.500247.876 / 246.884
Kabarcık (erken dur.)999 / 0499.500 / 499.500499.035 / 246.884
Shell (n/2, n/4, …)8.006 / 011.716 / 4.70014.760 / 7.254

Değerler Python simülasyonuyla sayıldı (rastgele girdi: tohum 19). Taşıma: seçimlide takas, eklemeli/Shell'de kaydırma, kabarcıkta takas.

Seçimli

Girdiye kör: her durumda 499.500 karşılaştırma, 999 takas.

Eklemeli

Sıralıda 999 karşılaştırma; rastgelede ≈ n²/4; tersinde n²/2.

Shell

Rastgele girdide eklemeliden kat kat az iş: uzak kaydırmalar ters çiftleri hızla yok eder.

Özet

Karmaşıklık Özeti

algoritmaen iyiortalamaen kötüek bellekkararlıyerinde
Seçimlin²/2n²/2n²/2O(1)hayırevet
EklemeliO(n)≈ n²/4n²/2O(1)evetevet
Kabarcık (erken dur.)O(n)≈ n²/2 karş.n²/2O(1)evetevet
Shell (Knuth h)O(n lg n)—O(n3/2)O(1)hayırevet
SaymalıO(n+k)O(n+k)O(n+k)O(n+k)evethayır
Kova (düzgün dağ.)O(n)O(n)O(n²)O(n)evet*hayır

Seçimli/eklemeli/kabarcık satırlarında sayılar karşılaştırma sayısıdır. * Kova içinde kararlı bir sıralama kullanılırsa. Shell ortalaması aralık dizisine bağlı ve kapalı biçimde bilinmiyor. Karşılaştırmalı sıralamada alt sınır ⌈lg n!⌉ ≈ n lg n.

Özet

Hangi Durumda Hangisi?

Takas/yazma pahalı

Seçimli: tam n − 1 takas.

Küçük ya da neredeyse sıralı

Eklemeli: kararlı, yerinde, sıralıya yakında O(n); hızlı sıralamanın küçük parçalarında da kullanılır.

Öğretim / sıralılık testi

Kabarcık + erken durma: bir turda sıralı olduğunu anlar; üretimde nadiren.

Küçük tam sayı aralığı

Saymalı: O(n + k), kararlı; k ≫ n ise bellek ve zaman boşa gider.

Orta boy, ek bellek yok

Shell: birkaç satırlık kodla O(n²)'den belirgin hızlı.

[0,1) düzgün dağılım

Kova: ortalama O(n).

Özet

Sık Yapılan Hatalar

1Seçimlide en küçük değeri tutmak: takas için indisi (min) gerekir.
2Eklemelide v = a[i]'yi kaydırmadan önce saklamamak: ilk kaydırma a[i]'yi ezer.
3j >= 0 yazmak: alt dizi a[L..R] ise sınır j >= L.
4if (kucuk(A, B)) SWAP(A, B) kalıbında yönü karıştırmak: A < B ise takas edilir.
5Saymalı sıralamada k'yı unutmak: negatif ya da k'dan büyük değer c[] dışına yazar.
6Saymalı sıralamada L4'ü soldan sağa yazmak: sıralar ama kararlılık bozulur.

Özet

Alıştırmalar

  1. [5, 1, 4, 2, 8] için seçimli sıralamada her tur sonundaki diziyi yazın. Kaç takas yapıldı?
  2. Eklemeli sıralama [1, 2, 3, …, n] ve [n, …, 2, 1] için kaç karşılaştırma yapar?
  3. bubble2 [1, 2, 3, 5, 4] girdisinde kaç tur çalışır?
  4. a = [2, 5, 3, 0, 2, 3, 0, 3], k = 5 için counting sort'un L3 sonrası c dizisini yazın.
  5. 3 öğe için karar ağacının en az yüksekliği nedir? 4 öğe için?
  6. Seçimli sıralamanın kararsız olduğunu gösteren en küçük örneği bulun.
Cevapları göster
  1. [1,5,4,2,8] → [1,2,4,5,8] → [1,2,4,5,8] → [1,2,4,5,8]; 4 tur, ilk ikisi gerçek takas (kalan ikisi kendisiyle).
  2. Sıralıda n − 1; ters sıralıda n(n−1)/2.
  3. 2 tur: ilk turda 5 ile 4 takaslanır, ikinci turda takas yok → erken çıkış.
  4. Sayaçlar [2,0,2,3,0,1] → kümülatif [2,2,4,7,7,8].
  5. ⌈lg 3!⌉ = ⌈lg 6⌉ = 3; ⌈lg 4!⌉ = ⌈lg 24⌉ = 5.
  6. [2a, 2b, 1]: ilk turda 1 ile 2a takaslanır → [1, 2b, 2a].

Özet

Bölüm Özeti

Üç O(n²) algoritma

Aynı sınıfta ama farklı karakter: seçimli az takas, eklemeli sıralıya yakında hızlı, kabarcık erken durabilir.

Ortalama durum

Tüm n! dizilişin beklenen değeri: eklemeli ≈ n²/4 karşılaştırma. En iyi ile en kötünün ortalaması değildir!

Karşılaştırmasız

Saymalı O(n+k) ve kova ortalama O(n): veri hakkında bilgi (aralık, dağılım) hız kazandırır.

Kavramlar

Kararlılık (eşitlerin sırası) ve yerindelik (ek bellek) algoritma seçiminde belirleyicidir.

Shell

Aralıklı eklemeli sıralama; son tur h = 1 doğruluğu garanti eder.

Alt sınır

Karşılaştırmalı sıralama ⌈lg n!⌉ ≈ n lg n karşılaştırmanın altına inemez → ileri sıralama algoritmalarının mihenk taşı.

Özet

Kaynaklar ve Devam

  • E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, 2. bs., Bölüm 7 (Sorting).
  • R. Sedgewick, Algorithms in C, Kısım 1–4, Bölüm 6 (Elementary Sorting Methods): makro yaklaşımı, shell sort.
  • T. H. Cormen vd., Introduction to Algorithms (CLRS), Bölüm 2 (insertion), 8 (alt sınır, counting, bucket).

Son

Bölüm 19: Temel Sıralama Algoritmaları
Sonraki bölüm: Bölüm 20: İleri Sıralama Algoritmaları · Ders sayfası
1 / 1 C ile Veri Yapıları