C ile Veri Yapıları İçin Ön Bilgiler

Bu derste her yapı C ile, bellek elle yönetilerek kurulur: bir düğüm struct ile tanımlanır, malloc ile ayrılır, işaretçilerle bağlanır ve işi bitince free ile geri verilir. Bu yüzden C'nin tipleri, işaretçileri, dizileri ve dinamik belleği derse girmeden önce rahatça okunabilmelidir. Maliyetleri karşılaştırmak için de logaritma, toplam formülleri ve tümevarım gibi küçük bir matematik birikimi gerekir.

Her kartta konunun kısa bir özeti, küçük bir C örneği ve bir kontrol sorusu yer alır; cevap sorunun altındaki açılır kutudadır. Bağlantılar konunun derste kullanıldığı slaytlara gider.

Önerilen hazırlık sırası

Tipler ve fonksiyonlar→ İşaretçiler→ Diziler ve dizgeler→ struct→ Dinamik bellek→ Özyineleme→ Logaritma ve toplamlar→ Büyük O

C temelleri

Temel Tipler ve sizeof

int, char, double · tamsayı bölmesi · bayt sayısı

C'de her değişkenin bir tipi ve bellekte kapladığı belirli bir bayt sayısı vardır; sizeof bu sayıyı derleme sırasında verir. İki tamsayı arasındaki bölme tamsayı bölmesidir: kesirli kısım atılır. Dizi indisleri, heap'te ebeveyn indisi (i / 2) ve ikili aramadaki orta nokta hep bu kurala dayanır.

int a = 7, b = 2;
printf("%d\n", a / b);           /* 3   */
printf("%d\n", a % b);           /* 1   */
printf("%.1f\n", (double)a / b); /* 3.5 */
printf("%zu\n", sizeof(int));    /* çoğu sistemde 4 */

Kontrol sorusu: double m = 7 / 2; satırından sonra m değişkeninin değeri nedir?

Cevap

3.0. Bölme iki int arasında yapıldığı için sonuç önce 3 olur; ardından double tipine çevrilir. 3.5 için işlenenlerden en az biri double olmalıdır: 7.0 / 2.

Fonksiyonlar ve Değerle Çağrı

parametre · dönüş değeri · kopya

C'de bütün argümanlar değerle geçirilir (call by value): fonksiyon, çağıranın değişkeninin bir kopyasını alır. Bu yüzden iki değişkeni takas etmek isteyen bir fonksiyon, değişkenlerin kendilerini değil adreslerini almalıdır. Derste swap, yığının top sayacı ve arama ağacına ekleme hep bu nedenle adres alır.

void takas_yanlis(int x, int y) { int t = x; x = y; y = t; }
void takas(int *x, int *y)      { int t = *x; *x = *y; *y = t; }

int a = 1, b = 2;
takas_yanlis(a, b);   /* a == 1, b == 2: kopyalar değişti */
takas(&a, &b);        /* a == 2, b == 1 */

Kontrol sorusu: takas_yanlis içindeki üç atama çalıştığı hâlde main'deki a neden değişmez?

Cevap

x ve y, fonksiyon çağrılırken a ve b'nin değerleriyle ilklenen ayrı yerel değişkenlerdir. Atamalar yalnız bu kopyaları değiştirir ve fonksiyon dönünce kopyalar yok olur.

Diziler ve Dizgeler

ardışık bellek · '\0' ile biten char dizisi

Dizi, aynı tipte öğelerin bellekte art arda durduğu bir bloktur; a[i] erişimi O(1) sürer ve C sınır denetimi yapmaz. Bir diziyi fonksiyona geçirmek, ilk öğesinin adresini geçirmektir; boyut ayrıca verilmelidir. Dizge (string) ise sonunda '\0' bulunan bir char dizisidir: "dog" dört bayt kaplar.

int topla(const int a[], int n) {
    int s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    return s;
}

int uzunluk(const char *s) {
    int n = 0;
    while (s[n] != '\0') n++;
    return n;
}
/* topla((int[]){4, 5, 6}, 3) == 15, uzunluk("dog") == 3 */

Kontrol sorusu: char s[] = "veri"; tanımında sizeof s ile uzunluk(s) aynı değeri mi verir?

Cevap

Hayır. sizeof s sondaki '\0' dahil 5 bayttır; uzunluk(s) ise '\0''a kadar sayar ve 4 döndürür.

