Bölüm 1: Giriş ve Algoritma Analizi

C ile Veri Yapıları ve Algoritmalar
Sistem yaşam döngüsüAlgoritma ve ADTSeçimli sıralamaBellek ve zamanO, Ω, ΘSihirli karePerformans ölçümü
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Süreci tanımak

Sistem yaşam döngüsünün beş aşamasını ve her aşamada veri yapısı kararlarının yerini açıklamak.

Soyutlamak

Algoritma ve soyut veri tipi (ADT) kavramlarını tanımlamak; C'de başlık dosyası ile arayüz kurmak.

Saymak

Bir C yordamının bellek gereksinimini ve adım sayısını çıkarmak (sum, rsum, add).

Sınıflandırmak

Adım sayısını O, Ω ve Θ ile ifade etmek; en iyi, ortalama ve en kötü durumu ayırmak.

Karşılaştırmak

Alışılmış karmaşıklık sınıflarının n büyüdükçe nasıl ayrıştığını yorumlamak.

Ölçmek

clock() ile işletim süresini ölçmek ve ölçüm tuzaklarını (saat vuruşu, çıktı) tanımak.

Derse Giriş · kitaplarda yazmaz!

Ders Kitapları ve Kaynaklar

Başucu kitabı

Kernighan ve Ritchie, The C Programming Language. C'nin tasarımcısının kitabı; her an el altında bulunması gereken bir başvuru kaynağı.

Ders kitabı

Horowitz, Sahni ve Anderson-Freed, Fundamentals of Data Structures in C.

Fundamentals ofData StructuresSPARKS diliFund. of DataStructures in Cdersin ana kitabıFund. of DataStructures in C++en kapsamlısı

Aynı kitabın üç sürümü: içerik hemen hemen aynı, yalnızca kodların dili değişiyor.

Ek kaynaklar: Cormen ve diğ. (CLRS) Introduction to Algorithms; Sedgewick, Algorithms in C.

Derse Giriş · Okuma parçası

Okuma Parçası: Meslek Sevgisi

Arkeolog

Bugün yaşıyor ama aklı 5500 yıl öncesinde: “Firavunun karısı oraya bu sebeple gömülmüş olamaz” diyebiliyor.

Astronom

Kara deliği ispatlamak için yıllarca fotoğraf çekiyor; bir yıldızın bir piksel oynamasına seviniyor.

Taraftar

8 saat yol, 150 TL bilet, taş ve değnek riski… Ama konferans için aynı yolu gideni “aptal” sayıyoruz.

Hocadan“Meslek çok sevilmeli. Mesleğini seveni herkes dinler; elinin ucuyla anlatanı kimse dinlemez. Kodda da, kodun içinde yaşıyormuş gibi ne olup bittiği anlaşılmalı.”

“Bir ömür mutlu olmanın yolu, işini sevmektir.” — Çin atasözü

Temeller

Sistem Yaşam Döngüsü (System Life Cycle)

Yazılımlar, aşamaları aşağıdaki gibi olan bir süreçten geçer:

GereksinimlerRequirementsÇözümlemeAnalysisTasarımDesignİnceltme veKodlamaDoğrulamaVerificationhata bulunursa tasarım / kodlama kararlarına geri dönülür

Gereksinimler: Proje, amacını belirten şartlarla başlar: programcıya hangi girdiler verilecek, hangi çıktılar üretilecek?

GereksinimlerRequirementsÇözümlemeAnalysisTasarımDesignİnceltme veKodlamaDoğrulamaVerificationhata bulunursa tasarım / kodlama kararlarına geri dönülür

Çözümleme: Problem parçalara ayrılır. İki yol var: aşağıdan yukarı (bottom-up) ya da yukarıdan aşağı (top-down).

GereksinimlerRequirementsÇözümlemeAnalysisTasarımDesignİnceltme veKodlamaDoğrulamaVerificationhata bulunursa tasarım / kodlama kararlarına geri dönülür

Tasarım: Veri nesneleri (→ ADT) ve işlemler (→ algoritma) tanımlanır; ikisi de dilden bağımsızdır. Tasarıma ayrılan zaman arttıkça hata azalır.

GereksinimlerRequirementsÇözümlemeAnalysisTasarımDesignİnceltme veKodlamaDoğrulamaVerificationhata bulunursa tasarım / kodlama kararlarına geri dönülür

İnceltme ve kodlama: Seçilen veri yapıları üzerinde algoritmalar kodlanır. Veri gösterimi, algoritmanın etkinliğini belirler.

GereksinimlerRequirementsÇözümlemeAnalysisTasarımDesignİnceltme veKodlamaDoğrulamaVerificationhata bulunursa tasarım / kodlama kararlarına geri dönülür

Doğrulama: Doğruluk ispatı, geniş test verisiyle sınama ve hata giderme. Her biri başlı başına bir araştırma konusu.

Horowitz–Sahni, Bölüm 1

Temeller

Çözümleme: Aşağıdan Yukarı mı, Yukarıdan Aşağı mı?

Aşağıdan yukarı (bottom-up)

  • Problem parçalara bölünür, her parçaya ayrı odaklanılır.
  • Modüller doğrudan görülür; problem modül modül çözülür.
  • Proje konusunda tecrübeliysek tercih edilir.

Yukarıdan aşağı (top-down)

  • Önce bir ana plan: “Amaç ne?”, “Son ürün ne olacak?”
  • Problem alt parçalara bölünür; daha çok zaman harcanır.
  • Geri dönüş ve hata olasılığı daha azdır; tecrübemiz yoksa tercih edilir.
Tasarım aşamasında öğrenci kütüğünün hangi alanları içereceğine karar veririz, ama onu dizi mi, bağlı liste mi, ağaç mı ile gerçekleyeceğimizi erteleyebiliriz. Erteleme, daha etkili bir gerçeklemeyi seçme fırsatı verir.

Temeller

Kodlama ve Doğrulama

Doğruluk ispatı

Matematiksel tekniklerle program doğruluğu ispatlanır; büyük projede baştan sona ispat neredeyse imkânsız. Doğruluğu ispatlanmış algoritmaları kullanmak hatayı azaltır.

