İçeriğe geç
Educora
Üniversite25 dk52 / 59

Verimli sıralama algoritmaları

Birleştirmeli sıralamayı ve hızlı sıralamayı adım adım öğren, karşılaştırmalı sıralamanın Ω(n log n) alt sınırını kanıtla ve bu sınırı aşan sayarak sıralamayı tanı.

Kendini test et
Bu derste öğreneceklerin
  • Birleştirmeli sıralamayı ve birleştirme adımını izlemek, Θ(n log n) olduğunu göstermek
  • Hızlı sıralamada bölümlemeyi yapmak ve en kötü durumunu açıklamak
  • Karar ağacıyla Ω(n log n) sınırını gerekçelendirmek ve sayarak sıralamayı uygulamak

Kabarcık sıralaması bir milyon kaydı sıralamak için yaklaşık 5 · 10¹¹ karşılaştırma yapar; saniyede 10⁸ karşılaştırmayla bu bir saati aşar. Birleştirmeli sıralama aynı işi yaklaşık 2 · 10⁷ karşılaştırmayla, saniyenin bir kesrinde yapar. Fark bilgisayarda değil, fikirdedir: böl ve yönet. Bu derste iki temel n log n algoritmasını, karşılaştırmalı sıralamanın daha hızlı olamayacağının ispatını ve bu yasağı nasıl “atlatacağımızı” öğreneceğiz.

Birleştirmeli sıralama (merge sort)

  1. Böl: diziyi ortadan iki yarıya ayır.
  2. Çöz: her yarıyı özyinelemeli sırala (tek elemanlı dizi zaten sıralıdır).
  3. Birleştir: iki sıralı yarının baştaki elemanlarını karşılaştırıp küçüğü sonuca aktararak birleştir; doğrusal süre O(n).
Örnek 1: birleştirmeli sıralamayı izlemek

[38, 27, 43, 3, 9, 82, 10] dizisini birleştirmeli sıralamayla sırala. Son birleştirme kaç karşılaştırma yapar?

Çözümü göster
Bölme: [38, 27, 43] | [3, 9, 82, 10] → [38] | [27, 43] ve [3, 9] | [82, 10].
Küçük birleştirmeler: [27, 43]; [38] + [27, 43] → [27, 38, 43]; [3, 9]; [10, 82]; [3, 9] + [10, 82] → [3, 9, 10, 82].
Son birleştirme [27, 38, 43] + [3, 9, 10, 82]:
27 ↔ 3 → 3; 27 ↔ 9 → 9; 27 ↔ 10 → 10; 27 ↔ 82 → 27; 38 ↔ 82 → 38; 43 ↔ 82 → 43; sol bitti → 82 eklenir.
Sonuç: [3, 9, 10, 27, 38, 43, 82], son birleştirmede 6 karşılaştırma (en fazla n − 1 = 6).
T(n) = 2T(n/2) + cn = Θ(n log n)T(n) = 2T(n/2) + cn = Θ(n log n)
burada:
  • 2T(n/2)2T(n/2)iki yarının özyinelemeli sıralanması
  • cnbirleştirme (en fazla n − 1 karşılaştırma)

log₂ n düzeyin her birinde birleştirmeler toplam ≤ n karşılaştırma yapar → ≤ n log₂ n. Bu en kötü, ortalama ve en iyi durumda aynıdır. Ek bellek O(n)'dir; algoritma kararlıdır (eşit elemanlar sırasını korur).

Python
comparisons = 0

def merge(left, right):
    global comparisons
    out, i, j = [], 0, 0
    while i < len(left) and j < len(right):
        comparisons += 1
        if left[i] <= right[j]:
            out.append(left[i])
            i += 1
        else:
            out.append(right[j])
            j += 1
    return out + left[i:] + right[j:]

def merge_sort(a):
    if len(a) <= 1:
        return a
    mid = len(a) // 2
    return merge(merge_sort(a[:mid]), merge_sort(a[mid:]))

print(merge_sort([38, 27, 43, 3, 9, 82, 10]))
comparisons = 0
merge_sort([(i * 37) % 1024 for i in range(1024)])
print('n = 1024:', comparisons, 'comparisons; n log2 n =', 1024 * 10)
▸ Beklenen çıktı
[3, 9, 10, 27, 38, 43, 82]
n = 1024: 8161 comparisons; n log2 n = 10240
<= koşulu kararlılığı sağlar: eşitlikte sol yarının elemanı önce gelir. 1024 elemanlı karışık bir dizi için 8161 karşılaştırma n log₂ n = 10.240 sınırının altında kalır.

