Bölüm 2: Algoritma Analizi

Veri Yapıları
Algoritma ve verimlilikDeneysel analizİlkel işlemleri sayma O, Ω, ΘDöngü kalıplarıDoğrusal ve ikili aramaAlan karmaşıklığıDoğruluk ve sağlamlık
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Ölçme ve sayma

Bir algoritmayı deneysel olarak ölçmek ve ilkel işlemlerini sayarak çözümlemek.

Asimptotik gösterim

Zaman ve alan karmaşıklığını O, Ω ve Θ gösterimleriyle ifade etmek.

Koddan karmaşıklığa

Döngü kalıplarından en iyi, ortalama ve en kötü durum maliyetini çıkarmak.

Karşılaştırma

Aynı problemi çözen algoritmaları maliyet, doğruluk ve sağlamlık açısından karşılaştırmak.

Algoritmalar ve Ölçüm

Algoritmalar

  • Bir algoritma, bir problemi çözmek için adım adım bir prosedürdür: girdi → adımlar → çıktı.
  • Aynı problemi çözmek için farklı algoritmalar bulunabilir; bazıları diğerlerine göre daha verimli çalışır.
  • Doğru algoritma seçimi kritiktir: iyi bir algoritma, işi daha az kaynak kullanarak yapar.
girdi5291algoritma (sıralama)sonlu · kesin · etkili adımlarçıktı1259

Bazı yaygın algoritmalar:

SıralamaVerileri belirli bir sıraya göre düzenler.kabarcık, birleştirme sıralaması
AramaBelirli bir öğeyi veri kümesinde bulur.doğrusal arama, ikili arama
ÇizgeÇizge teorisi problemlerini çözer.en kısa yol, ağ akışı

Algoritmalar ve Ölçüm

İyi Bir Algoritma

Bir problemi etkili bir şekilde çözen algoritmadır. Dört ölçüte bakarız:

Zaman Karmaşıklığı (Time Complexity)Çalışma süresi veya işlem sayısı gibi faktörler zaman karmaşıklığını belirler.
Alan Karmaşıklığı (Space Complexity)Algoritmanın kullandığı bellek miktarı alan karmaşıklığını belirler.
Doğruluk (Correctness)Her geçerli girdi için doğru sonucu üretmelidir. Yanlış sonuç veren bir algoritma kullanışsızdır.
Sağlamlık (Robustness)Çeşitli durumlar ve girdilerle başa çıkabilmeli, farklı senaryolara uyum sağlayabilmelidir.

Algoritmalar ve Ölçüm · Ölçerek analiz

Deneysel Analiz: System.nanoTime()

for (int n = 1000; n <= 64000; n *= 2) {
  int[] a = new int[n];
  for (int i = 0; i < n; i++) a[i] = i; // tekrarsız
  long bas = System.nanoTime();
  tekrarVar(a);                        // ölçülen iş
  long sure = System.nanoTime() - bas;  // ns
  System.out.println(n + " " + sure / 1e6 + " ms");
}
ÖlçSystem.nanoTime() iki okuma arasındaki farkı ns cinsinden verir (duvar saati değil).
TekrarlaJIT ısınması, çöp toplayıcı ve diğer süreçler gürültü yapar: birkaç kez çalıştır, medyanı al.
ÇizSüreyi n'ye karşı çiz; n'yi ikiye katlayınca sürenin kaç katına çıktığına bak.
Dikkat: new int[n] sıfırlarla dolar → tekrarVar (tekrar arayan O(n²) metot) ilk karşılaştırmada true döner. En kötü durumu ölçmek için tekrarsız veri gerekir.

Algoritmalar ve Ölçüm · Ölçerek analiz

Katlama Deneyi ve Deneysel Analizin Sınırları

2×4×6×8×2.0×O(n)2.2×O(n log n)4.0×O(n²)8.0×O(n³)n = 1000 → 2000 olunca süre kaç katına çıkar?

Deneysel analizin sınırları (Goodrich §4.1):

  • Sonuçlar donanıma, JVM'e ve yüke bağlıdır; iki algoritma aynı ortamda ölçülmelidir.
  • Yalnız sınırlı sayıda girdi denenebilir; önemli bir girdi kaçabilir.
  • Ölçmek için algoritmanın tamamen kodlanması gerekir.
Katlama deneyi: T(2n)/T(n) ≈ 2k ise T(n) ≈ c·nk. Sınırları aşmak için: ilkel işlemleri sayarak analiz — donanımdan bağımsız, tüm girdiler için, kod çalıştırmadan.

Algoritmalar ve Ölçüm · Adım sayma