Sınama (testing)

Çalışan kod ve tüm senaryoları kapsayan test verisi gerekir. Sözdizimi hatası vermeden çalışan program doğru demek değildir.

Hata giderme

İspat ve testler hatayı gösterirse, tasarım ve kodlama kararlarına göre hata ayıklanır.

İşletim zamanı

Hatasız ama yavaş çalışan bir programın da fazla değeri yoktur: doğruluk ile verimlilik ayrı sorulardır.

Hocadan“Kodlama ne kadar geciktirilirse, arka planda o kadar iyi bir yöntem yakalanabilir.”

Temeller

Algoritma Nedir?

Algoritma, belirli bir görevi yerine getiren sonlu sayıda komut dizisidir. Her algoritma şu ölçütleri sağlamalıdır:

ÖlçütAnlamı
GirdiDışarıdan sıfır ya da daha fazla nicelik alınır.
ÇıktıEn az bir nicelik üretilir.
Kesinlik (definiteness)Her komut açık ve tek anlamlıdır: “x’e 6 ya da 7 ekle” olmaz.
Sonluluk (finiteness)Her durumda, sonlu adım sonra sona erer.
Etkinlik (effectiveness)Her komut, kâğıt kalemle yapılabilecek kadar temel ve uygulanabilirdir.
Sonlanmayan program (ör. işletim sistemi) algoritma değil, bir hesaplama yöntemidir. Algoritmayı doğal dil, akış şeması ya da C ile ifade edebiliriz; biz C kullanacağız.

Horowitz–Sahni, Bölüm 1

Temeller

Soyut Veri Tipi (Abstract Data Type)

Veri tipi = nesneler + işlemler. ADT: nesnelerin ve işlemlerin belirtiminin (specification), gösterimden ve gerçeklemeden (implementation) ayrıldığı veri tipi.

Belirtim: NE yapılır?

İşlem adları, parametre türleri, sonuç türü ve işlemin ne yaptığı. Kullanıcı yalnızca bunu bilir.

Gerçekleme: NASIL?

Verinin bellekte gösterimi (struct, dizi…) ve işlemleri yapan kod. Kullanıcıdan gizlenir.

ADT Kesir
  nesneler: pay/payda, payda ≠ 0
  işlemler:
    olustur(p, q)  → Kesir   /* kurucu */
    topla(a, b)    → Kesir   /* dönüştürücü */
    esit(a, b)     → Boolean /* gözlemci */
    yaz(a)                   /* raporlayıcı */
    sil(a)
Kurucu yeni örnek yaratır, dönüştürücü var olandan yeni değer üretir, gözlemci/raporlayıcı bilgi verir. Aynı ADT farklı veri yapılarıyla gerçeklenebilir.

Horowitz–Sahni, Bölüm 1.3

Temeller

C’de Soyutlama: Başlık Dosyası + struct

/* kesir.h : arayuz (NE?) */
#ifndef KESIR_H
#define KESIR_H
typedef struct kesir Kesir; /* opak */

Kesir *kesir_olustur(int pay, int payda);
Kesir *kesir_topla(const Kesir *a,
                   const Kesir *b);
int  kesir_esit(const Kesir *a,
                const Kesir *b);
void kesir_yaz(const Kesir *k);
void kesir_sil(Kesir *k);
#endif
/* kesir.c : gercekleme (NASIL?) */
#include <stdio.h>
#include <stdlib.h>
#include "kesir.h"

struct kesir { int pay, payda; };

Kesir *kesir_olustur(int pay, int payda) {
  Kesir *k;
  if (payda == 0) return NULL;
  k = malloc(sizeof *k);
  if (k == NULL) return NULL;
  k->pay = pay;
  k->payda = payda;
  return k;
}
typedef struct kesir Kesir; eksik (opak) tip bildirir: kesir.h’yi kullanan dosya yapının alanlarını göremez, yalnızca Kesir * tutar. Alanlar kesir.c’de gizlidir → ADT’nin C’deki karşılığı.

Temeller

Gerçeklemenin Kalanı ve Kullanımı

Kesir *kesir_topla(const Kesir *a,
                   const Kesir *b) {
  return kesir_olustur(
      a->pay * b->payda
        + b->pay * a->payda,
      a->payda * b->payda);
}
int kesir_esit(const Kesir *a,
               const Kesir *b) {
  return a->pay * b->payda
      == b->pay * a->payda;
}
void kesir_yaz(const Kesir *k) {
  printf("%d/%d\n", k->pay, k->payda);
}
void kesir_sil(Kesir *k) { free(k); }
/* main.c : yalnizca arayuzu bilir */
#include "kesir.h"
#include <stddef.h> /* NULL */
int main(void) {
  Kesir *a = kesir_olustur(1, 2);
  Kesir *b = kesir_olustur(1, 3);
  Kesir *t = NULL;
  if (a && b) t = kesir_topla(a, b);
  if (t) kesir_yaz(t);  /* 5/6 */
  kesir_sil(a); kesir_sil(b);
  kesir_sil(t);  /* free(NULL) zararsiz */
  return 0;
}
Sözleşme

kesir_olustur payda 0 ise ya da bellek yoksa NULL döner; çağıran bunu denetler.

Sahiplik

Her olustur/topla sonucu çağırana aittir; kesir_sil ile bırakılır.

Seçimli Sıralama

Seçimli Sıralama (Selection Sort)

Sıralı olmayan bir diziyi küçükten büyüğe sıralamak istiyoruz. Her turda kalan kısmın en küçüğü bulunur ve başa (i. konuma) taşınır:

for (i = 0; i < n; i++) {
    list[i] .. list[n-1] arasini incele;
    en kucuk tamsayi list[min] olsun;
    list[i] ile list[min]'i takas et;
}
010113229337414sıralı bölgesıralanmamış bölge▲ i▲ mintakas
Hocadan“Yazılmış algoritmaları beğenmeyip daha iyi alternatifler sunabilecek seviyeye gelmek gerekir.”

Seçimli Sıralama

Seçimli Sıralama: C Gerçeklemesi

#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 101
#define SWAP(x, y, t) ((t) = (x), (x) = (y), (y) = (t))
void sort(int list[], int n);