struct ve typedef

kayıt · nokta ve ok işleci · kendine başvuran yapı

struct, farklı tipteki alanları tek bir kayıtta toplar; typedef bu tipe kısa bir ad verir. Alana değişken üzerinden ., işaretçi üzerinden -> ile erişilir; p->deger yazımı (*p).deger ile aynıdır. Bağlı listenin düğümü, kendi tipinden bir işaretçi taşıyan kendine başvuran yapıdır (self-referential struct).

typedef struct dugum *dugum_gost;
typedef struct dugum {
    int deger;
    dugum_gost bag;      /* sonraki düğüm ya da NULL */
} dugum;

dugum d = { 3, NULL };
dugum_gost p = &d;
p->deger = 8;            /* d.deger artık 8 */

Kontrol sorusu: Bir yapı, kendi tipinden bir alanı (struct dugum sonraki;) doğrudan içerebilir mi?

Cevap

Hayır. Böyle bir yapının boyu sonsuz olurdu. Yapı kendi tipini ancak bir işaretçi üzerinden gösterebilir; işaretçinin boyu sabittir.

Makrolar ve Başlık Dosyaları

#define · #include · opak tip

Önişlemci, #define ile tanımlanan makroları derlemeden önce metin olarak yerine koyar; bu yüzden makro parametreleri parantez içinde yazılır. Başlık dosyası (.h) bir modülün arayüzünü, .c dosyası ise gerçeklemesini taşır. Başlıkta yalnız typedef struct kesir Kesir; bildirilirse yapının içi kullanıcıdan gizlenir: bu bir opak tiptir ve C'de soyut veri tipinin karşılığıdır.

/* kesir.h */
#ifndef KESIR_H
#define KESIR_H
typedef struct kesir Kesir;     /* opak: alanlar görünmez */
Kesir *kesir_olustur(int pay, int payda);
void   kesir_sil(Kesir *k);
#endif

#define KARE(x) ((x) * (x))     /* KARE(1 + 2) == 9 */

Kontrol sorusu: Makro #define KARE(x) x * x biçiminde yazılsaydı KARE(1 + 2) kaç olurdu?

Cevap

5. Metin olarak 1 + 2 * 1 + 2 açılır ve çarpma önce yapılır. Parametrelerin ve bütün ifadenin parantez içine alınması bu yüzden gereklidir.

Özyineleme ve Çağrı Yığını

taban durum · yığın çerçevesi · yığın taşması

Kendini çağıran bir fonksiyon, her çağrıda problemi küçültmeli ve bir taban duruma ulaşmalıdır. Her çağrı için çağrı yığınına yerel değişkenleri ve dönüş adresini tutan bir çerçeve eklenir; derinlik n ise ek bellek O(n) olur. Ağaç gezinmeleri, ikili arama, Hanoi kuleleri ve hızlı sıralama bu düzenle yazılır.

long fakt(int n) {
    if (n <= 1) return 1;        /* taban durum */
    return n * fakt(n - 1);      /* küçülen problem */
}
/* fakt(4) -> 4 * fakt(3) -> 4 * 3 * fakt(2)
           -> 4 * 3 * 2 * fakt(1) = 24 */

Kontrol sorusu: fakt(4) çağrıldığında çağrı yığınında aynı anda en çok kaç fakt çerçevesi bulunur?

Cevap

Dört: fakt(4), fakt(3), fakt(2) ve fakt(1). fakt(1) dönmeden hiçbir çerçeve kapanmaz.

İşaretçiler ve bellek

İşaretçiler

& ve * · NULL · takma ad

İşaretçi (pointer), bir nesnenin adresini tutan değişkendir. &x x'in adresini verir, *p p'nin gösterdiği nesneye ulaşır. NULL hiçbir yeri göstermeyen işaretçidir ve derste boş listenin, boş alt ağacın gösterimidir. İki işaretçi aynı nesneyi gösterebilir; biri üzerinden yapılan değişiklik ötekinden de görülür.

int x = 5;
int *p = &x;     /* p, x'in adresini tutar */
int *q = p;      /* q de x'i gösterir      */
*q = 7;          /* x == 7, *p == 7        */
p = NULL;        /* p artık hiçbir yeri göstermez; x hâlâ 7 */

Kontrol sorusu: Son satırdan sonra *q ifadesinin değeri nedir?

