Bölüm 1: Giriş
Giriş
Öğrenme Çıktıları
Algoritmanın tanımını ve özelliklerini (girdi, çıktı, açıklık, sonluluk, etkinlik) açıklamak; günlük bir işi algoritma olarak yazmak.
Çalışma süresini girdi boyutu n'nin fonksiyonu T(n) olarak ifade etmek; en kötü, ortalama ve en iyi durumu ayırt etmek.
O, Ω ve Θ gösterimlerinin üst, alt ve sıkı sınır anlamını kavramak; bir koddan karmaşıklık sınıfını (1, log n, n, n log n, n², n³, 2ⁿ, n!) okumak.
Araya ekleyerek sıralama, birleştirme, ikili arama, hızlı üs alma ve Euclid algoritmasını adım adım izlemek ve yineleme bağıntılarını kurmak.
Algoritma Kavramı
Algoritma Nedir?
- Bir problemi çözmek veya bir görevi yerine getirmek için izlenen adım adım yönergeler dizisidir.
- Girdi verileri alınır, işlemler yapılır, çıktı elde edilir.
- Her adım belirli, açık ve anlaşılır olmalı; algoritma sınırlı sayıda adımda sonlanmalıdır.
Algoritma Kavramı
Günlük Hayattan: Çay Hazırlama Algoritması
Girdi: su, çay yaprakları, şeker (isteğe bağlı), süt (isteğe bağlı)
Çıktı: çay
- Suyu kaynat.
- Kaynamış suyu demliğe koy.
- Demliğin içine çay yapraklarını ekle.
- Çayın demlenmesini bekle.
- Demlenen çayı fincana süzerek koy.
- İsteğe bağlı olarak şeker ve süt ekle.
Algoritma Kavramı
Ortalama Bulma: Algoritmadan Koda
1. Toplam = Sayı1 + Sayı2 + Sayı3
2. Ortalama = Toplam / 3
3. Ortalama değerini ekrana yazdır.
Python
def ortalama_hesapla(sayi1, sayi2, sayi3): ortalama = (sayi1 + sayi2 + sayi3) / 3 return ortalama sayi1 = float(input("Birinci sayıyı girin: ")) sayi2 = float(input("İkinci sayıyı girin: ")) sayi3 = float(input("Üçüncü sayıyı girin: ")) ort = ortalama_hesapla(sayi1, sayi2, sayi3) print("Girilen sayıların ortalaması:", ort)
Java
public static void main(String[] args) { Scanner giris = new Scanner(System.in); System.out.println("Birinci sayıyı girin: "); double sayi1 = giris.nextDouble(); System.out.println("İkinci sayıyı girin: "); double sayi2 = giris.nextDouble(); System.out.println("Üçüncü sayıyı girin: "); double sayi3 = giris.nextDouble(); double ortalama = (sayi1 + sayi2 + sayi3) / 3; System.out.println("Sayıların ortalaması: " + ortalama); }
Aynı algoritma farklı dillerde yazılabilir: algoritma dilden bağımsızdır, program onun bir gerçekleştirimidir.
Algoritma Kavramı
Algoritmalar Nerede? Başlıca Türler
Algoritma Analizi
Algoritma Analizi
- Bir algoritmanın performansını anlamak ve değerlendirmek için kullanılır.
- İşlemci ve bellek gibi kaynakların verimli kullanılmasını sağlar.
- Zaman analizi: algoritmanın çalışma süresini ölçer; genellikle Big-O gösterimiyle yazılır.
- Bellek analizi: algoritmanın ek bellek kullanımını (bellek karmaşıklığı) değerlendirir.
- Aynı problemi çözen algoritmalar arasında seçim yapmamızı sağlar.
doğrusal arama → en kötü 1.000.000 karşılaştırma
ikili arama → en fazla 20 karşılaştırma
Birleştirerek sıralama: O(n) yardımcı dizi.
Özyineleme: çağrı yığını kadar bellek.
Algoritma Analizi
Zaman Karmaşıklığı
- Bir algoritmanın çalışma süresini girdi boyutu n ile ilişkilendiren kavramdır: T(n).
- Saniye değil, temel işlem sayısı sayılır: saniye bilgisayara ve dile göre değişir, işlem sayısı değişmez.
- Veri büyüdükçe performansın nasıl değiştiğini inceler; bir algoritmanın iyi ya da kötü olduğunu belirlemede kritik faktördür.
Örnek: n elemanlı dizinin toplamı, satır satır sayım
int toplam = 0; // 1 kez for (int i = 0; i < n; i++) // n+1 karşılaştırma toplam += dizi[i]; // n kez return toplam; // 1 kez
Algoritma Analizi
Fonksiyonların Büyüme Hızları
n = 10'da bile n! (3,6 milyon) ve 2ⁿ (1024) grafiğin dışına çıkar; log₂ n ve √n ise n = 100'de hâlâ 10'un altındadır.
Algoritma Analizi
Büyüme Tablosu: n Büyürken
| f(n) | n = 10 | n = 100 | n = 1000 | n = 10⁶ | süre (n = 10⁶) |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 1 | 1 ns |
| log₂ n | 3,3 | 6,6 | 10,0 | 19,9 | 19,9 ns |
| √n | 3,2 | 10 | 31,6 | 1.000 | 1 µs |
| n | 10 | 100 | 1.000 | 1.000.000 | 1 ms |
| n log₂ n | 33,2 | 664,4 | 9.966 | 2,0 · 10⁷ | 19,9 ms |
| n² | 100 | 10.000 | 1.000.000 | 1,0 · 10¹² | 16,7 dk |
| 2ⁿ | 1.024 | 1,3 · 10³⁰ | ≈ 1,1 · 10³⁰¹ | ≈ 10³⁰¹⁰³⁰ | evrenin yaşından çok |
| n! | 3.628.800 | 9,3 · 10¹⁵⁷ | ≈ 10²⁵⁶⁷ | ≈ 10⁵⁵⁶⁵⁷⁰⁸ | evrenin yaşından çok |
Son sütun: saniyede 10⁹ temel işlem yapan bir bilgisayarda n = 10⁶ için tahmini süre. 2ⁿ ve n! için n = 100 bile (≈ 10³⁰ ve 10¹⁵⁸ işlem) pratikte çözülemez.
Algoritma Analizi
Deneyin: Büyüme Hesaplayıcı
| f(n) | işlem sayısı | 10⁹ işlem/s ile süre | bağıl |
|---|
"n × 2" düğmesiyle girdiyi ikiye katlayın: n sütunu 2 katına, n² 4 katına, n³ 8 katına çıkar; log₂ n yalnızca 1 artar, 2ⁿ ise karesini alır.
Algoritma Analizi
Asimptotik Gösterimler: O, Ω, Θ
Karmaşıklık Örnekleri
Doğrusal Arama (Linear Search)
int dogrusalAra(int[] dizi, int aranan) { for (int i = 0; i < dizi.length; i++) { if (dizi[i] == aranan) { return i; // bulundu! } } return -1; // bulunamadı }
T(n) = c₁ · n + c₂
c₁ ve c₂ bilgisayarın hızı, programlama dili gibi etkenlere bağlıdır.
k₁ · n ≤ T(n) ≤ k₂ · n.
En iyi durum: hedef ilk eleman → 1 karşılaştırma, Θ(1).
Karmaşıklık Örnekleri
Sabit ve Doğrusal Zaman: O(1), O(n)
İlk elemanı yazdırma T(n) = O(1)
void ilkElemaniYazdir(int[] dizi) { if (dizi.length > 0) System.out.println("İlk eleman: " + dizi[0]); else System.out.println("Dizi boş"); }
Dizi 10 elemanlı da olsa 10 milyon elemanlı da olsa tek karşılaştırma ve tek erişim yapılır: süre n'den bağımsızdır.
En büyük elemanı bulma T(n) = O(n)
Integer enBuyukBul(int[] dizi) { if (dizi.length == 0) return null; // dizi boşsa int enBuyuk = dizi[0]; for (int eleman : dizi) if (eleman > enBuyuk) enBuyuk = eleman; return enBuyuk; }
Her eleman bir kez incelenir: n karşılaştırma. Ek bellek O(1). Örnek: [5, 8, 12, 4] → 12.
Karmaşıklık Örnekleri
Logaritmik Zaman: İkili Arama, O(log n)
int ikiliArama(int[] dizi, int aranan) { int alt = 0, ust = dizi.length - 1; while (alt <= ust) { int orta = (alt + ust) / 2; int tahmin = dizi[orta]; if (tahmin == aranan) return orta; // bulundu else if (tahmin < aranan) alt = orta + 1; else ust = orta - 1; } return -1; // bulunamadı }
Dizi sıralı olmalıdır. Her adımda aranan aralık yarıya iner: n → n/2 → n/4 → … → 1.
| n | en fazla adım |
|---|---|
| 8 | 4 |
| 1.000 | 10 |
| 1.000.000 | 20 |
| 1,0 · 10⁹ | 30 |
En fazla ⌊log₂ n⌋ + 1 adım → T(n) = O(log n). Adım adım örnek: Böl ve Fethet bölümünde.
Karmaşıklık Örnekleri
Asal Sayı Kontrolü: O(√n)
boolean asalMi(int n) { if (n <= 1) return false; else if (n == 2) return true; else if (n % 2 == 0) return false; int kokN = (int) Math.sqrt(n) + 1; for (int i = 3; i < kokN; i += 2) if (n % i == 0) return false; return true; }
n = 97: i = 3, 5, 7, 9 → 4 bölme (2'den 96'ya denense 95).
n = 1.000.000.007: 15.810 bölme.
Not: burada girdi boyutu n sayısının değeridir. Sayının bit uzunluğu b ise √n = 2b/2: bu yöntem bit sayısına göre üstel büyür.
Karmaşıklık Örnekleri
n log n Zaman: Çabuk (Hızlı) Sıralama
List<Integer> hizliSirala(List<Integer> liste) {
if (liste.size() <= 1)
return liste;
int pivot = liste.get(0);
List<Integer> kucukler = new ArrayList<>();
List<Integer> buyukler = new ArrayList<>();
for (int x : liste.subList(1, liste.size()))
if (x <= pivot) kucukler.add(x);
else buyukler.add(x);
List<Integer> sonuc = hizliSirala(kucukler);
sonuc.add(pivot);
sonuc.addAll(hizliSirala(buyukler));
return sonuc;
}İlk eleman pivot; küçük-eşitler solda, büyükler sağda özyinelemeli sıralanır ve birleştirilir.
Hızlı sıralamanın O(n log n) süresi ortalama durumdur; rastgele pivot seçimi en kötü durumu olası olmaktan çıkarır (Bölüm 2).
Karmaşıklık Örnekleri
Karesel Zaman: Kabarcık Sıralama, O(n²)
void kabarcikSirala(int[] dizi) { int n = dizi.length; for (int i = 0; i < n; i++) for (int j = 0; j < n - i - 1; j++) if (dizi[j] > dizi[j + 1]) { int gecici = dizi[j]; // takas dizi[j] = dizi[j + 1]; dizi[j + 1] = gecici; } }
Komşu elemanlar karşılaştırılır, sırasızsa takas edilir. Her dış turda en büyük eleman sona "kabarcık gibi" çıkar.
(n−1) + (n−2) + … + 1 + 0 = n(n−1)/2 karşılaştırma.
n = 1000 → 499.500 karşılaştırma; n iki katına çıkınca süre ≈ 4 katına çıkar. T(n) = O(n²).
Karmaşıklık Örnekleri
Kübik Zaman: Matris Çarpımı, Θ(n³)
int[][] matrisCarp(int[][] A, int[][] B) { int n = A.length, m = B[0].length, p = B.length; int[][] sonuc = new int[n][m]; for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) { int eleman = 0; for (int k = 0; k < p; k++) eleman += A[i][k] * B[k][j]; sonuc[i][j] = eleman; } return sonuc; }
Girdi: A = [aᵢⱼ], B = [bᵢⱼ] (n × n). Çıktı: C = [cᵢⱼ] = A · B, i, j = 1, 2, …, n.
| 1 | 2 |
| 3 | 4 |
| 5 | 6 |
| 7 | 8 |
| 19 | 22 |
| 43 | 50 |
c₁₁ = 1·5 + 2·7 = 19. Her cᵢⱼ için n çarpma, n² eleman → n³ çarpma: T(n) = Θ(n³).
Karmaşıklık Örnekleri
Faktöriyel Zaman: Tüm Permütasyonları Üretme
void permutasyon(int[] dizi, int k) { if (k == dizi.length) { // bir permütasyon hazır System.out.println(Arrays.toString(dizi)); return; } for (int i = k; i < dizi.length; i++) { takas(dizi, k, i); // i. elemanı k. konuma al permutasyon(dizi, k + 1); // geri kalanları diz takas(dizi, k, i); // eski hâline getir } }
[1, 2, 3] → 123, 132, 213, 231, 321, 312. İlk konum için n, ikinci için n−1, … seçenek: n · (n−1) · … · 1 = n! permütasyon.
| n | n! |
|---|---|
| 3 | 6 |
| 5 | 120 |
| 10 | 3.628.800 |
| 15 | 1,3 · 10¹² |
| 20 | 2,4 · 10¹⁸ |
Her permütasyonu yazmak n adım sürer: toplam Θ(n · n!), kısaca "O(n!) sınıfı". n = 20 bile pratikte imkânsızdır.
Karmaşıklık Örnekleri
Üstel Zaman: Fibonacci Serisi
long fib(int n) { // saf özyineleme if (n <= 1) return n; return fib(n - 1) + fib(n - 2); } long[] bellek = new long[N + 1]; // 0: yok long fibBellek(int n) { // ezberleme if (n <= 1) return n; if (bellek[n] == 0) bellek[n] = fibBellek(n - 1) + fibBellek(n - 2); return bellek[n]; }
F(0) = 0, F(1) = 1, F(n) = F(n−1) + F(n−2).
| n | fib çağrısı | fibBellek |
|---|---|---|
| 5 | 15 | 9 |
| 10 | 177 | 19 |
| 20 | 21.891 | 39 |
| 30 | 2.692.537 | 59 |
fib: her çağrı iki çağrı doğurur → O(2ⁿ) (kesin olarak Θ(1,618ⁿ)).
fibBellek: her F(k) bir kez hesaplanır, 2n − 1 çağrı → O(n).
Karmaşıklık Örnekleri
Fibonacci: Çağrı Ağacı fib(5)
Saf özyineleme: 15 çağrı. f3 iki kez, f2 üç kez, f1 beş kez hesaplanır; ağaç her düzeyde yaklaşık ikiye katlanır → üstel büyüme.
Ezberlemeli: 9 çağrı. Turuncu kesikli düğümler (f2, f3) bellek dizisinden hazır döner, alt ağaçları hiç açılmaz → O(n).
Karmaşıklık Örnekleri
Karmaşıklık Sınıfları: Örneklerle
| Sınıf | Adı | Örnek | n iki katına çıkınca süre |
|---|---|---|---|
| O(1) | sabit | ilk elemanı yazdırma, dizi erişimi | değişmez |
| O(log n) | logaritmik | ikili arama, hızlı üs alma, Euclid | +1 adım |
| O(√n) | karekök | asal sayı kontrolü (deneme bölmesi) | ≈ 1,41 katı |
| O(n) | doğrusal | doğrusal arama, en büyük eleman, birleştirme | 2 katı |
| O(n log n) | doğrusal-logaritmik | birleştirerek sıralama, hızlı sıralama (ortalama) | 2 katından biraz fazla |
| O(n²) | karesel | kabarcık, araya ekleyerek sıralama | 4 katı |
| O(n³) | kübik | klasik matris çarpımı | 8 katı |
| O(2ⁿ) | üstel | saf özyinelemeli Fibonacci, alt kümeler | karesi alınır |
| O(n!) | faktöriyel | tüm permütasyonlar | çok daha hızlı patlar |
Yukarıdan aşağıya büyüme hızı artar: 1 < log n < √n < n < n log n < n² < n³ < 2ⁿ < n!. Polinom sınıflar (nk) genelde "verimli", üstel olanlar "verimsiz" sayılır.
Araya Ekleyerek Sıralama
Sıralama Problemi
- Girdi: n sayıdan oluşan dizi ⟨a₁, a₂, …, aₙ⟩.
- Çıktı: aynı sayıların bir permütasyonu ⟨a′₁, a′₂, …, a′ₙ⟩ öyle ki a′₁ ≤ a′₂ ≤ … ≤ a′ₙ.
Araya Ekleyerek Sıralama
Araya Ekleyerek Sıralama (Insertion Sort)
- Diziyi sıralamak için iç içe iki döngü kullanır.
- Dış döngü dizinin her elemanını bir kez geçer: A[i]
anahtardeğişkenine alınır. - İç döngü anahtardan büyük elemanları birer sağa kaydırır ve anahtarı uygun konuma yerleştirir.
- İskambil kâğıtlarını elde sıralamak gibi: her yeni kâğıt, sıralı eldeki yerine sokulur.
Değişmez (invariant): her dış adımın başında A[0..i−1] sıralıdır. Zaman karmaşıklığı (en kötü) O(n²).
Araya Ekleyerek Sıralama
Araya Ekleyerek Sıralama: Kod
public void Sirala(int[] A) { for (int i = 1; i < A.length; ++i) { int anahtar = A[i]; int j = i - 1; // A[0..i-1] alt dizisi sıralı. // anahtar uygun konuma yerleştirilir. while (j >= 0 && A[j] > anahtar) { A[j + 1] = A[j]; j = j - 1; } A[j + 1] = anahtar; } }
Araya Ekleyerek Sıralama
Örnek: 8 2 4 9 3 6
Başlangıç: 8 tek başına sıralıdır (yeşil). Anahtar 2: 8'den küçük → 8 sağa kayar, 2 başa girer.
1. ekleme sonrası: 2 8 4 9 3 6. Sıradaki anahtar 4: kendinden büyük 1 eleman sağa kayar, 1. konuma girer.
2. ekleme sonrası: 2 4 8 9 3 6. Sıradaki anahtar 9: solundaki 8'ten büyük, yerinde kalır.
3. ekleme sonrası: 2 4 8 9 3 6. Sıradaki anahtar 3: kendinden büyük 3 eleman sağa kayar, 1. konuma girer.
4. ekleme sonrası: 2 3 4 8 9 6. Sıradaki anahtar 6: kendinden büyük 2 eleman sağa kayar, 3. konuma girer.
Son eleman da yerine girdi: 2 3 4 6 8 9 — sıralama bitti (n − 1 = 5 dış adım).
Araya Ekleyerek Sıralama · Kodu adım adım çalıştır
Sirala(A): Kodu Adım Adım İzle
Dizi: 8 2 4 9 3 6. A[0] = 8 tek başına sıralı kabul edilir; dış döngü i = 1'den başlar.
i = 1: anahtar = A[1] = 2, j = 0. Sıralı kısım: 8.
A[0] = 8 > 2 → A[1] = A[0] (sağa kaydır), j = -1.
Koşul yanlış: j = −1 (başa gelindi). A[0] = anahtar = 2. A[0..1] sıralı: 2 8.
i = 2: anahtar = A[2] = 4, j = 1. Sıralı kısım: 2 8.
A[1] = 8 > 4 → A[2] = A[1] (sağa kaydır), j = 0.
Koşul yanlış: A[0] = 2 ≤ 4. A[1] = anahtar = 4. A[0..2] sıralı: 2 4 8.
i = 3: anahtar = A[3] = 9, j = 2. Sıralı kısım: 2 4 8.
Koşul yanlış: A[2] = 8 ≤ 9. A[3] = anahtar = 9. A[0..3] sıralı: 2 4 8 9.
i = 4: anahtar = A[4] = 3, j = 3. Sıralı kısım: 2 4 8 9.
A[3] = 9 > 3 → A[4] = A[3] (sağa kaydır), j = 2.
A[2] = 8 > 3 → A[3] = A[2] (sağa kaydır), j = 1.
A[1] = 4 > 3 → A[2] = A[1] (sağa kaydır), j = 0.
Koşul yanlış: A[0] = 2 ≤ 3. A[1] = anahtar = 3. A[0..4] sıralı: 2 3 4 8 9.
i = 5: anahtar = A[5] = 6, j = 4. Sıralı kısım: 2 3 4 8 9.
A[4] = 9 > 6 → A[5] = A[4] (sağa kaydır), j = 3.
A[3] = 8 > 6 → A[4] = A[3] (sağa kaydır), j = 2.
Koşul yanlış: A[2] = 4 ≤ 6. A[3] = anahtar = 6. A[0..5] sıralı: 2 3 4 6 8 9.
i = 6 = A.length → döngü biter. Sonuç: 2 3 4 6 8 9. Toplam 11 karşılaştırma, 7 kaydırma.
public void Sirala(int[] A) { for (int i = 1; i < A.length; ++i) { int anahtar = A[i]; int j = i - 1; // A[0..i-1] sıralı // anahtar yerine girer while (j >= 0 && A[j] > anahtar) { A[j + 1] = A[j]; j = j - 1; } A[j + 1] = anahtar; } }
Araya Ekleyerek Sıralama
Çalışma Süresi ve Analiz Türleri
- Girdi durumuna bağlı: dizi zaten sıralıysa sıralamak kolaydır.
- Girdi boyutuna bağlı: az elemanlı dizi kısa sürede sıralanır → T(n).
- En kötü durum (genellikle): n boyutlu herhangi bir girdi için maksimum süre. Herkes garanti ister.
- Ortalama durum (bazen): tüm girdiler üzerinden beklenen süre; girdi dağılımı hakkında varsayım gerekir.
- En iyi durum (yanıltıcı): özel, "hileli" bir girdide geçen süre; tek başına bir şey kanıtlamaz.
Araya ekleme: sıralı girdi → n−1 karşılaştırma; tersten sıralı girdi → n(n−1)/2. Örnek dizimizde: 11 karşılaştırma.
Araya Ekleyerek Sıralama
Dikkat: Durum ve Sınır Farklı Kavramlardır
| Araya ekleyerek sıralama | Sıkı sınır | Yorum |
|---|---|---|
| En iyi durum (sıralı girdi) | Θ(n) | n−1 karşılaştırma |
| En kötü durum (ters sıralı girdi) | Θ(n²) | n(n−1)/2 karşılaştırma |
| Tüm girdiler için | O(n²) ve Ω(n) | tek bir Θ yazılamaz |
Araya Ekleyerek Sıralama
Makineden Bağımsız Çalışma Süresi
- Araya ekleyerek sıralamanın en kötü durumu O(n²): girdi tersten sıralıysa gerçekleşir.
- Bilgisayarın hızına göre süre değişir; asimptotik analiz büyüme oranına odaklanır, makineye bağlı sabit çarpanlar göz ardı edilir.
- n → ∞ giderken T(n)'nin büyüme oranına bakılır: yalnızca en önemli (üssü en büyük) terim değerlendirilir.
- T(n) = 3n² + 2n + 5 = O(n²)
| n | 3n²+2n+5 | 3n² | oran |
|---|---|---|---|
| 10 | 325 | 300 | 1,0833 |
| 100 | 30.205 | 30.000 | 1,0068 |
| 1.000 | 3.002.005 | 3.000.000 | 1,0007 |
n büyüdükçe düşük dereceli terimlerin payı sıfıra yaklaşır. 3 katsayısı ise makineye bağlıdır: hızlı bilgisayarda 1, yavaşta 10 olabilir → atılır.
Araya Ekleyerek Sıralama
Θ Gösterimi: Tanım ve Uygulama
Mühendislik kuralı:
- T(n) = 3n³ + 90n² − 5n + 6046
- Düşük kuvvetli terimleri sil: 3n³
- Katsayıları kaldır: T(n) = Θ(n³)
c₂ = 4: T(n) ≤ 4n³ ⇔ 90n² − 5n + 6046 ≤ n³, bu da n ≥ 91 için doğrudur.
⇒ c₁ = 3, c₂ = 4, n₀ = 91 ile T(n) = Θ(n³).
O ve Ω'nın biçimsel tanımları, limit yöntemi ve özellikleri Bölüm 1.2: Karmaşıklık'ta ele alınır.
Böl ve Fethet
Böl ve Fethet (Divide and Conquer)
| Algoritma | Böl | Fethet | Birleştir | T(n) |
|---|---|---|---|---|
| Birleştirerek sıralama | ortadan ikiye | 2 yarıyı sırala | birleştirme: Θ(n) | 2T(n/2) + Θ(n) |
| İkili arama | ortayı kontrol et | 1 yarıda ara | önemsiz: Θ(1) | T(n/2) + Θ(1) |
| Üs alma aⁿ | n'yi ikiye böl | a⌊n/2⌋ hesapla | kare al: Θ(1) | T(n/2) + Θ(1) |
Böyle özyinelemeli tanımlanan süre fonksiyonlarına yineleme bağıntısı (recurrence) denir. Çözüm yöntemleri (özyineleme ağacı, ana teorem) Bölüm 1.2'de.
Böl ve Fethet
Birleştirerek Sıralama (Merge Sort)
| Adım | Maliyet |
|---|---|
| BirleştirSırala A[1 . . n] | T(n) |
| 1. n = 1 ise işlem tamam. | |
| 2. liste1 = BirleştirSırala A[1 . . ⌈n/2⌉] | |
| 3. liste2 = BirleştirSırala A[⌈n/2⌉+1 . . n] | |
| 4. İki sıralı listeyi birleştir. |
Birleştirerek sıralama (Merge Sort): n elemanlı diziyi sıralamanın süresi T(n) olsun.
| Adım | Maliyet |
|---|---|
| BirleştirSırala A[1 . . n] | T(n) |
| 1. n = 1 ise işlem tamam. | Θ(1) |
| 2. liste1 = BirleştirSırala A[1 . . ⌈n/2⌉] | |
| 3. liste2 = BirleştirSırala A[⌈n/2⌉+1 . . n] | |
| 4. İki sıralı listeyi birleştir. |
n = 1 ise dizi zaten sıralıdır: sabit süre, Θ(1).
| Adım | Maliyet |
|---|---|
| BirleştirSırala A[1 . . n] | T(n) |
| 1. n = 1 ise işlem tamam. | Θ(1) |
| 2. liste1 = BirleştirSırala A[1 . . ⌈n/2⌉] | T(n/2) |
| 3. liste2 = BirleştirSırala A[⌈n/2⌉+1 . . n] | |
| 4. İki sıralı listeyi birleştir. |
Sol yarıyı sıralamak, aynı problemin n/2 boyutlu hâlidir: T(n/2).
| Adım | Maliyet |
|---|---|
| BirleştirSırala A[1 . . n] | T(n) |
| 1. n = 1 ise işlem tamam. | Θ(1) |
| 2. liste1 = BirleştirSırala A[1 . . ⌈n/2⌉] | T(n/2) |
| 3. liste2 = BirleştirSırala A[⌈n/2⌉+1 . . n] | T(n/2) |
| 4. İki sıralı listeyi birleştir. |
Sağ yarı da T(n/2) sürer.
| Adım | Maliyet |
|---|---|
| BirleştirSırala A[1 . . n] | T(n) |
| 1. n = 1 ise işlem tamam. | Θ(1) |
| 2. liste1 = BirleştirSırala A[1 . . ⌈n/2⌉] | T(n/2) |
| 3. liste2 = BirleştirSırala A[⌈n/2⌉+1 . . n] | T(n/2) |
| 4. İki sıralı listeyi birleştir. | Θ(n) |
İki sıralı listeyi birleştirmek her elemanı bir kez yazar: Θ(n). Böylece T(n) = 2T(n/2) + Θ(n).
Böl ve Fethet · Kodu adım adım çalıştır
Birleştirme İşlemi (Merge): Adım Adım
İki sıralı liste: A = 2 7 13 20, B = 1 9 11 12 (en küçükler altta). Yalnızca iki listenin en önündeki (sarı) elemanlar karşılaştırılır.
A[0] = 2 > B[0] = 1 → 1, C'ye yazılır; j ilerler.
A[0] = 2 ≤ B[1] = 9 → 2, C'ye yazılır; i ilerler.
A[1] = 7 ≤ B[1] = 9 → 7, C'ye yazılır; i ilerler.
A[2] = 13 > B[1] = 9 → 9, C'ye yazılır; j ilerler.
A[2] = 13 > B[2] = 11 → 11, C'ye yazılır; j ilerler.
A[2] = 13 > B[3] = 12 → 12, C'ye yazılır; j ilerler.
B bitti: A'da kalan 13 doğrudan C'ye kopyalanır (karşılaştırma yok).
B bitti: A'da kalan 20 doğrudan C'ye kopyalanır (karşılaştırma yok). Sonuç: 1 2 7 9 11 12 13 20. Her eleman bir kez yazıldı → Θ(n).
int i = 0, j = 0, k = 0; while (i < A.length && j < B.length) { if (A[i] <= B[j]) C[k++] = A[i++]; else C[k++] = B[j++]; } while (i < A.length) C[k++] = A[i++]; while (j < B.length) C[k++] = B[j++];
Böl ve Fethet
Birleştirerek Sıralama: Neden Θ(n log n)?
- n elemanlı listeyi birleştirmek Θ(n) sürer (yukarıdaki örnekte 8 eleman, 8 yazma, 6 karşılaştırma).
- Her düzeyde alt problemlerin toplam boyutu n'dir → her düzeyin toplam birleştirme işi c·n.
- Boyut her düzeyde yarıya iner: 1'e inmek için log₂ n bölme gerekir.
- Araya ekleyerek sıralamanın Θ(n²) en kötü durumuna karşı, birleştirerek sıralama her girdide Θ(n log n)'dir; bedeli O(n) ek bellektir.
Böl ve Fethet · Kodu adım adım çalıştır
İkili Arama: 9'u Bul
Örnek: 9'u bul. Dizi sıralı; aralık tüm dizi: alt = 0, ust = 6.
orta = (0 + 6) / 2 = 3, tahmin = dizi[3] = 8.
8 < 9 → hedef sağ yarıda: alt = 4. Sol yarı elendi.
orta = (4 + 6) / 2 = 5, tahmin = dizi[5] = 12.
12 > 9 → hedef sol yarıda: ust = 4. Sağ taraf elendi.
orta = (4 + 4) / 2 = 4, tahmin = dizi[4] = 9.
tahmin == aranan → return 4. Yalnızca 3 karşılaştırma (⌊log₂ 7⌋ + 1 = 3).
int ikiliArama(int[] dizi, int aranan) { int alt = 0, ust = dizi.length - 1; while (alt <= ust) { int orta = (alt + ust) / 2; int tahmin = dizi[orta]; if (tahmin == aranan) return orta; // bulundu else if (tahmin < aranan) alt = orta + 1; else ust = orta - 1; } return -1; // bulunamadı }
Böl ve Fethet
İkili Arama: Böl ve Fethet Analizi
| İkili arama (Binary Search): sıralı listede bir elemanı arar | Maliyet |
|---|---|
| Böl (Divide): orta elemanı kontrol et. | Θ(1) |
| Fethet (Conquer): seçilen alt diziyi özyinelemeli olarak ara. | T(n/2) |
| Birleştir (Combine): önemsiz (trivial); alt dizinin cevabı asıl cevaptır. | Θ(1) |
Açılım: T(n) = T(n/2) + c = T(n/4) + 2c = … = T(1) + c · log₂ n ⇒ T(n) = Θ(log n)
Böl ve Fethet
Bir Sayının Üssünü Alma: aⁿ
Problem: aⁿ'i hesapla (n ∈ ℕ). Doğal algoritma: aⁿ = a · a · … · a → Θ(n) çarpma.
Böl ve fethet:
aⁿ = a(n−1)/2 · a(n−1)/2 · a (n tek ise)
long us(long a, int n) { if (n == 0) return 1; long yarim = us(a, n / 2); // a^(n/2), tek kez! if (n % 2 == 0) return yarim * yarim; // n çift else return a * yarim * yarim; // n tek }
| n | doğal | böl-fethet |
|---|---|---|
| 10 | 10 | 6 |
| 62 | 62 | 11 |
| 1.000 | 1.000 | 16 |
| 1.000.000 | 1.000.000 | 27 |
çarpma sayıları (yukarıdaki kodla). Örn. 2¹⁰: n = 10 → 5 → 2 → 1 → 0.
⇒ T(n) = Θ(lg n)
Euclid Algoritması
Euclid Algoritması (EBOB)
- En büyük ortak böleni (EBOB, gcd) bulur.
- Bilinen en eski algoritmalardan biridir (Öklid, MÖ ~300); en basit EBOB yöntemi.
- Temel fikir: ebob(a, b) = ebob(b, a mod b), ebob(a, 0) = a.
- Adım 1: büyük sayıyı küçüğe böl, kalanı al.
- Adım 2: kalan sıfır olana kadar (bölen, kalan) çiftiyle tekrarla.
- Adım 3: kalan sıfır olduğunda son bölen EBOB'dur.
int ebob(int a, int b) { if (b == 0) return a; return ebob(b, a % b); }
Euclid Algoritması · Kodu adım adım çalıştır
ebob(1599, 650): Adım Adım
1599 = 2 · 650 + 299. 1599×650 dikdörtgenden 2 kare kesilir, kalan şerit 650×299 → ebob(650, 299).
650 = 2 · 299 + 52. 650×299 dikdörtgenden 2 kare kesilir, kalan şerit 299×52 → ebob(299, 52).
299 = 5 · 52 + 39. 299×52 dikdörtgenden 5 kare kesilir, kalan şerit 52×39 → ebob(52, 39).
52 = 1 · 39 + 13. 52×39 dikdörtgenden 1 kare kesilir, kalan şerit 39×13 → ebob(39, 13).
39 = 3 · 13 + 0. 39×13 dikdörtgenden 3 kare kesilir, kalan şerit 13×0 → ebob(13, 0).
b == 0 → return 13. 13, 1599 ile 650'yi tam bölen en büyük sayıdır.
Dönüş değeri 13 tüm çağrılardan aynen geri döner: ebob(1599, 650) = 13. 5 bölme yetti. 13 × 13'lük kareler 1599 × 650'yi tam döşer.
int ebob(int a, int b) { if (b == 0) return a; return ebob(b, a % b); }
Euclid Algoritması
Euclid Algoritması: Neden Hızlı?
- Verimli ve hızlı çalışır: her iki adımda büyük sayı en az yarıya iner (a mod b < a/2).
- Bu yüzden adım sayısı en fazla ≈ 2 log₂ min(a, b): O(log min(a, b)).
- En kötü senaryo ardışık Fibonacci sayılarıyla oluşur: her bölümde q = 1 çıkar, sayılar en yavaş küçülür (Lamé teoremi).
- Karşılaştırma: 1'den min(a, b)'ye kadar tüm bölenleri denemek O(min(a, b)) sürer.
34 = 1 · 21 + 13
21 = 1 · 13 + 8
13 = 1 · 8 + 5
8 = 1 · 5 + 3
5 = 1 · 3 + 2
3 = 1 · 2 + 1
2 = 2 · 1 + 0
8 bölme, EBOB = 1. Karşılaştırın: ebob(1599, 650) yalnızca 5 bölme.
Özet
Özet
| Konu | Hatırlanacaklar |
|---|---|
| Algoritma | girdi → açık, sonlu, etkin adımlar → çıktı; dilden bağımsızdır |
| Analiz | T(n): girdi boyutuna göre temel işlem sayısı; zaman ve bellek |
| Durumlar | en kötü (garanti), ortalama (beklenen), en iyi (yanıltıcı) |
| Gösterimler | O üst, Ω alt, Θ sıkı sınır; sabitler ve düşük terimler atılır |
| Sınıflar | 1 < log n < √n < n < n log n < n² < n³ < 2ⁿ < n! |
| Araya ekleme | en iyi Θ(n), en kötü Θ(n²), yerinde, O(1) ek bellek |
| Böl ve fethet | birleştirerek sıralama 2T(n/2) + Θ(n) = Θ(n log n); ikili arama ve üs alma T(n/2) + Θ(1) = Θ(log n) |
| Euclid | ebob(a, b) = ebob(b, a mod b); O(log min(a, b)); en kötü: ardışık Fibonacci |
Özet
Etkileşimli Simülatörler
Özet
Sık Yapılan Hatalar
Özet
Örnek Problemler
- Araya ekleyerek sıralama [5, 4, 3, 2, 1] ve [1, 2, 3, 4, 5] dizilerinde kaç karşılaştırma ve kaç kaydırma yapar?
- T(n) = 5n² + 100n log₂ n + 7 için Θ sınıfını yazın. n = 1000'de hangi terim baskındır?
- [2, 4, 7, 10, 13, 18, 21, 25, 30] dizisinde ikili arama ile 21 ve 5'i arayın; incelenen orta elemanları yazın.
- ebob(252, 105)'i Euclid algoritmasıyla bulun.
- 2¹³ yukarıdaki
usfonksiyonuyla hesaplanırken hangi üsler için çağrı yapılır, kaç çarpma gerekir?
Cevapları göster
- [5,4,3,2,1]: 10 karşılaştırma, 10 kaydırma (en kötü, n(n−1)/2). [1,2,3,4,5]: 4 karşılaştırma, 0 kaydırma (en iyi, n−1).
- Θ(n²). n = 1000: 5n² = 5.000.000, 100n log₂ n ≈ 996.578 → 5n² baskın.
- 21: orta elemanlar 13, 21 → indis 6. 5: 13, 4, 7 → bulunamadı (−1).
- 252 = 2·105 + 42; 105 = 2·42 + 21; 42 = 2·21 + 0 → EBOB = 21.
- n = 13 → 6 → 3 → 1 → 0; çarpma: 7 (tek üslerde 2, çiftte 1). Doğal yöntem 13 çarpma.
Özet
Alıştırmalar
- Bir dizide verilen bir sayının kaç kez geçtiğini bulan algoritmayı yazın; zaman ve bellek karmaşıklığını verin.
- İç içe döngüler:
for i in 0..n-1: for j in i..n-1: işlemkaç kez çalışır? Sınıfı nedir? - Araya ekleyerek sıralamayı azalan sırada sıralayacak şekilde değiştirin. Hangi satır değişir?
- Euclid algoritmasını döngüyle (özyinelemesiz) yazın.
- Saniyede 10⁹ işlemle 1 saniyede çözülebilecek en büyük n: f(n) = n² ve f(n) = 2ⁿ için bulun.
while (n > 1) n = n / 2;döngüsü kaç kez döner?
Cevaplar / ipuçları
- Tek geçiş, sayaç: zaman Θ(n), ek bellek O(1).
- n + (n−1) + … + 1 = n(n+1)/2 → Θ(n²).
- Yalnızca
A[j] > anahtarkoşuluA[j] < anahtarolur. while (b != 0) { int r = a % b; a = b; b = r; } return a;- n² ≤ 10⁹ → n ≈ 31.622; 2ⁿ ≤ 10⁹ → n = 29.
- ⌊log₂ n⌋ kez → Θ(log n).
Özet
Kaynaklar
- Cormen, Leiserson, Rivest ve Stein, Introduction to Algorithms, 4. baskı, MIT Press, 2022 (Bölüm 1–3).
- Sedgewick ve Wayne, Algorithms, 4. baskı, Addison-Wesley, 2011 (Bölüm 1.4: Analysis of Algorithms).
- D. E. Knuth, The Art of Computer Programming, Vol. 1: Fundamental Algorithms, 3. baskı, 1997 (algoritmanın özellikleri; Euclid).
- J. Kleinberg ve É. Tardos, Algorithm Design, Pearson, 2005 (Bölüm 2: Basics of Algorithm Analysis).