int main(void) {
  int i, n, list[MAX_SIZE];
  printf("Dizi boyunu giriniz: ");
  if (scanf("%d", &n) != 1 || n < 1 || n > MAX_SIZE) {
    printf("Hatali n degeri!\n");
    return 1;
  }
  for (i = 0; i < n; i++) list[i] = rand() % 100;
  sort(list, n);
  for (i = 0; i < n; i++) printf("%d ", list[i]);
  return 0;
}
void sort(int list[], int n) {
  int i, j, min, temp;
  for (i = 0; i < n - 1; i++) {
    min = i;
    for (j = i + 1; j < n; j++)
      if (list[j] < list[min])
        min = j;
    SWAP(list[i], list[min], temp);
  }
}
Karşılaştırma sayısı: (n−1) + (n−2) + … + 1 = n(n−1)/2 → O(n²). Takas en fazla n−1 kez.

Seçimli Sıralama · Sık yapılan hata

Sık Görülen Hatalı sort() Nerede Ayrılıyor?

void sort(int list[], int n) {
  int i, j, min, temp;
  for (i = 0; i < n; i++) {
    min = i;
    for (j = 0; j < n; j++)
      if (list[j] > list[min]) {
        min = j;
        SWAP(list[i], list[min], temp);
      }
  }
}
GirdiSürümKarş.Takas*
{29,10,14,37,13}hatalı257
seçimli103
100 rastgelehatalı100002561
seçimli495094
500…1 (azalan)hatalı250000124750
seçimli124750250
Soldaki hatalı sürüm diziyi yine sıralar (n ≤ 7 için tüm permütasyonlar denendi), ama sözde koddaki seçimli sıralama değildir: iç döngü j = 0’dan başlar, takas döngünün içinde yapılır. Doğru sürüm (j = i + 1, takas iç döngüden sonra) bu bölümde kullanılan sürümdür. *Takas: iki farklı hücrenin yer değiştirdiği SWAP’lar (i ≠ min).

Seçimli Sıralama

SWAP: Makro mu, Fonksiyon mu?

Makro

#define SWAP(x, y, t) \
        ((t) = (x), (x) = (y), (y) = (t))

int temp;
SWAP(list[i], list[min], temp);
  • Çağrı maliyeti yok: önişlemci metni yerine koyar.
  • Geçici değişken t dışarıdan verilir.

Fonksiyon

void swap(int *x, int *y) {
  int temp = *x;
  *x = *y;
  *y = temp;
}

swap(&list[i], &list[min]);
  • Tür denetimi var; temp yerel olur.
  • Değişkenlerin adresleri gönderilmelidir.
Makro, argümanlarını birden çok kez değerlendirir: SWAP(a[i++], a[j], t) i’yi iki kez artırır! Fonksiyona da SWAP adı verilirse makroyla çakışır; bu yüzden fonksiyonun adı swap.

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

Kod İzleme: swap(&a, &b)

main() çerçevesia3b8

main’in çerçevesinde a = 3, b = 8.

main() çerçevesia3b8swap() çerçevesixytemp?

Çağrı: değerler değil adresler kopyalanır. x → a, y → b.

main() çerçevesia3b8swap() çerçevesixytemp3

temp = *x: x’in gösterdiği yerdeki değer (3) okunur.

main() çerçevesia8b8swap() çerçevesixytemp3

*x = *y: y’nin gösterdiği 8, x’in gösterdiği yere, yani a’ya yazılır.

main() çerçevesia8b3swap() çerçevesixytemp3

*y = temp: b’ye 3 yazılır. Takas main’in değişkenlerinde gerçekleşti.

main() çerçevesia8b3

swap’ın çerçevesi yok oldu (x, y, temp gitti) ama etkisi kaldı.

void swap(int *x, int *y) {
  int temp = *x;
  *x = *y;
  *y = temp;
}

int main(void) {
  int a = 3, b = 8;
  swap(&a, &b);
  printf("%d %d\n", a, b);
}

Performans Analizi

Bir Programı Nasıl Değerlendiririz?

  1. İsteneni karşılıyor mu?
  2. Doğru çalışıyor mu?
  3. Nasıl işletileceği ve kullanılacağı belgelenmiş mi (açıklama satırları dâhil)?
  4. Mantıksal sonuç üretmek için fonksiyonları etkili kullanıyor mu?
  5. Kod okunabilir mi?
  6. Ana ve ikincil belleği etkili kullanıyor mu?
  7. İşletim zamanı kabul edilebilir mi?
1 · Performans analizi

Makineden bağımsız: zaman ve bellek için tahmin (karmaşıklık).

2 · Performans ölçümü

Makineye bağlı: gerçek işletim süresi ölçülür.

Son iki soru performans değerlendirmenin konusudur; ilk beşi de en az onlar kadar önemlidir.

Performans Analizi

Alan Karmaşıklığı (Space Complexity)

Programın işini tamamlaması için gereken bellek miktarı: S(P) = c + SP(I)

Sabit kısım c

Girdi/çıktı büyüklüğünden bağımsız: kodun kendisi, basit değişkenler, sabitler.

Değişken kısım SP(I)

I girdisinin özelliklerine (çoğunlukla boyutuna) bağlı kısım. Çözümlemede bununla ilgileniriz.

float abc(float a, float b, float c) {
  return a + b + b * c
         + (a + b - c) / (a + b) + 4.00;
}
/* yalniz sabit bellek: S_abc(I) = 0 */
Hocadan“Kod, her şey kısıtlıymış gibi yazılmalı. Bellek nasıl olsa bol diye boşa bellek harcanamaz.”

Performans Analizi

sum ve rsum: Değişken Bellek

float sum(float list[], int n) {
  float tempsum = 0;
  int i;
  for (i = 0; i < n; i++)
    tempsum += list[i];
  return tempsum;
}

float rsum(float list[], int n) {
  if (n)
    return rsum(list, n - 1) + list[n - 1];
  return 0;
}
  • sum’a dizinin ilk öğesinin adresi gider → Ssum(n) = 0. Öğeler değerle kopyalansaydı Ssum(n) = n olurdu.
  • rsum(list, n) toplam n+1 kez çağrılır; her çağrı yığında bir çerçeve açar → Srsum(n) ∝ n.