İlkel İşlemleri Sayma: maksimum()

int maksimum(int[] a) {
  int enBuyuk = a[0];
  for (int i = 1; i < a.length; i++) {
    if (a[i] > enBuyuk)
      enBuyuk = a[i];
  }
  return enBuyuk;
}
  • İlkel işlem: atama, karşılaştırma, aritmetik, dizi indisleme, metot çağrısı / dönüş.
  • Her birinin süresi sabit kabul edilir → yalnız kaç kez çalıştıklarını sayarız.
SatırİfadeKaç kez?
2enBuyuk = a[0]1
3i = 11
3i < a.lengthn
4a[i] > enBuyukn − 1
5enBuyuk = a[i]0 … n − 1
3i++n − 1
7return1
En iyi: 3n + 1 (a[0] en büyük, hiç güncelleme yok) · En kötü: 4n (dizi artan sıralı) → her iki durumda Θ(n). Sabitler donanıma bağlı olduğundan atılır.

Algoritmalar ve Ölçüm · Kodu adım adım çalıştır

Kod İzleme: maksimum(a)

0318223944a[0..0] işlendienBuyuk3

enBuyuk = a[0] = 3: ilk öğe geçici en büyük.

0318223944▲ ia[0..0] işlendienBuyuk3

i = 1, 1 < 5 → döngü sürer. Değişmez: enBuyuk = a[0..0]'in en büyüğü.

0318223944▲ ia[0..0] işlendienBuyuk3

a[1] = 8 > 3 → doğru.

0318223944▲ ia[0..1] işlendienBuyuk8

enBuyuk = 8: yeni en büyük bulundu.

0318223944▲ ia[0..1] işlendienBuyuk8

i = 2, 2 < 5 → döngü sürer. Değişmez: enBuyuk = a[0..1]'in en büyüğü.

0318223944▲ ia[0..2] işlendienBuyuk8

a[2] = 2 > 8 → yanlış; enBuyuk değişmez.

0318223944▲ ia[0..2] işlendienBuyuk8

i = 3, 3 < 5 → döngü sürer. Değişmez: enBuyuk = a[0..2]'in en büyüğü.

0318223944▲ ia[0..2] işlendienBuyuk8

a[3] = 9 > 8 → doğru.

0318223944▲ ia[0..3] işlendienBuyuk9

enBuyuk = 9: yeni en büyük bulundu.

0318223944▲ ia[0..3] işlendienBuyuk9

i = 4, 4 < 5 → döngü sürer. Değişmez: enBuyuk = a[0..3]'in en büyüğü.

0318223944▲ ia[0..4] işlendienBuyuk9

a[4] = 4 > 9 → yanlış; enBuyuk değişmez.

0318223944a[0..4] işlendienBuyuk9

i = 5: 5 < 5 yanlış → döngü biter. Tüm dizi işlendi.

0318223944a[0..4] işlendienBuyuk9

return 9. n = 5 için n − 1 = 4 karşılaştırma yapıldı → Θ(n).

int maksimum(int[] a) {
  int enBuyuk = a[0];
  for (int i = 1; i < a.length; i++) {
    if (a[i] > enBuyuk)
      enBuyuk = a[i];
  }
  return enBuyuk;
}

Asimptotik Gösterim

Zaman Karmaşıklığı ve Büyük O (Big O)

  • Algoritmanın ne kadar sürede çalıştığını, girdi boyutu n'ye bağlı olarak ifade eder.
  • Büyük O gösterimi, çalışma süresi için bir üst sınır belirtir; genellikle en kötü durum için kullanılır.
  • O(1): işlem sayısı girdi boyutuyla büyümez; farklı girdilerde süre birebir aynı olmak zorunda değildir.
  • O(n): girdi büyüdükçe süre doğrusal olarak artar.
n arttıkça işlem sayısı
Büyük O'da yalnızca en hızlı büyüyen terim önemlidir; sabitler ve küçük terimler atılır:
3n2 + 5n + 2 → O(n2)  ·  4n + 100 → O(n)  ·  7 → O(1)

Asimptotik Gösterim

O, Ω ve Θ: Tanım ve Grafik

Tanım: f(n) = O(g(n)) ⇔ öyle c > 0 ve n₀ ≥ 1 vardır ki her n ≥ n₀ için f(n) ≤ c·g(n). Ω için ≥, Θ için ikisi birden.
2040600481216n →n₀ = 5c·g(n) = 4nf(n) = 2n + 10

Büyük O: n ≥ n₀ = 5 için 2n + 10 ≤ 4·n → f(n) = O(n) (c = 4). Küçük n'de (n < 5) f(n) daha büyük; önemli değil.

