- 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)
- Böl: diziyi ortadan iki yarıya ayır.
- Çöz: her yarıyı özyinelemeli sırala (tek elemanlı dizi zaten sıralıdır).
- 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).
[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Çözümü gizle
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).
- 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).
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.
[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Çözümü gizle
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.
- 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²).
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
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!.
- 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.
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Çözümü gizle
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)).
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
| Algoritma | En iyi | Ortalama | En kötü | Ek bellek | Kararlı |
|---|---|---|---|---|---|
| Eklemeli | n | n² | n² | 1 | evet |
| Merge sort | n log n | n log n | n log n | n | evet |
| Quicksort | n log n | n log n | n² | log n | hayır |
| Heap sort | n log n | n log n | n log n | 1 | hayır |
| Timsort (Python) | n | n log n | n log n | n | evet |
| Sayarak | n + k | n + k | n + k | n + k | evet |
Ö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.