Cevap

7. p = NULL yalnız p değişkenini değiştirir; q hâlâ x'i gösterir.

Dizi ve İşaretçi Aritmetiği

a[i] == *(a + i) · öğe sayar, bayt değil

Bir ifade içinde dizinin adı ilk öğesinin adresine dönüşür. İşaretçiye 1 eklemek onu bir bayt değil, bir sonraki öğeye ilerletir: int 4 bayt ise adres 4 artar. a[i] yazımı tanım gereği *(a + i) demektir. Çok boyutlu dizilerin satır öncelikli adres hesabı da bu kurala dayanır.

int a[] = {10, 20, 30, 40};
int *p = a;               /* p == &a[0] */
printf("%d\n", *(p + 2)); /* 30 */
p++;                      /* p == &a[1] */
printf("%d\n", p[1]);     /* 30: p[1] == a[2] */
printf("%td\n", &a[3] - p);  /* 2 öğe */

Kontrol sorusu: int 4 bayt ve a dizisi 1000 adresinden başlıyorsa a + 3 hangi adresi gösterir?

Cevap

1012. Adres 3 öğe, yani 3 · 4 = 12 bayt ilerler.

Dinamik Bellek

malloc · calloc · realloc · free

Yerel değişkenler fonksiyon bitince yok olur; malloc ile ayrılan blok ise free çağrılana kadar heap bellekte yaşar. malloc bellek kalmazsa NULL döndürür, bu yüzden dönüş değeri her seferinde denetlenir. Geri verilmeyen blok bellek sızıntısına (memory leak), geri verildikten sonra kullanılan işaretçi ise sarkan işaretçiye (dangling pointer) yol açar.

int n = 5;
int *a = malloc(n * sizeof *a);
if (a == NULL) return 1;          /* bellek yok */
for (int i = 0; i < n; i++) a[i] = i * i;
int *b = realloc(a, 2 * n * sizeof *a);  /* büyüt */
if (b == NULL) { free(a); return 1; }
a = b;
free(a);
a = NULL;                         /* sarkan işaretçi kalmasın */

Kontrol sorusu: a = realloc(a, yeni_boy); yazımı, realloc başarısız olursa neden sızıntıya yol açar?

Cevap

Başarısızlıkta realloc NULL döndürür ve eski blok yerinde kalır. Sonuç doğrudan a'ya yazılırsa eski bloğun adresi kaybolur ve blok bir daha free edilemez. Örnekteki gibi sonuç önce ayrı bir işaretçide tutulmalıdır.

İşaretçiye İşaretçi

listenin başını değiştirmek · &kok

Bir fonksiyon, çağıranın işaretçisini (örneğin listenin başını ya da ağacın kökünü) değiştirecekse o işaretçinin adresini almalıdır. Tip bu durumda dugum ** olur. Değerle çağrı kuralının işaretçilere uygulanmasıdır: işaretçi de bir değişkendir ve kopyalanarak geçirilir.

void basa_ekle(dugum_gost *bas, dugum_gost yeni) {
    yeni->bag = *bas;   /* yeni düğüm eski başı gösterir */
    *bas = yeni;        /* çağıranın baş işaretçisi değişir */
}

dugum_gost liste = NULL;
basa_ekle(&liste, p);   /* liste artık p'nin düğümünü gösterir */

Kontrol sorusu: Fonksiyon void basa_ekle(dugum_gost bas, dugum_gost yeni) biçiminde yazılıp sonda bas = yeni; yapılsaydı ve liste boşsa, çağrıdan sonra liste ne olurdu?

Cevap

NULL kalırdı. bas, liste'nin kopyasıdır; kopyaya yapılan atama çağıranı etkilemez ve yeni düğüme hiçbir işaretçi kalmaz.

Fonksiyon İşaretçileri ve qsort

void * · karşılaştırma fonksiyonu

Fonksiyonun adı da bir adrestir ve başka bir fonksiyona argüman olarak geçirilebilir. Standart kitaplıktaki qsort, öğe tipini bilmeden çalışır: diziyi void * olarak alır ve iki öğenin sırasını, kullanıcının verdiği karşılaştırma fonksiyonuna sorar. Fonksiyon negatif, sıfır ya da pozitif döndürür.