2040600481216n →c·g(n) = 2nf(n) = 2n + 10

Büyük Omega: her n ≥ 1 için 2n + 10 ≥ 2·n → f(n) = Ω(n): f, n'den daha yavaş büyümez (alt sınır).

2040600481216n →n₀ = 54n2nf(n)

Büyük Teta: n ≥ 5 için 2n ≤ f(n) ≤ 4n → f(n) = Θ(n): iki doğru arasına sıkışır, büyüme hızı tam olarak n.

Asimptotik Gösterim

Asimptotik Sınırlar ve Durumlar

O(g(n)) · Üst sınırÇalışma miktarı, yeterince büyük n için g(n)'den daha hızlı büyümez.
Ω(g(n)) · Alt sınırÇalışma miktarı, yeterince büyük n için g(n)'den daha yavaş büyümez.
Θ(g(n)) · Sıkı sınırÜst ve alt sınır aynıdır; büyüme hızı g(n) ile aynı sınıftadır.
AnalizNe sorar?Doğrusal arama örneği
En iyi durumEn elverişli geçerli girdide kaç işlem gerekir?İlk öğe hedefse Θ(1)
Ortalama durumBir girdi dağılımı varsayımı altında beklenen maliyet nedir?Hedefin konumu eşit olasılıklıysa Θ(n)
En kötü durumHer geçerli girdi için garanti edilen üst maliyet nedir?Hedef yoksa veya sondaysa Θ(n)
Amortize analiz: Tek bir işlem pahalı olabilir; uzun bir işlem dizisinin toplam maliyeti işlem sayısına bölünür. Örneğin büyüyen dizide yeniden boyutlandırma O(n), sona ekleme ise amortize O(1)'dir.

Asimptotik Gösterim · Durum analizi

En İyi, Ortalama ve En Kötü Durum

Dizi [4, 8, 15, 16, 23, 42] üzerinde doğrusal arama; her olası hedef için karşılaştırma sayısı:

0246481516234250en kötü = 6ortalama ≈ 3,86en iyi = 1aranan değer (50 dizide yok)karşılaştırma
  • Aynı n için girdiye göre maliyet değişir.
  • En kötü durum bir garanti verir ve hesaplaması kolaydır → genellikle bu raporlanır.
  • Ortalama bir olasılık dağılımı varsayar (burada her hedef eşit olasılıklı).
  • En iyi durum tek başına yanıltıcıdır.

Asimptotik Gösterim

Büyük O Gösterimi Örnekleri

GösterimAdıÖrnekNeden?
O(1)SabitDizinin ilk öğesine erişmeDizinin boyutu ne olursa olsun erişim süresi sabit
O(log n)LogaritmikSıralı dizide ikili aramaHer adımda arama alanı yarıya iner
O(n)DoğrusalDiziyi baştan sona taramaHer öğe bir kez işlenir
O(n log n)Doğrusal logaritmikBirleştirme sıralaması (Merge Sort)Böl ve fethet: log n seviye, her seviyede n iş
O(n2)KareTüm öğe çiftlerini karşılaştırmaİç içe iki döngü
O(2n)ÜstelTüm alt kümeleri bulman öğeli kümenin 2n alt kümesi var

Asimptotik Gösterim

n Büyüdükçe Ne Olur?

Karmaşıklıkn = 10n = 100n = 1.000
log2 n3,36,610
n101001.000
n log2 n336649.966
n210010.0001.000.000
2n1.024≈ 1,3·1030≈ 1,1·10301
Saniyede 109 işlem yapan bir bilgisayar, n = 100 için 2n adımı yaklaşık 4·1013 yılda bitirir. Evrenin yaşı ≈ 1,4·1010 yıl.

Koddan Karmaşıklığa · Adım sayma

Döngü Kalıplarından Karmaşıklığa

KalıpGövde kaç kez?n = 16Sınıf
for (i = 0; i < n; i++)n16O(n)
for (i = 0; i < n; i += 2)n / 28O(n)
for (i = 1; i < n; i *= 2)⌈log₂ n⌉4O(log n)
iç içe: i < n, j < nn²256O(n²)
iç içe: i < n, j < in(n−1)/2120O(n²)
ardışık iki i < n döngüsü2n32O(n)
Ardışık bloklarmaliyetler toplanır, en büyüğü kalır: O(n) + O(n²) = O(n²)
İç içe döngülermaliyetler çarpılır: dış × iç (iç döngü dışa bağlıysa topla)

Koddan Karmaşıklığa · Kodu adım adım çalıştır