Hızlı sıralama (quicksort)

Hızlı sıralama işi ters sırayla yapar: önce bir pivot (dayanak elemanı) seçilir ve dizi bölümlenir; pivottan küçük ya da eşit olanlar sola, büyükler sağa gider. Pivot son yerine oturur, ardından iki parça özyinelemeli sıralanır; birleştirme gerekmez. Yerinde çalışır (ek dizi gerekmez) ve uygulamada çok hızlıdır ama kararlı değildir.

Örnek 2: Lomuto bölümlemesi

[7, 2, 9, 4, 3, 8, 5] dizisini son eleman (5) pivot olacak biçimde bölümle. i, “≤ pivot” kısmının sınırıdır; başta i = −1.

Çözümü göster
j = 0: 7 > 5 → geç.
j = 1: 2 ≤ 5 → i = 0, a[0] ↔ a[1] → [2, 7, 9, 4, 3, 8, 5]
j = 2: 9 > 5 → geç.
j = 3: 4 ≤ 5 → i = 1, a[1] ↔ a[3] → [2, 4, 9, 7, 3, 8, 5]
j = 4: 3 ≤ 5 → i = 2, a[2] ↔ a[4] → [2, 4, 3, 7, 9, 8, 5]
j = 5: 8 > 5 → geç.
Sonunda pivot a[i + 1] = a[3] ile değiştirilir → [2, 4, 3, 5, 9, 8, 7].
Pivot 5, indeks 3'te, son yerindedir; solda {2, 4, 3}, sağda {9, 8, 7}. 6 = n − 1 karşılaştırma.
en kötü durum: T(n) = T(n − 1) + (n − 1) = n(n − 1)/2; ortalama: ≈ 2n ln n ≈ 1,39 n log₂ nen kötü durum: T(n) = T(n − 1) + (n − 1) = n(n − 1)/2; ortalama: ≈ 2n ln n ≈ 1,39 n log₂ n
burada:
  • T(n − 1)pivot hep en küçük ya da en büyük olduğunda kalan parça
  • n − 1bir bölümlemenin karşılaştırma sayısı

Dengeli bölümlemelerde hızlı sıralama birleştirmeli sıralama gibi davranır: T(n) = 2T(n/2) + n → Θ(n log n); her seferinde en kötü bölümlemede ise Θ(n²).

Python
comparisons = 0

def partition(a, lo, hi):
    global comparisons
    pivot, i = a[hi], lo - 1
    for j in range(lo, hi):
        comparisons += 1
        if a[j] <= pivot:
            i += 1
            a[i], a[j] = a[j], a[i]
    a[i + 1], a[hi] = a[hi], a[i + 1]
    return i + 1

def quicksort(a, lo, hi):
    if lo < hi:
        p = partition(a, lo, hi)
        quicksort(a, lo, p - 1)
        quicksort(a, p + 1, hi)

a = [7, 2, 9, 4, 3, 8, 5]
print(partition(a, 0, 6), a)
for name, data in [('mixed', [(i * 37) % 200 for i in range(200)]), ('sorted', list(range(200)))]:
    comparisons = 0
    quicksort(data, 0, len(data) - 1)
    print(name, comparisons, data == sorted(data))
▸ Beklenen çıktı
3 [2, 4, 3, 5, 9, 8, 7]
mixed 1542 True
sorted 19900 True
200 karışık eleman için 1542 karşılaştırma (n log₂ n ≈ 1529), zaten sıralı 200 eleman için ise tam 200 · 199 / 2 = 19.900; pivot son eleman olunca sıralı girdi en kötü durumdur.
Etkileşimli
Simülasyon yükleniyor…
Eklemeli sıralama küçük ya da neredeyse sıralı dizilerde çok hızlıdır. Bu yüzden Python'un Timsort'u diziyi önce kısa parçalara (run) böler, onları eklemeli sıralamayla dizer, sonra birleştirmeli sıralama gibi ikişer ikişer birleştirir. Animasyonda böyle küçük bir parçanın nasıl sıralandığını izle.

Alt sınır: Ω(n log n)

Yalnızca “a ≤ b mi?” sorusunu soran herhangi bir sıralama algoritmasını bir karar ağacı olarak düşünelim: her iç düğüm bir karşılaştırma, her yaprak bir cevaptır, yani elemanların bir dizilişidir. n farklı eleman n! biçimde dizilebilir ve her biri farklı bir yaprağa götürmelidir. h yüksekliğindeki bir ikili ağacın en fazla 2ʰ yaprağı vardır; demek ki 2ʰ ≥ n!.