int artan(const void *x, const void *y) {
    int a = *(const int *)x, b = *(const int *)y;
    return (a > b) - (a < b);   /* -1, 0 ya da 1 */
}

int d[] = {37, 15, 24};
qsort(d, 3, sizeof d[0], artan);   /* d: 15 24 37 */

Kontrol sorusu: Karşılaştırma için kısaca return a - b; yazmak hangi durumda yanlış sonuç verir?

Cevap

Fark int aralığını aşarsa, örneğin a çok büyük pozitif ve b çok büyük negatif olduğunda. Taşma tanımsız davranıştır ve işaret ters çıkabilir; (a > b) - (a < b) bu riski taşımaz.

Matematik

Üsler ve Logaritmalar

lg n = log₂ n · art arda ikiye bölme

Derste lg n, 2 tabanında logaritmadır: n'yi 1'e indirmek için kaç kez ikiye bölmek gerektiğini söyler. 2¹⁰ = 1024 ≈ 10³ olduğundan bir milyon öğede lg n ≈ 20'dir. İkili arama, dengeli ağacın yüksekliği ve heap işlemleri bu yüzden O(lg n) sürer. Logaritmanın tabanı yalnız sabit bir çarpanı değiştirir; büyük O içinde taban yazılmaz.

int ikiye_bolme_sayisi(int n) {
    int k = 0;
    while (n > 1) { n /= 2; k++; }
    return k;                     /* ⌊lg n⌋ */
}
/* 1 → 0, 8 → 3, 1000 → 9, 1024 → 10 */

Kontrol sorusu: Sıralı bir dizide 1.000.000 öğe varsa ikili arama en çok yaklaşık kaç karşılaştırma yapar?

Cevap

Yaklaşık 20. 2²⁰ = 1.048.576 ≥ 1.000.000 olduğu için aralık en çok 20 bölmede tek öğeye iner.

Toplam Formülleri

aritmetik ve geometrik seriler

İç içe döngülerin maliyeti çoğu zaman bir toplamdır. İki formül derste sürekli kullanılır: 1 + 2 + … + n = n(n + 1)/2 ve 1 + 2 + 4 + … + 2ᵏ = 2ᵏ⁺¹ − 1. İlki seçimli ve eklemeli sıralamanın O(n²) olduğunu, ikincisi k derinlikli ikili ağacın en çok 2ᵏ − 1 düğüm taşıdığını ve kapasitesi ikiye katlanan dizide toplam kopya sayısının O(n) kaldığını gösterir.

int sayac = 0;
for (int i = 0; i < n - 1; i++)       /* seçimli sıralama */
    for (int j = i + 1; j < n; j++)
        sayac++;                      /* bir karşılaştırma */
/* sayac == (n-1) + (n-2) + ... + 1 == n(n-1)/2
   n = 5 için 10 */

Kontrol sorusu: Kapasitesi 1'den başlayıp her dolduğunda ikiye katlanan bir diziye 16 öğe eklenirse büyütmeler sırasında toplam kaç öğe kopyalanır?

Cevap

15. Büyütmeler 1, 2, 4 ve 8 öğe dolu iken olur: 1 + 2 + 4 + 8 = 2⁴ − 1 = 15. Öğe başına sabit sayıda kopya düşer.

Tümevarım

taban adım · tümevarım adımı

Bir önerme n = 1 için doğruysa (taban adım) ve n − 1 için doğruluğu n için doğruluğunu gerektiriyorsa (tümevarım adımı), bütün n ≥ 1 için doğrudur. Özyinelemeli bir fonksiyonun doğruluğu da aynı biçimde kanıtlanır: taban durum doğru sonuç verir, küçük çağrılar doğruysa büyük çağrı da doğrudur. Ağaçlardaki düğüm sayısı lemmaları bu yöntemle kanıtlanır.

/* Önerme: toplam(n) == n * (n + 1) / 2 */
int toplam(int n) {
    if (n == 0) return 0;            /* taban: 0 == 0·1/2 */
    return toplam(n - 1) + n;        /* (n-1)n/2 + n == n(n+1)/2 */
}

Kontrol sorusu: İkili ağacın i. seviyesinde en çok 2ⁱ⁻¹ düğüm bulunduğunu kanıtlarken tümevarım adımında hangi gözlem kullanılır?

Cevap