Kod İzleme: adimSay(16) — i her turda ikiye katlanır

12345678910111213141516n

adimSay(16): kaç kez ikiye katlayarak n'ye ulaşırız?

12345678910111213141516n

Sayaç k = 0.

12345678910111213141516▲ in

i = 1 < 16 → gövde çalışır.

12345678910111213141516▲ in

k++ → k = 1.

12345678910111213141516▲ in

i = 2 < 16 → gövde çalışır.

12345678910111213141516▲ in

k++ → k = 2.

12345678910111213141516▲ in

i = 4 < 16 → gövde çalışır.

12345678910111213141516▲ in

k++ → k = 3.

12345678910111213141516▲ in

i = 8 < 16 → gövde çalışır.

12345678910111213141516▲ in

k++ → k = 4.

12345678910111213141516▲ in

i *= 2 → i = 16; 16 < 16 yanlış → döngü biter.

12345678910111213141516n

return 4 = log₂ 16. n'yi 1000 kat büyütmek yalnız ~10 tur ekler → O(log n).

int adimSay(int n) {
  int k = 0;
  for (int i = 1; i < n; i *= 2)
    k++;
  return k;
}

Koddan Karmaşıklığa

Koddan Karmaşıklığa

O(1)
int ilk(int[] a) {
    return a[0];
}
O(n)
int toplam(int[] a) {
    int s = 0;
    for (int x : a)  // n kez
        s += x;
    return s;
}
O(n²)
boolean tekrarVar(int[] a) {
    for (int i = 0; i < a.length; i++)
        for (int j = i + 1; j < a.length; j++)
            if (a[i] == a[j])
                return true;
    return false;
}
// dış döngü n, iç döngü ≈ n/2
// → n(n−1)/2 karşılaştırma

Kural: sabitler ve küçük terimler atılır. n(n−1)/2 = n²/2 − n/2 → O(n²)

Koddan Karmaşıklığa · Kodu adım adım çalıştır

Kod İzleme: toplam()

071229

main, toplam(a)'yı çağırır. Yeni bir çağrı çerçevesi açılır.

071229

Metoda girildi; a aynı diziyi gösterir (kopyalanmaz).

071229

Toplam değişkeni s = 0 ile başlar.

071229

Döngü 1. tur: x = a[0] = 7.

071229

s += x → s = 0 + 7 = 7.

071229

Döngü 2. tur: x = a[1] = 2.

071229

s += x → s = 7 + 2 = 9.

071229

Döngü 3. tur: x = a[2] = 9.

071229

s += x → s = 9 + 9 = 18.

071229

Dizide öğe kalmadı; döngüden çıkılır.

071229

return 18: çerçeve kapanır, değer çağırana döner.

071229

println 18 yazar. Döngü gövdesi n = 3 kez çalıştı → O(n).

int toplam(int[] a) {
    int s = 0;
    for (int x : a) {
        s += x;
    }
    return s;
}

// main içinde:
System.out.println(toplam(a));

Koddan Karmaşıklığa · Kodu adım adım çalıştır

Kod İzleme: dogrusalArama(a, 23)

205182123164235386567728919

Sıralı bir dizide 23 aranıyor; doğrusal arama sıralı olmayı kullanmaz, baştan tek tek bakar.

205182123164235386567728919▲ i

a[0] = 2 ≠ 23 → i ilerler. Her adımda yalnızca bir öğe elenir.

205182123164235386567728919▲ i

a[1] = 5 ≠ 23 → i ilerler.

205182123164235386567728919▲ i

a[2] = 8 ≠ 23 → i ilerler.

205182123164235386567728919▲ i

a[3] = 12 ≠ 23 → i ilerler.

205182123164235386567728919▲ i

a[4] = 16 ≠ 23 → i ilerler.

205182123164235386567728919▲ i

a[5] = 23 == 23 → return 5. 6 karşılaştırma; hedef sonda ya da yoksa n = 10 karşılaştırma → O(n).

int dogrusalArama(int[] a, int hedef) {
    for (int i = 0; i < a.length; i++)
        if (a[i] == hedef) return i;
    return -1;
}

Koddan Karmaşıklığa · Kodu adım adım çalıştır

Kod İzleme: ikiliArama(a, 23)

021528312416523638756872991▲ lo▲ hi

Arama aralığı tüm dizi: lo = 0, hi = 9.

021528312416523638756872991▲ lo▲ hi

0 ≤ 9 → aralık boş değil, 1. tur başlar.

021528312416523638756872991▲ lo▲ hi▲ mid

mid = (0 + 9) / 2 = 4, a[4] = 16.