sistem yığınırsum(n=3)list · n · dönüş adr.rsum(n=2)list · n · dönüş adr.rsum(n=1)list · n · dönüş adr.rsum(n=0)list · n · dönüş adr.Her çağrı bir çerçeve:16 bitlik makinede 2 + 2 + 2 = 6 baytn = 1000 → en az 6000 bayt64 bitte bir çerçeve tipik olarak ≥ 24 bayt

Performans Analizi

Zaman Karmaşıklığı (Time Complexity)

T(P) = derleme zamanı + işletim zamanı. Derleme bir kez yapılır; biz işletim zamanı tP ile ilgileniriz.

tP(n) = ca·ADD(n) + cs·SUB(n) + cl·LDA(n) + cst·STA(n)
Sabitler c

Her işlemin süresi: derleyiciye ve makineye bağlı, önceden bilmek zor.

Adım sayısı

Bunun yerine program adımlarını sayarız: makineden bağımsız, anlamlı bir birim.

Hocadan“Performansı yüksek programlar yazmak neden bu kadar önemli? Bir savaş uçağının bombalama sistemi programlanıyor olsun. Bu program 3 sn’de, karşı tarafınki 2 sn’de bomba bırakıyorsa savaş baştan kaybedilmiş demektir.”

Performans Analizi

Adım Sayma Tablosu: sum()