Her düğümün en çok iki çocuğu vardır. (i − 1). seviyede en çok 2ⁱ⁻² düğüm varsa i. seviyede en çok 2 · 2ⁱ⁻² = 2ⁱ⁻¹ düğüm olabilir.

Modüler Aritmetik

% işleci · dairesel indis · hash

x % m, x'in m'ye bölümünden kalandır ve negatif olmayan x için 0 ile m − 1 arasında kalır. Dairesel kuyrukta dizinin sonuna gelen indis (arka + 1) % MAX ile başa döner; hash tablosunda k % m anahtarı bir kovaya eşler. C'de negatif bir sayının kalanı negatif çıkabilir; hash hesaplarında bu yüzden unsigned tipler kullanılır.

#define MAX 5
int arka = 4;
arka = (arka + 1) % MAX;     /* 0: başa dönüldü */

unsigned h(unsigned k, unsigned m) { return k % m; }
/* m = 13: h(27, 13) == 1, h(40, 13) == 1 → çakışma */

Kontrol sorusu: m = 13 için 18 ve 44 anahtarları hangi kovalara düşer?

Cevap

18 % 13 = 5 ve 44 % 13 = 5 (44 = 3 · 13 + 5). İkisi aynı kovaya düşer; bu bir çakışmadır.

Sayma: Faktöriyel ve Alt Kümeler

n! · 2ⁿ · kaba kuvvetin sınırı

n öğenin n! farklı dizilişi ve 2ⁿ farklı alt kümesi vardır. Kaba kuvvet algoritmaları bu sayıların tümünü dener; permütasyon üretimi O(n · n!), alt küme üretimi ve SAT denemesi O(2ⁿ) sürer. 2ⁿ ve n! o kadar hızlı büyür ki n = 25 civarında bile en hızlı makine yetmez; geri izleme ve iyi bir algoritma bu yüzden önemlidir.

/* n öğenin bütün alt kümeleri: her bit bir öğe */
for (unsigned m = 0; m < (1u << n); m++) {
    for (int i = 0; i < n; i++)
        if (m & (1u << i)) printf("%d ", a[i]);
    printf("\n");
}   /* n = 3 için 8 satır, ilki boş küme */

Kontrol sorusu: "abcd" dizgisinin kaç farklı permütasyonu vardır ve her birinde kaç harf yazılır?

Cevap

4! = 24 permütasyon, her birinde 4 harf; toplam 96 harf yazılır. Bu, permütasyon üretiminin O(n · n!) sürmesinin nedenidir.

Algoritma analizine giriş

Adım Sayma

program adımı · döngü sayısı

Bir algoritmanın zamanı, girdi boyutu n'nin fonksiyonu olarak kaç temel adım çalıştığıyla ölçülür. Her atama, karşılaştırma ve dönüş bir adım sayılır; döngünün koşulu gövdeden bir kez fazla denetlenir. Sabitler ayrıntıdır; önemli olan adım sayısının n ile nasıl büyüdüğüdür.

float sum(float list[], int n) {
    float t = 0;                  /* 1 adım      */
    for (int i = 0; i < n; i++)   /* n + 1 adım  */
        t += list[i];             /* n adım      */
    return t;                     /* 1 adım      */
}                                 /* toplam 2n + 3 */

Kontrol sorusu: İç içe iki döngüyle m × n'lik iki matrisi toplayan bir fonksiyonun adım sayısı hangi terimle büyür?

Cevap

m · n terimiyle. İç gövde m · n kez çalışır; döngü denetimleri yalnız daha küçük terimler ekler, sonuç O(m · n) olur.

Büyük O, Ω ve Θ

üst sınır · alt sınır · sıkı sınır

f(n) = O(g(n)), yeterince büyük her n için f(n) ≤ c · g(n) olacak biçimde pozitif bir c sabitinin bulunduğunu söyler: g, f için bir üst sınırdır. Ω alt sınırı, Θ ise ikisinin birlikte geçerli olduğunu gösterir. Alışılmış sıra 1 < lg n < n < n lg n < n² < n³ < 2ⁿ < n! biçimindedir.

/* T(n) = 3n² + 6n + 7 için:
   n ≥ 1 iken 6n ≤ 6n² ve 7 ≤ 7n² olduğundan
   T(n) ≤ 3n² + 6n² + 7n² = 16n²  →  T(n) = O(n²)
   T(n) ≥ 3n²                      →  T(n) = Ω(n²)
   ikisi birlikte                  →  T(n) = Θ(n²) */