021528312416523638756872991▲ lo▲ hi

16 < 23 → hedef sağ yarıda: lo = mid + 1 = 5. Sol yarı elendi.

021528312416523638756872991▲ lo▲ hi

5 ≤ 9 → aralık boş değil, 2. tur başlar.

021528312416523638756872991▲ lo▲ hi▲ mid

mid = (5 + 9) / 2 = 7, a[7] = 56.

021528312416523638756872991▲ lo▲ hi

56 > 23 → hedef sol yarıda: hi = mid − 1 = 6. Sağ yarı elendi.

021528312416523638756872991▲ lo▲ hi

5 ≤ 6 → aralık boş değil, 3. tur başlar.

021528312416523638756872991▲ lomid▲ hi

mid = (5 + 6) / 2 = 5, a[5] = 23.

021528312416523638756872991▲ lomid▲ hi

23 == 23 → bulundu, return 5. Yalnızca 3 tur; doğrusal arama 6 karşılaştırma yapardı. Her tur aralığı yarıya indirir → O(log n).

int ikiliArama(int[] a, int hedef) {
    int lo = 0, hi = a.length - 1;
    while (lo <= hi) {
        int mid = (lo + hi) / 2;
        if (a[mid] == hedef) return mid;
        if (a[mid] < hedef) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}

Koddan Karmaşıklığa · Kodu adım adım çalıştır

Kod İzleme: ikiliArama(a, 40) — hedef yok

021528312416523638756872991▲ lo▲ hi

Bu kez 40 dizide yok: en kötü durum. Aralık tüm dizi.

021528312416523638756872991▲ lo▲ hi

0 ≤ 9 → aralık boş değil, 1. tur.

021528312416523638756872991▲ lo▲ hi▲ mid

1. tur: mid = (0 + 9) / 2 = 4, a[4] = 16.

021528312416523638756872991▲ lo▲ hi

16 < 40 → lo = 5; sol taraf elendi.

021528312416523638756872991▲ lo▲ hi

5 ≤ 9 → aralık boş değil, 2. tur.

021528312416523638756872991▲ lo▲ hi▲ mid

2. tur: mid = (5 + 9) / 2 = 7, a[7] = 56.

021528312416523638756872991▲ lo▲ hi

56 > 40 → hi = 6; sağ taraf elendi.

021528312416523638756872991▲ lo▲ hi

5 ≤ 6 → aralık boş değil, 3. tur.

021528312416523638756872991▲ lomid▲ hi

3. tur: mid = (5 + 6) / 2 = 5, a[5] = 23.

021528312416523638756872991▲ lohi

23 < 40 → lo = 6; sol taraf elendi.

021528312416523638756872991▲ lohi

6 ≤ 6 → aralık boş değil, 4. tur.

021528312416523638756872991▲ lohimid

4. tur: mid = (6 + 6) / 2 = 6, a[6] = 38.

021528312416523638756872991▲ hi▲ lo

38 < 40 → lo = 7; sol taraf elendi.

021528312416523638756872991▲ hi▲ lo

7 ≤ 6 yanlış → aralık boş, döngü biter.

021528312416523638756872991▲ hi▲ lo

return -1. En kötü durumda bile yalnız 4 tur = ⌊log₂ 10⌋ + 1; doğrusal arama 10 karşılaştırma yapardı.

int ikiliArama(int[] a, int hedef) {
    int lo = 0, hi = a.length - 1;
    while (lo <= hi) {
        int mid = (lo + hi) / 2;
        if (a[mid] == hedef) return mid;
        if (a[mid] < hedef) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}

Koddan Karmaşıklığa · Kodu adım adım çalıştır

Kod İzleme: tekrarVar()

04172137▲ i

Dış döngü: i = 0 (a[i] = 4). j, i+1'den başlar.

04172137▲ i▲ j

a[0] = 4 ile a[1] = 7 karşılaştırılır → farklı, j ilerler.

04172137▲ i▲ j

a[0] = 4 ile a[2] = 1 karşılaştırılır → farklı, j ilerler.

04172137▲ i▲ j

a[0] = 4 ile a[3] = 7 karşılaştırılır → farklı, j ilerler.

04172137▲ i

Dış döngü: i = 1 (a[i] = 7). j, i+1'den başlar.

04172137▲ i▲ j

a[1] = 7 ile a[2] = 1 karşılaştırılır → farklı, j ilerler.

04172137▲ i▲ j

a[1] = 7 ve a[3] = 7 eşit → return true. En kötü durumda (tekrar yoksa) n(n−1)/2 = 6 karşılaştırma yapılırdı → O(n²).

boolean tekrarVar(int[] a) {
    for (int i = 0; i < a.length; i++)
        for (int j = i + 1; j < a.length; j++)
            if (a[i] == a[j])
                return true;
    return false;
}

Koddan Karmaşıklığa · Aynı problem, iki algoritma

Örnek Problem: Önek Ortalamaları

Girdi x[0..n−1]; çıktı a[j] = (x[0] + … + x[j]) / (j + 1). Kullanım: hareketli ortalama, finansal eğilim analizi (Goodrich §4.3).

x04182632a4.0a[0] = (4) / 1 = 4.0

a[0] = x[0] … x[0] öğelerinin ortalaması = 4.0.

x04182632a4.06.0a[1] = (4 + 8) / 2 = 6.0

a[1] = x[0] … x[1] öğelerinin ortalaması = 6.0.

x04182632a4.06.06.0a[2] = (4 + 8 + 6) / 3 = 6.0

a[2] = x[0] … x[2] öğelerinin ortalaması = 6.0.

x04182632a4.06.06.05.0a[3] = (4 + 8 + 6 + 2) / 4 = 5.0

a[3] = x[0] … x[3] öğelerinin ortalaması = 5.0.

İki çözüm: her j için baştan topla (O(n²)) · birikimli toplamı taşı (O(n)).

Koddan Karmaşıklığa · Kodu adım adım çalıştır

Kod İzleme: onekOrt1 — her j için baştan topla

x04182632a

n = 4; sonuç dizisi a oluşturulur (0.0 ile dolu).

x04182632atop0.0

j = 0: top = 0 — her j için toplam baştan başlar.

x04182632atop4.0

top += x[0] = 4 → top = 4.0.

x04182632a4.0top4.0

a[0] = 4.0 / 1 = 4.0.

x04182632a4.0top0.0

j = 1: top = 0 — her j için toplam baştan başlar.

x04182632a4.0top4.0

top += x[0] = 4 → top = 4.0.

x04182632a4.0top12.0

top += x[1] = 8 → top = 12.0.

x04182632a4.06.0top12.0

a[1] = 12.0 / 2 = 6.0.

x04182632a4.06.0top0.0

j = 2: top = 0 — her j için toplam baştan başlar.

x04182632a4.06.0top4.0

top += x[0] = 4 → top = 4.0.

x04182632a4.06.0top12.0

top += x[1] = 8 → top = 12.0.

x04182632a4.06.0top18.0

top += x[2] = 6 → top = 18.0.

x04182632a4.06.06.0top18.0

a[2] = 18.0 / 3 = 6.0.

x04182632a4.06.06.0top0.0

j = 3: top = 0 — her j için toplam baştan başlar.

x04182632a4.06.06.0top4.0

top += x[0] = 4 → top = 4.0.

x04182632a4.06.06.0top12.0

top += x[1] = 8 → top = 12.0.

x04182632a4.06.06.0top18.0

top += x[2] = 6 → top = 18.0.

x04182632a4.06.06.0top20.0

top += x[3] = 2 → top = 20.0.

x04182632a4.06.06.05.0top20.0

a[3] = 20.0 / 4 = 5.0.

x04182632a4.06.06.05.0

return a. Toplama sayısı 1 + 2 + 3 + 4 = 10 = n(n+1)/2 → O(n²).

double[] onekOrt1(double[] x) {
  int n = x.length;
  double[] a = new double[n];
  for (int j = 0; j < n; j++) {
    double top = 0;
    for (int i = 0; i <= j; i++)
      top += x[i];
    a[j] = top / (j + 1);
  }
  return a;
}

Koddan Karmaşıklığa · Kodu adım adım çalıştır

Kod İzleme: onekOrt2 — birikimli toplam

x04182632atop0.0

top döngüden önce bir kez sıfırlanır ve turlar boyunca korunur.

x04182632atop4.0

j = 0: top += x[0] = 4 → top = 4.0 (önceki toplam yeniden kullanıldı).

x04182632a4.0top4.0

a[0] = 4.0 / 1 = 4.0.

x04182632a4.0top12.0

j = 1: top += x[1] = 8 → top = 12.0 (önceki toplam yeniden kullanıldı).

x04182632a4.06.0top12.0

a[1] = 12.0 / 2 = 6.0.

x04182632a4.06.0top18.0

j = 2: top += x[2] = 6 → top = 18.0 (önceki toplam yeniden kullanıldı).

x04182632a4.06.06.0top18.0

a[2] = 18.0 / 3 = 6.0.

x04182632a4.06.06.0top20.0

j = 3: top += x[3] = 2 → top = 20.0 (önceki toplam yeniden kullanıldı).

x04182632a4.06.06.05.0top20.0

a[3] = 20.0 / 4 = 5.0.

x04182632a4.06.06.05.0

Aynı sonuç, yalnız 4 toplama (onekOrt1: 10). Tek döngü → O(n).

double[] onekOrt2(double[] x) {
  int n = x.length;
  double[] a = new double[n];
  double top = 0;
  for (int j = 0; j < n; j++) {
    top += x[j];
    a[j] = top / (j + 1);
  }
  return a;
}

Koddan Karmaşıklığa · Aynı problem, iki algoritma

İki Çözümün Karşılaştırması

20406078123456789101112nonekOrt1: n(n+1)/2onekOrt2: n
nonekOrt1onekOrt2
105510
1005.050100
1.000500.5001.000
1.000.000500.000.500.0001.000.000
Fark, tekrarlanan işi ortadan kaldırmaktan geliyor: onekOrt2 önceki toplamı hatırlar. n = 10⁶ için ~5·10¹¹ yerine 10⁶ toplama.

Alan ve Doğruluk

Alan Karmaşıklığı

  • Algoritmanın kullandığı bellek miktarını ölçer; genellikle Büyük O gösterimiyle ifade edilir.
  • Bellek kullanımının girdi boyutuna bağlı artışını gösterir (girdinin kendisi çoğu zaman sayılmaz; ek alan).
  • Sınırlı bellek kaynaklarına sahip sistemlerde bellek verimliliği önemlidir.
O(1) · SabitÖrnek: bir değişken oluşturma. Bellek kullanımı girdi boyutuyla değişmez.s
O(n) · DoğrusalÖrnek: bir dizinin tüm öğelerini saklama; onekOrt'un a dizisi.n hücre
O(n2) · KareÖrnek: n×n boyutunda bir matrisi saklama. Bellek, n'nin karesiyle artar.n × n

Alan ve Doğruluk

Doğruluk (Correctness)

  • Doğru bir algoritma, her geçerli girdi için sonlu sürede beklenen çıktıyı üretir.
  • Hızlı çalışan ama yanlış sonuç veren bir algoritmanın değeri yoktur.

Doğruluk nasıl gösterilir?

Test SenaryolarıÇıktılar, bilinen doğru sonuçlarla karşılaştırılır.
Uç DurumlarBoş girdi, tek öğe, en büyük/en küçük değerler.
Döngü DeğişmeziHer adımda doğru kalan bir özellikle ispat.
Karşı ÖrnekTek bir yanlış sonuç, algoritmanın doğru olmadığını gösterir.
0318223944▲ iişlendi: enBuyuk = 8 = max(a[0..2])Değişmez (invariant):her turun başında enBuyuk,a[0..i−1]'in en büyüğüdür.Döngü bitince i = n → doğru sonuç.

Alan ve Doğruluk

Sağlamlık (Robustness)

  • Algoritmanın beklenmeyen durumlarla başa çıkma yeteneğidir.
  • Sağlam bir algoritma, normal aralığın dışındaki girdileri bile çökmeden veya yanlış sonuç üretmeden işleyebilir.

Sağlamlık nasıl sınanır?

Geçersiz GirdilerUygun olmayan veya beklenmeyen girdilerle başa çıkma.null, negatif boyut
Hatalı VerilerBozuk veya eksik verilerle başa çıkma.eksik satır, yanlış biçim
Büyük Veri KümeleriÇok büyük girdileri işleyebilme.10⁸ kayıt
Aşırı YüklenmeAlgoritmanın aşırı yük altında nasıl davrandığı.aynı anda binlerce istek

Özet

Sık Yapılan Hatalar

“O = en kötü durum”O bir üst sınır gösterimidir; en iyi / en kötü durum ayrı kavramdır. Her durum için O, Ω, Θ yazılabilir.
“O(1) her zaman hızlıdır”Sabit ama büyük olabilir; küçük n'de 1000·n'lik algoritma, n²'lik olandan yavaş kalabilir.
“İç içe döngü = n²”İç döngü j *= 2 ile ilerliyorsa n log n; sabit sayıda dönüyorsa O(n).
“log tabanı önemlidir”log₂ n ile log₁₀ n arasında yalnız sabit çarpan var: ikisi de O(log n).
(lo + hi) / 2 taşmasıÇok büyük dizilerde int taşar; lo + (hi - lo) / 2 güvenlidir.
a == b ile dizi karşılaştırmak== referansları karşılaştırır; içerik için Arrays.equals(a, b).

Özet

Özet

KonuHatırlanacaklar
Deneysel analizsüre donanıma ve girdiye bağlıdır; katlama deneyi: T(2n)/T(n) ≈ 2k → T(n) ≈ c·nk
İlkel işlem saymaatama, karşılaştırma, aritmetik, dizi erişimi; donanımdan bağımsız, tüm girdiler için
O, Ω, Θüst, alt ve sıkı sınır; sabit çarpanlar ve küçük terimler atılır
Durumlaren iyi, ortalama, en kötü; garanti için en kötü durum, uzun işlem dizisi için amortize
Döngü kalıplarıtek döngü O(n) · iç içe O(n²) · yarıya inen O(log n) · dış n, iç log n → O(n log n)
Algoritma seçimiönek ortalamaları O(n²) → O(n); sıralı dizide ikili arama O(log n)
Alan, doğrulukek bellek de Büyük O ile ölçülür; doğru ve sağlam olmayan hızlı algoritmanın değeri yoktur

Özet

Temel Veri Yapılarında İşlem Maliyetleri

Veri yapısıi. öğeye erişimAramaEklemeSilme
DiziO(1)O(n)O(n)O(n)
Bağlı listeO(n)O(n)O(1)†O(1)†
Yığınyalnızca üstO(n)O(1) pushO(1) pop
Kuyrukyalnızca önO(n)O(1) enqueueO(1) dequeue
Eşleme / Küme (hash)—O(1)*O(1)*O(1)*

† Konum (önceki düğüm) biliniyorsa.   * Ortalama durum; en kötü durumda O(n).

Özet

Örnek Problemler

  1. Tüm öğe çiftlerini karşılaştıran algoritmanın zaman karmaşıklığı nedir?
  2. f(n) = 5n³ + 2n² + 100 hangi Θ sınıfındadır?
  3. n = 1024 öğeli sıralı dizide ikili arama en kötü durumda kaç tur döner?
  4. for (int i = 1; i < n; i *= 2) döngüsü kaç kez döner?
  5. Doğrusal aramanın en iyi ve en kötü durumu nedir?
  6. A algoritması 100n, B algoritması n² adım atıyor. Hangi n değerleri için A daha hızlıdır?
Cevapları göster
  1. n(n − 1)/2 karşılaştırma → O(n2).
  2. Θ(n³): en hızlı büyüyen terim belirler, sabitler atılır.
  3. ⌊log₂ 1024⌋ + 1 = 11 tur.
  4. i = 1, 2, 4, …: ⌈log₂ n⌉ tur → O(log n).
  5. En iyi: hedef ilk öğe, Θ(1). En kötü: hedef sonda ya da yok, Θ(n).
  6. 100n < n² ⇔ n > 100: büyük n için daha yavaş büyüyen algoritma kazanır.

Özet

Alıştırmalar

  1. Kod parçasının Büyük O'su nedir?
    for (int i = n; i > 1; i /= 2)
      for (int j = 0; j < n; j++) s++;
  2. f(n) = 3n² + 5n + 2 = O(n²) olduğunu c ve n₀ vererek gösterin.
  3. Katlama deneyinde n iki katına çıkınca süre ≈ 8 katına çıkıyor. T(n) hangi sınıfta?
  4. n = 5 için onekOrt1 ve onekOrt2 kaçar toplama yapar?
  5. for (i = 0; i < n; i++) for (j = i; j < n; j++) s++; sonunda s kaçtır?
  6. O(n²) bir algoritma n = 1000 için 2 saniye sürüyor. n = 4000 için süre yaklaşık kaçtır?
Cevaplar / ipuçları
  1. Dış döngü ⌊log₂ n⌋ tur, iç döngü n → O(n log n).
  2. n ≥ 1 için 5n ≤ 5n² ve 2 ≤ 2n² → f(n) ≤ 10n²: c = 10, n₀ = 1.
  3. 2³ = 8 → O(n³) (kübik).
  4. onekOrt1: 1+2+3+4+5 = 15; onekOrt2: 5.
  5. n + (n − 1) + … + 1 = n(n + 1)/2 → O(n²).
  6. n 4 katına çıkınca süre 4² = 16 katına: ≈ 32 saniye.

Özet

Kaynaklar

  • Goodrich, Tamassia ve Goldwasser, Data Structures and Algorithms in Java, 6. baskı, 2014.
  • Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, 2022.
  • Sedgewick ve Wayne, Algorithms, 4. baskı, 2011.

Son

Bölüm 2: Algoritma Analizi
Sonraki sunum: Bölüm 3: Diziler · Çalışma soruları · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.