h ≥ log₂(n!) ≥ log₂((n/2)^(n/2)) = (n/2) · log₂(n/2) = Ω(n log n)h ≥ log₂(n!) ≥ log₂((n/2)^(n/2)) = (n/2) · log₂(n/2) = Ω(n log n)
burada:
  • hen kötü durumda karşılaştırma sayısı (ağacın yüksekliği)
  • n!n elemanın olası dizilişlerinin sayısı

n!'in n/2'den büyük n/2 çarpanının her biri ≥ n/2 olduğundan n! ≥ (n/2)^(n/2). Stirling formülüyle daha kesin olarak log₂(n!) ≈ n log₂ n − 1,44n.

Örnek 3: en az kaç karşılaştırma?

En kötü durumda herhangi bir karşılaştırmalı sıralama 3, 4 ve 10 eleman için en az kaç karşılaştırma yapmalıdır?

Çözümü göster
h ≥ ⌈log₂(n!)⌉:
n = 3: 3! = 6, 2² = 4 < 6 ≤ 8 = 2³ → 3 karşılaştırma.
n = 4: 4! = 24, 2⁴ = 16 < 24 ≤ 32 = 2⁵ → 5 karşılaştırma.
n = 10: 10! = 3.628.800, 2²¹ = 2.097.152 < 10! ≤ 2²² = 4.194.304 → 22 karşılaştırma.
Kıyaslama için: kabarcık sıralaması n = 10 için en kötü durumda 45 karşılaştırma yapar.

Sayarak sıralama: sınırı aşmak

Ω(n log n) sınırı yalnızca karşılaştırmaya dayalı algoritmalar için geçerlidir. Anahtarlar 0 … k aralığında tam sayılarsa, sayarak sıralama hiç karşılaştırma yapmaz: her değerin kaç kez geçtiğini sayar, sonra değerleri sayıları kadar sırayla yazar. Süre ve bellek O(n + k)'dir. Kararlı sayarak sıralamanın basamak basamak tekrarlanmasına taban sıralaması (radix sort) denir: d basamaklı sayılar için O(d · (n + k)).

Python
import math

def counting_sort(a, k):
    count = [0] * (k + 1)
    for x in a:
        count[x] += 1
    out = []
    for value, c in enumerate(count):
        out.extend([value] * c)
    return count, out

print(counting_sort([2, 5, 3, 0, 2, 3, 0, 3], 5))
for n in [3, 4, 5, 10, 1000]:
    print(n, math.ceil(math.log2(math.factorial(n))))
▸ Beklenen çıktı
([2, 0, 2, 3, 0, 1], [0, 0, 2, 2, 3, 3, 3, 5])
3 3
4 5
5 7
10 22
1000 8530
Sayaç dizisi: 0 iki kez, 1 hiç, 2 iki kez, 3 üç kez, 4 hiç, 5 bir kez. Alttaki satırlar ⌈log₂(n!)⌉ alt sınırını hesaplar: hiçbir karşılaştırmalı sıralama 1000 elemanı en kötü durumda 8530'dan az karşılaştırmayla sıralayamaz.
AlgoritmaEn iyiOrtalamaEn kötüEk bellekKararlı
Eklemelinn²n²1evet
Merge sortn log nn log nn log nnevet
Quicksortn log nn log nn²log nhayır
Heap sortn log nn log nn log n1hayır
Timsort (Python)nn log nn log nnevet
Sayarakn + kn + kn + kn + kevet
Tüm değerler O(…) içindedir. Hızlı sıralama için log n, özyineleme yığıtının ortalama derinliğidir.

Önemli noktalar

  • Birleştirmeli sıralama: böl, özyinelemeli sırala, O(n)'de birleştir; T(n) = 2T(n/2) + n → Θ(n log n), kararlı, O(n) bellek.
  • Hızlı sıralama pivot etrafında bölümler; ortalama Θ(n log n), en kötü Θ(n²); rastgele pivot bunu uygulamada önler.
  • Karar ağacı: 2ʰ ≥ n! → her karşılaştırmalı sıralama en kötü durumda Ω(n log n) karşılaştırma yapar.
  • Sayarak sıralama karşılaştırma yapmaz ve O(n + k)'de çalışır; anahtarlar küçük tam sayılar olunca sınırı aşar.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Birleştirmeli sıralama 1024 elemanlı bir dizide kaç birleştirme düzeyinden geçer?