Kontrol sorusu: 2n + 3 = O(n²) yazmak yanlış mıdır?

Cevap

Yanlış değildir; O yalnız bir üst sınır söyler ve n² de 2n + 3'ün üstündedir. Ama bilgi vermez: sıkı sınır Θ(n)'dir ve analizde en küçük üst sınır aranır.

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

girdiye bağlı süre · amortize maliyet

Aynı boyuttaki iki girdi farklı sürelerde işlenebilir. Doğrusal aramada aranan ilk öğedeyse tek karşılaştırma yeter (en iyi durum), hiç yoksa n karşılaştırma gerekir (en kötü durum). Ortalama durum, girdilerin dağılımına dair bir varsayım ister. Amortize analiz ise tek tek işlemlere değil bir işlem dizisinin toplam maliyetine bakar.

int dogrusal_ara(const int a[], int n, int x) {
    for (int i = 0; i < n; i++)
        if (a[i] == x) return i;   /* bulundu */
    return -1;                     /* yok: n karşılaştırma */
}

Kontrol sorusu: Aranan öğe dizide varsa ve her konumda olma olasılığı eşitse doğrusal arama ortalama kaç karşılaştırma yapar?

Cevap

(1 + 2 + … + n) / n = (n + 1) / 2. Ortalama durum da Θ(n)'dir.

Yineleme Bağıntıları

özyinelemeli algoritmaların süresi

Özyinelemeli bir algoritmanın süresi kendisi cinsinden yazılır ve açılarak çözülür. Her adımda problemi yarıya indirip sabit iş yapan algoritma T(n) = T(n/2) + c ile O(lg n) sürer (ikili arama). İki yarıyı çözüp doğrusal işle birleştiren algoritma T(n) = 2T(n/2) + cn ile O(n lg n) sürer (birleştirme sıralaması). Hanoi kulelerinde h(n) = 2h(n − 1) + 1 bağıntısı 2ⁿ − 1 verir.

int ikili_ara(const int a[], int sol, int sag, int x) {
    if (sol > sag) return -1;
    int orta = sol + (sag - sol) / 2;
    if (a[orta] == x) return orta;
    if (a[orta] < x) return ikili_ara(a, orta + 1, sag, x);
    return ikili_ara(a, sol, orta - 1, x);
}   /* T(n) = T(n/2) + c  →  O(lg n) */

Kontrol sorusu: T(n) = T(n − 1) + n bağıntısı (T(1) = 1) hangi büyüme sınıfındadır?

Cevap

Θ(n²). Açılınca T(n) = n + (n − 1) + … + 1 = n(n + 1)/2 olur. Her adımda yalnız bir öğe eleyen özyinelemeler bu bağıntıyı verir; hızlı sıralamanın en kötü durumu da böyledir.

Alan Karmaşıklığı ve Ölçüm

ek bellek · clock() ile süre ölçme

Alan karmaşıklığı, girdinin dışında gereken ek belleği ölçer: döngüyle toplama O(1), aynı işi özyinelemeyle yapmak çağrı yığınında O(n) yer tutar. Kuramsal analiz gerçek ölçümle de sınanır: clock() programın harcadığı işlemci zamanını verir. Kısa işler birçok kez tekrarlanarak ölçülür, printf gibi yavaş giriş çıkış işlemleri ölçülen bölgenin dışında tutulur.

#include <time.h>
clock_t bas = clock();
for (int t = 0; t < 1000; t++)
    dogrusal_ara(a, n, -1);        /* en kötü durum */
double sn = (double)(clock() - bas) / CLOCKS_PER_SEC;
printf("arama başına %g sn\n", sn / 1000);

Kontrol sorusu: n büyüdükçe ölçülen süre n ikiye katlandığında yaklaşık dört katına çıkıyorsa algoritmanın büyümesi hakkında ne söylenebilir?

Cevap

Süre büyük olasılıkla n² ile büyümektedir: (2n)² = 4n². Doğrusal bir algoritmada süre yalnız iki katına çıkardı.

Kod örnekleri C99'a göre yazılmıştır; int tipinin 4 bayt olduğu varsayılmıştır. Örneklerdeki dugum ve dugum_gost tipleri “struct ve typedef” kartında tanımlanır.

Sercan KÜLCÜ, Tüm hakları saklıdır.