Deyims/eSıklıkToplam
float sum(float list[], int n) {000
float tempsum = 0;111
int i;000
for (i = 0; i < n; i++)1n + 1n + 1
tempsum += list[i];1nn
return tempsum;111
Toplam2n + 3
s/e (steps per execution): deyimin bir kez işletilmesi kaç adım. for testi n+1 kez yapılır: n kez “devam”, bir kez de “bitti”. Bildirimler adım sayılmaz.

Performans Analizi · Kodu adım adım çalıştır

Kod İzleme: count ile sum(list, 3)

031124

Global count = 0. Her program adımında bir artacak.

031124

Atama bir adım → count = 1.

031124▲ i

for testi 0 < 3 doğru → bir adım (count = 2).

031124▲ i

tempsum += list[0] → 3; count = 3.

031124▲ i

for testi 1 < 3 doğru → bir adım (count = 4).

031124▲ i

tempsum += list[1] → 4; count = 5.

031124▲ i

for testi 2 < 3 doğru → bir adım (count = 6).

031124▲ i

tempsum += list[2] → 8; count = 7.

031124

Son test 3 < 3 yanlış; o da bir adım → count = 8.

031124

return: count = 9 = 2·3 + 3. Tablodaki 2n + 3 doğrulandı.

float sum(float list[], int n) {
  float tempsum = 0; count++;
  int i;
  for (i = 0; i < n; i++) {
    count++;             /* for testi */
    tempsum += list[i]; count++;
  }
  count++;  /* for'un son testi */
  count++;  return tempsum;
}

Performans Analizi

Adım Sayma Tablosu: rsum()

Deyims/eSıklıkToplam
float rsum(float list[], int n) {000
if (n)1n + 1n + 1
return rsum(list, n-1) + list[n-1];1nn
return 0;111
Toplam2n + 2
Adım sayısı yanıltabilir

rsum (2n+2) sum’dan (2n+3) “daha az adım” görünür; ama her özyinelemeli çağrının (çerçeve açma, dönüş) maliyeti bir döngü turundan fazladır.

İkisi de O(n)

Önemli olan n büyüdükçe adım sayısının nasıl büyüdüğüdür: ikisi de doğrusal.

Performans Analizi · Kodu adım adım çalıştır

Kod İzleme: rsum(list, 3) ve çağrı yığını

031124listrsum(list, 3)yığın (alttan üste)

rsum(list, 3): if testi (count = 1); n = 3 ≠ 0.

031124listrsum(list, 3)yığın (alttan üste)

count = 2; önce rsum(list, 2) hesaplanmalı → yeni çerçeve.

031124listrsum(list, 3)rsum(list, 2)yığın (alttan üste)

rsum(list, 2): if testi (count = 3); n = 2 ≠ 0.

031124listrsum(list, 3)rsum(list, 2)yığın (alttan üste)

count = 4; önce rsum(list, 1) hesaplanmalı → yeni çerçeve.

031124listrsum(list, 3)rsum(list, 2)rsum(list, 1)yığın (alttan üste)

rsum(list, 1): if testi (count = 5); n = 1 ≠ 0.

031124listrsum(list, 3)rsum(list, 2)rsum(list, 1)yığın (alttan üste)

count = 6; önce rsum(list, 0) hesaplanmalı → yeni çerçeve.

031124listrsum(list, 3)rsum(list, 2)rsum(list, 1)rsum(list, 0)yığın (alttan üste)

rsum(list, 0): if testi (count = 7); n = 0 → taban durum.

031124listrsum(list, 3)rsum(list, 2)rsum(list, 1)rsum(list, 0)→ 0yığın (alttan üste)

count = 8; return 0. Yığın en derin hâlinde: 4 çerçeve.

031124listrsum(list, 3)rsum(list, 2)rsum(list, 1)→ 3yığın (alttan üste)

Dönen değere list[0] = 3 eklenir → rsum(list, 1) = 3.

031124listrsum(list, 3)rsum(list, 2)→ 4yığın (alttan üste)

Dönen değere list[1] = 1 eklenir → rsum(list, 2) = 4.

031124listrsum(list, 3)→ 8yığın (alttan üste)

Dönen değere list[2] = 4 eklenir → rsum(list, 3) = 8. count = 8 = 2·3 + 2.

float rsum(float list[], int n) {
  count++;               /* if testi */
  if (n) {
    count++;             /* toplama + return */
    return rsum(list, n - 1) + list[n - 1];
  }
  count++;
  return 0;
}

Performans Analizi

Adım Sayma: Matris Toplama add()

DeyimSıklık
void add(int a[][MAX_SIZE], int b[][MAX_SIZE],
  int c[][MAX_SIZE], int rows, int cols) {
0
int i, j;0
for (i = 0; i < rows; i++)rows + 1
for (j = 0; j < cols; j++)rows · (cols + 1)
c[i][j] = a[i][j] + b[i][j];rows · cols
Toplam2·rows·cols + 2·rows + 1
Sonuç O(rows·cols); kare matriste (rows = cols = n) O(n²). Dış döngü daha küçük boyutta dönerse 2·rows terimi küçülür: rows ≫ cols iken döngüler yer değiştirilirse sayım 2·rows·cols + 2·cols + 1 olur. Matris çarpımı ise O(n³).

“O(rows, cols)” yazmak yanlıştır; doğrusu çarpım: O(rows·cols).

Performans Analizi

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

Aynı n için adım sayısı girdinin kendisine de bağlı olabilir. Doğrusal aramada:

071329354158▲ en iyi: 1 karş.en kötü: n karş. ▲
DurumNe zaman?Karşılaştırma
En iyiAranan ilk hücrede1 → Θ(1)
OrtalamaAranan var ve her konum eşit olasılıklı(n + 1) / 2 → Θ(n)
En kötüAranan son hücrede ya da hiç yokn → Θ(n)
Genellikle en kötü durumu veririz: her girdi için garanti bir üst sınırdır. Ortalama durum, girdinin olasılık dağılımını bilmeyi gerektirir.

CLRS Bölüm 2.2

Asimptotik Gösterim

Büyük O (Big-O) Gösterimi

Tanım: Tüm n ≥ n0 için f(n) ≤ c·g(n) olacak biçimde c > 0 ve n0 sabitleri varsa f(n) = O(g(n)) yazılır: “algoritmanın karmaşıklığı g(n) derecesindedir”.

  • Tsum(n) = 2n + 3 = O(n)
  • Trsum(n) = 2n + 2 = O(n)
  • Tadd = 2·rows·cols + 2·rows + 1 = O(rows·cols) → O(n²)
nadımn₀c·g(n)f(n)n ≥ n₀ için f(n) ≤ c·g(n)
Hocadan“Zaman, bellekten daha önemli!”

O bir üst sınırdır; “=” burada “elemanıdır” anlamında tek yönlü okunur.

Asimptotik Gösterim

Örnek: f(n) = 3n³ + 6n² + 7n = O(n³)

3n³ + 6n² + 7n ≤ 3n³ + 6n² + n² (n ≥ 7 için 7n ≤ n²)3n³ + 6n² + 7n ≤ 3n³ + 7n²3n³ + 6n² + 7n ≤ 3n³ + n³ (n ≥ 7 için 7n² ≤ n³)3n³ + 6n² + 7n ≤ 4n³ ⇒ f(n) = O(n³) (c = 4, n₀ = 7)

Baskın olmayan 7n terimini n² ile sınırlarız: n ≥ 7 olduğunda 7n ≤ n².

3n³ + 6n² + 7n ≤ 3n³ + 6n² + n² (n ≥ 7 için 7n ≤ n²)3n³ + 6n² + 7n ≤ 3n³ + 7n²3n³ + 6n² + 7n ≤ 3n³ + n³ (n ≥ 7 için 7n² ≤ n³)3n³ + 6n² + 7n ≤ 4n³ ⇒ f(n) = O(n³) (c = 4, n₀ = 7)

6n² + n² = 7n².

3n³ + 6n² + 7n ≤ 3n³ + 6n² + n² (n ≥ 7 için 7n ≤ n²)3n³ + 6n² + 7n ≤ 3n³ + 7n²3n³ + 6n² + 7n ≤ 3n³ + n³ (n ≥ 7 için 7n² ≤ n³)3n³ + 6n² + 7n ≤ 4n³ ⇒ f(n) = O(n³) (c = 4, n₀ = 7)

Aynı hile bir kez daha: n ≥ 7 için 7n² ≤ n³.

3n³ + 6n² + 7n ≤ 3n³ + 6n² + n² (n ≥ 7 için 7n ≤ n²)3n³ + 6n² + 7n ≤ 3n³ + 7n²3n³ + 6n² + 7n ≤ 3n³ + n³ (n ≥ 7 için 7n² ≤ n³)3n³ + 6n² + 7n ≤ 4n³ ⇒ f(n) = O(n³) (c = 4, n₀ = 7)

c = 4 ve n₀ = 7 bulundu: tanım sağlandı.

1 · Üst sınır

İşletim zamanı hiçbir zaman 4n³ adımı aşmaz (n ≥ 7).

2 · Kübik büyüme

100 sayı 2 sn’de sıralanıyorsa 300 sayı en fazla 2·3³ = 54 sn sürer (6 sn değil!).

Asimptotik Gösterim

Alt Sınır Ω ve Sıkı Sınır Θ

GösterimTanım (tüm n ≥ n₀ için)Anlamı
f(n) = O(g(n))f(n) ≤ c·g(n)g, f’nin üst sınırı
f(n) = Ω(g(n))f(n) ≥ c·g(n)g, f’nin alt sınırı
f(n) = Θ(g(n))c₁·g(n) ≤ f(n) ≤ c₂·g(n)hem O hem Ω: sıkı sınır
  • 3n + 2 = Θ(n): n ≥ 2 için 3n ≤ 3n + 2 ≤ 4n.
  • 3n + 2 = O(n²) de doğrudur ama bilgi vermez; mümkün olan en küçük g(n)’yi yazarız.
  • Seçimli sıralama her girdide n(n−1)/2 karşılaştırma yapar → Θ(n²).
Hocadan“Bir sınav sorusu O(n)’de çözülmüş, sonuna da m kez dönen boş bir döngü eklenmiş: soru “O(n+m)’de çözün” diyor diye. Gerek yok. İstenen O(n+m) ise, daha iyisi yapılabiliyorsa ne âlâ!”

Asimptotik Gösterim

Alışılmış Karmaşıklıklar (Practical Complexities)

KarmaşıklıkAdın=1n=2n=4n=8n=16n=32
1Sabit (constant)111111
lg nLogaritmik012345
nDoğrusal (linear)12481632
n·lg nLog-doğrusal0282464160
n²Karesel (quadratic)1416642561.024
n³Kübik (cubic)18645124.09632.768
2ⁿÜstel (exponential)241625665.5364,29·109
n!Faktöriyel122440.3202,09·10132,63·1035
lg n = log₂ n. Küçük n değerleri yanıltıcıdır: n = 4’te 2ⁿ = n², n = 8’de 2ⁿ = 256 > n² = 64. Belli bir noktadan sonra üstel ve faktöriyel terimler her şeyi ezer.

Asimptotik Gösterim

n Büyüdükçe: Büyüme Grafiği

050100150123456789101112n →lg nnn·lg nn²n³2ⁿ

Dikey ölçek 0–150: lg n, n ve n·lg n birbirinden ayrışıyor; n² hızla tırmanıyor, n³ ve 2ⁿ çoktan dışarıda.

01000200030004000123456789101112n →n·lg nn²n³2ⁿn!

Ölçek 0–4500: n³ = 1728 (n = 12), 2ⁿ = 4096 (n = 12); n! ise n = 7’de 5040 ile grafikten çıkar. Doğrusal fonksiyonlar tabana yapışmış görünür.

Asimptotik Gösterim

Saniyede 1 Milyar Komut İşleyen Makinede Süreler

nnlg nn·lg nn²n³n⁴n¹⁰2ⁿ
100,01 μs0,00332 μs0,0332 μs0,1 μs1 μs10,0 μs10,0 sn1,02 μs
200,02 μs0,00432 μs0,0864 μs0,4 μs8 μs160 μs2,84 saat1,05 ms
300,03 μs0,00491 μs0,147 μs0,9 μs27,0 μs810 μs6,83 gün1,07 sn
400,04 μs0,00532 μs0,213 μs1,6 μs64,0 μs2,56 ms4,05 ay18,3 dk
500,05 μs0,00564 μs0,282 μs2,5 μs125 μs6,25 ms3,1 yıl13,0 gün
1000,1 μs0,00664 μs0,664 μs10,0 μs1 ms100 ms31,7 asır4,02·10¹¹ asır
10001 μs0,00997 μs9,97 μs1 ms1 sn16,7 dk3,17·10¹¹ asır3,40·10²⁸² asır
1000010,0 μs0,0133 μs133 μs100 ms16,7 dk3,86 ay3,17·10²¹ asır6,33·10²⁹⁹¹ asır

Her adım 1 ns kabul edildi; 1 yıl = 365 gün, 1 ay = 30 gün. n = 10000 için 2ⁿ yaklaşık 102992 asır.

Asimptotik Gösterim

Amortize Analize Kısa Giriş

Bazı işlemler bazen pahalı, çoğunlukla ucuzdur. Amortize analiz, n işlemlik bir dizinin toplam maliyetini n’e böler. Örnek: doluysa kapasitesi ikiye katlanan dizi (realloc); bu dizinin adım adım izlemesi ve kopya sayımı dinamik dizilerle birlikte işlenir.

n eklemede kopyalamalar 1 + 2 + 4 + … < 2n → toplam < 3n. Tek bir ekleme O(n) sürebilir, ama ekleme başına amortize maliyet O(1). (Kapasiteyi sabit sayıda artırsaydık O(n) olurdu.)

CLRS 3. baskı, Bölüm 17 (Amortized Analysis)

Sihirli Kare

Sihirli Kare (Magic Square) Problemi

15812417161475232220136432119121092251811

Her satır, sütun ve köşegen: 65

1’den n²’ye kadar sayılar n×n kareye, her satır, sütun ve iki ana köşegenin toplamı eşit olacak biçimde yerleştirilir: toplam n(n²+1)/2 (5×5 için 65).

Coxeter kuralı (n tek; Siyam ya da de la Loubère yöntemi olarak da bilinir): İlk satırın ortasına 1 yazılır. Hep sol üst çapraza gidilir; kare doluysa bulunulan karenin altına inilir. Tablonun dışına taşılırsa karşı kenardan dönülür (sarmal). n² adımda biter → O(n²).

Kaba kuvvet (brute force): (n²)! diziliş. 25! ≈ 1,55·10²⁵ olasılık; saniyede 10⁹ deneme ile ≈ 1,55·10¹⁶ sn ≈ 4,9 milyon asır.

Sihirli Kare

“Bilgisayar Hızlı, Hemen Çözer!” mi?

Sık duyulan bir itiraz: 10 GHz’lik yeni bir bilgisayar bu problemi hemen çözer; algoritmayla uğraşmaya gerek yok.
10 kat hızlı makine

4,9 milyon asır → yaklaşık 490 bin asır. Hâlâ umutsuz.

Doğru algoritma

Coxeter kuralı: 25 adım, mikrosaniyenin altında. Algoritma, donanımdan önce gelir.

Hesap: 25! / (10¹⁰ · 3,15·10⁹) ≈ 4,9·10⁵ asır.

Sihirli Kare · Kodu adım adım çalıştır

Kod İzleme: sihirli(3, k)

1

İlk satırın ortası: i = 0, j = 1 → 1 yazılır.

12

s = 2: sol üst çapraz (2, 0) boş (satır taştı → en alt satır) → oraya geçilir.

132

s = 3: sol üst çapraz (1, 2) boş (sütun taştı → en sağ sütun) → oraya geçilir.

1324

s = 4: sol üst çapraz (0, 1) dolu (orada 1 var) → bir aşağı: (2, 2).

15324

s = 5: sol üst çapraz (1, 1) boş → oraya geçilir.

615324

s = 6: sol üst çapraz (0, 0) boş → oraya geçilir.

6175324

s = 7: sol üst çapraz (2, 2) dolu (orada 4 var) → bir aşağı: (1, 0).

61875324

s = 8: sol üst çapraz (0, 2) boş (sütun taştı → en sağ sütun) → oraya geçilir.

618753294

s = 9: sol üst çapraz (2, 1) boş (satır taştı → en alt satır) → oraya geçilir.

618753294

s = 10 > 9: döngü biter. Her satır, sütun ve köşegenin toplamı 15 = 3·(9+1)/2.

void sihirli(int n, int k[][MAX]) {
  int i = 0, j = (n - 1) / 2, s, r, c;
  k[i][j] = 1;        /* k sifirlanmis olmali */
  for (s = 2; s <= n * n; s++) {
    r = (i - 1 < 0) ? n - 1 : i - 1; /* yukari */
    c = (j - 1 < 0) ? n - 1 : j - 1; /* sol */
    if (k[r][c]) i = (i + 1) % n; /* in */
    else { i = r; j = c; }
    k[i][j] = s;
  }
}

Performans Ölçümü

Performans Ölçümü: İki Yöntem

1 · İşleyici zamanı (clock)2 · Takvim zamanı (time)
Başlangıçstart = clock();start = time(NULL);
Bitişstop = clock();stop = time(NULL);
Türclock_ttime_t
Sonuç (sn)(double)(stop - start) / CLOCKS_PER_SECdifftime(stop, start)
clock()

Program için harcanan işlemci saat vuruşu (clock tick) sayısı. Çözünürlüğü sistemin vuruş aralığıyla sınırlıdır.

time()

Duvar saati; saniye çözünürlüğü. Uzun işler için uygun, kısa işler için kaba.

CLK_TCK eski, standart dışı bir addır; standart C’de CLOCKS_PER_SEC kullanılır. Ölçerken çevre birimleriyle (ekran, disk, yazıcı) işlem yavaştır: printf’ler ölçülen bölgenin dışında tutulmalıdır.

Performans Ölçümü

Uygulama 1: Seçimli Sıralamayı Ölçmek

#include <stdio.h>
#include <time.h>
#define MAX_SIZE 5001
#define ITERATIONS 20
void sort(int list[], int n);   /* secmeli siralama */

int main(void) {
  int i, j, list[MAX_SIZE];
  int sizelist[] = { 0, 100, 300, 600, 900, 1200, 1500, 2000,
    3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800,
    3900, 4000, 5000 };
  clock_t start, stop;  double ms;
  for (i = 0; i < ITERATIONS; i++) {
    for (j = 0; j < sizelist[i]; j++)
      list[j] = sizelist[i] - j;   /* azalan dizi */
    start = clock();
    sort(list, sizelist[i]);
    stop = clock();
    ms = 1000.0 * (stop - start) / CLOCKS_PER_SEC;
    printf("%4d eleman: %6.1f ms\n", sizelist[i], ms);
  }
  return 0;
}
  • 20 farklı n; her biri için büyükten küçüğe dizilmiş dizi.
  • Yalnızca sort çağrısı iki clock() arasında.
  • (int)(1000*(stop-start)/CLK_TCK) yazılırsa tamsayı bölmesi küsuratı atar; 1000.0 ile double aritmetik daha doğru.

Performans Ölçümü

Ölçüm Sonuçları ve Tutarsızlıklar

050100150200250010002000300040005000n (öğe sayısı)ms

Noktalar: örnek ölçüm · kesikli: c·n² eğrisi

  • 600 öğeye kadar hepsi 0 ms?
  • 3000 öğe 110 ms, 3100 ise 93 ms?
  • 3500, 3600, 3700 hepsi 125 ms?
Neden? Sıralama iki saat vuruşu arasında bitince stop − start = 0. Vuruş aralığı ~15,6 ms olan bir sistemde sonuçlar 15–16 ms’nin katlarına yuvarlanır.
Çözüm: Her ölçüm çok kez tekrarlanır, ortalaması alınır.

Performans Ölçümü

Uygulama 2: Doğrusal Arama (Bekçi ile)

int seqsearch(int list[], int searchnum, int n) {
  int i;
  list[n] = searchnum;          /* bekci */
  for (i = 0; list[i] != searchnum; i++)
    ;
  return (i < n) ? i : -1;
}
start = clock();
for (j = 0; j < runtimes[i]; j++)
  pos = seqsearch(list, -1, sizelist[i]);
stop = clock();
total = 1000.0 * (stop - start) / CLOCKS_PER_SEC;
ort = 1000.0 * total / runtimes[i];  /* mikro sn */
list[sizelist[i]] = sizelist[i];  /* bekciyi sil */
  • Bekçi (sentinel): aranan değer list[n]’e konur; döngü hep durur, her turda i < n testine gerek kalmaz.
  • Dizide n + 1 yer olmalı (MAX_SIZE = 5001).
  • −1 aranır: hiç bulunmaz → en kötü durum ölçülür.
  • Her n için arama runtimes[i] kez (500000 … 5000) tekrarlanır.

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

Kod İzleme: seqsearch({4, 8, 2}, 5, 3)

0418223?

Dizide 3 öğe var, ama list[3] için yer ayrılmış. 5 aranıyor.

04182235bekçi

Bekçi: list[3] = 5. Artık döngü en geç burada duracak.

04182235▲ ibekçi

list[0] = 4 ≠ 5 → i++. (Tek test: sınır denetimi yok.)

04182235▲ ibekçi

list[1] = 8 ≠ 5 → i++. (Tek test: sınır denetimi yok.)

04182235▲ ibekçi

list[2] = 2 ≠ 5 → i++. (Tek test: sınır denetimi yok.)

04182235▲ ibekçi

list[3] = 5 → döngü durdu, ama bu bekçi.

04182235▲ ibekçi

i = 3 < n değil → return −1: bulunamadı. n + 1 = 4 karşılaştırma.

int seqsearch(int list[], int searchnum, int n) {
  int i;
  list[n] = searchnum;          /* bekci */
  for (i = 0; list[i] != searchnum; i++)
    ;
  return (i < n) ? i : -1;
}

Performans Ölçümü

Tekrarlı Ölçüm: Ortalama Arama Süresi

05101520010002000300040005000n (öğe sayısı)μs / arama

Noktalar: ölçülen ortalamalar · kesikli: doğrusal eğilim

  • Ortalamalar artık 0 değil ve n ile doğrusal büyüyor: O(n) beklentisiyle uyumlu.
  • 3000’den sonraki dalgalanma: tekrar sayısı (runtimes) azaldıkça ölçüm yine kabalaşıyor.
  • Ölçümün yapıldığı makinede saniyede 1000 vuruş: 1 vuruş = 1 ms.
Kısa işler çok kez tekrarlanır, toplam süre tekrar sayısına bölünür.

Ödevler

Ödev Soruları (Özyineleme)

  1. n! değerini bulan fonksiyonu özyinelemeli olarak yazınız.
  2. Fibonacci serisi f₀ = 0, f₁ = 1, fᵢ = fᵢ₋₁ + fᵢ₋₂ (i > 1) ise fᵢ’yi bulan özyinelemeli yordamı yazınız.
  3. C(n, m) = C(n−1, m) + C(n−1, m−1) verildiğine göre C(n, m) = n! / (m!·(n−m)!) değerini hesaplayan özyinelemeli yordamı yazınız.
İpuçları
  1. Taban: n ≤ 1 → 1. Adım: n · fakt(n−1).
  2. Taban: n ≤ 1 → n. Adım: fibo(n−1) + fibo(n−2).
  3. Taban: m == 0 ya da m == n → 1.

Ödevler

Ödev Cevapları ve Sık Hatalar

long fakt(long n) {
  if (n <= 1) return 1;
  return n * fakt(n - 1);
}

long fibo(long n) {
  if (n <= 1) return n;   /* f0 = 0, f1 = 1 */
  return fibo(n - 1) + fibo(n - 2);
}

long komb(int n, int m) {
  if (m == 0 || m == n) return 1;
  return komb(n - 1, m) + komb(n - 1, m - 1);
}
fibo: * değil +

Sık yapılan hata: fibo(deger-1) * fibo(deger-2) yazmak; çarpım her zaman 0 ya da 1 verir. Tanım gereği toplam olmalı.

komb: parametreler

Sık yapılan hata: long komb() parametresiz yazmak; n ve m parametre olmalı.

Maliyet

fakt O(n); fibo ve komb bu hâliyle üstel: aynı alt problemler tekrar tekrar çözülür.

Özet

Karmaşıklık Özeti

YordamZamanEk bellekNot
sort (seçimli sıralama)Θ(n²)O(1)n(n−1)/2 karşılaştırma, ≤ n−1 takas
sumΘ(n)O(1)2n + 3 adım
rsumΘ(n)O(n)2n + 2 adım, n+1 çerçeve
add (matris toplama)Θ(rows·cols)O(1)kare matriste Θ(n²)
seqsearchO(n)O(1)en iyi Θ(1), en kötü n + 1
sihirli (Coxeter)Θ(n²)O(1)kaba kuvvet: (n²)!
Dinamik dizi eklemeO(1) amortizeO(n)tek ekleme en kötü O(n)
fibo (özyinelemeli)üstelO(n)yığın derinliği n

Özet

Sık Yapılan Hatalar

1“Derlendi ve çalıştı, o hâlde doğru” sanmak: test verisi tüm senaryoları kapsamalı.
2Adım sayısını sabitlerle karıştırmak: 2n + 3 ile 100n aynı sınıftadır, O(n).
3O(n²) bulup “en iyi sınır” sanmak: mümkün olan en küçük g(n) yazılır; sıkı sınır için Θ.
4Kısa işi tek sefer clock() ile ölçmek: sonuç 0 ya da vuruş katı çıkar.
5Ölçülen bölgede printf bırakmak: G/Ç süresi algoritmayı gölgeler.
6Makroya yan etkili argüman vermek: SWAP(a[i++], …).

Özet

Alıştırmalar

  1. for (i = 0; i < n; i++) for (j = 0; j < i; j++) x++; parçasında x++ kaç kez işletilir? O(?)
  2. 10n² + 100n + 1000 = O(n²) olduğunu c ve n₀ vererek gösteriniz.
  3. Seçimli sıralama zaten sıralı bir dizide kaç karşılaştırma yapar? En iyi durumu var mı?
  4. O(n log n) bir algoritma n = 1000’de 10 ms sürüyorsa n = 10⁶’da yaklaşık ne kadar sürer?
  5. rsum’u n = 10⁶ ile çağırmak neden tehlikelidir?
  6. 7×7 sihirli karede ilk satırı Coxeter kuralıyla yazınız (toplam kaç olmalı?).
Cevapları / ipuçlarını göster
  1. n(n−1)/2 → Θ(n²).
  2. n ≥ 1 için 10n² + 100n + 1000 ≤ 1110n²: c = 1110, n₀ = 1 (başka seçimler de olur).
  3. Yine n(n−1)/2; karşılaştırma sayısı girdiden bağımsızdır (yalnız takas sayısı değişir).
  4. Oran (10⁶·log 10⁶)/(10³·log 10³) = 2000 → ≈ 20 sn.
  5. 10⁶ çerçeve yığını taşırabilir (stack overflow); döngülü sum güvenlidir.
  6. Toplam 7·50/2 = 175; ilk satır: 28 19 10 1 48 39 30.

Özet

Bölüm Özeti

Süreç

Gereksinim → çözümleme → tasarım → kodlama → doğrulama. Gerçeklemeyi ertelemek daha iyi seçim sağlar.

Soyutlama

ADT = belirtim ⟂ gerçekleme. C’de: başlık dosyası + opak struct.

Sayma

Bellek: c + SP(I). Zaman: program adımları (sum 2n+3, rsum 2n+2).

Sınıflama

O üst, Ω alt, Θ sıkı sınır. En kötü durum garantidir; amortize analiz dizinin toplamına bakar.

Büyüme

Küçük n yanıltır; 2ⁿ ve n! kısa sürede umutsuzlaşır. Algoritma donanımdan önce gelir.

Ölçüm

clock() + tekrar + ortalama; G/Ç ölçüm dışında.

Özet

Kaynaklar

  • Horowitz, Sahni, Anderson-Freed, Fundamentals of Data Structures in C, 2. baskı, Bölüm 1.
  • Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms, 3. baskı: Bölüm 2–3 (analiz, asimptotik), Bölüm 17 (amortize).
  • Kernighan, Ritchie, The C Programming Language, 2. baskı.

Son

Bölüm 1: Giriş ve Algoritma Analizi
Sonraki bölüm: Bölüm 2: Diziler ve Yapılar · Ders sayfası
1 / 1 C ile Veri Yapıları