Bölüm 2: Diziler ve Yapılar
Giriş
Öğrenme Çıktıları
Dizi ADT'sini tanımlayıp C dizisinin bellekteki ardışık yerleşimini açıklamak.
Satır/sütun öncelikli yerleşimde 2, 3 ve n boyutlu dizilerin öğe adresini bulmak.
Üçgen ve kuşak matrisleri tek boyutlu dizide saklayıp O(1) erişim kurmak.
malloc/realloc/free ile büyüyen dizi ve 2B dizi kurmak; amortize maliyeti açıklamak.
struct, union ve dizgileri (char dizisi) doğru ve güvenli kullanmak.
Matris toplama ve çarpımının adım sayısını çıkarıp O(n³) sonucunu göstermek.
Dizi ADT · Horowitz 2.1
Dizi: ⟨indis, değer⟩ çiftlerinden oluşan küme
Soyut bakışta dizi bir eşlemedir (mapping): tanımlı her indise bir değer karşılık gelir. “Ardışık bellek hücreleri” ise bu ADT’nin en yaygın gerçeklemesidir, tanımı değil.
| İşlem | Anlamı |
|---|---|
Create(j, liste) | j boyutlu, indis kümesi liste olan boş dizi |
Retrieve(A, i) | i geçerli indisse A[i] değerini, değilse hata döndür |
Store(A, i, x) | i geçerliyse A'da i ile x'i eşleştirip yeni diziyi döndür |
Başlangıç adresi α ve öğe boyutu s biliniyorsa A[i] adresi α + i·s: tek çarpma + toplama, O(1).
Boyut baştan sabittir; araya ekleme/silme sonraki öğeleri kaydırır: O(n).
Dizi ADT · C gerçeklemesi
C'de dizi bildirimi ve adres
int list[5], *plist[5]; /* list : 5 tamsayı */ /* plist: 5 tamsayı işaretçisi */ list[i] /* adresi: α + i * sizeof(int) */ sizeof list /* 5 * sizeof(int) bayt */
Derleyici list[0]’ın adresini (α) tutar; list[i] için adres çalışma anında hesaplanır. İndis denetlenmez: list[5] yazmak tanımsız davranıştır.
sizeof(int) = 4 varsayımıyla adresler
Dizi ADT · Dizi–işaretçi ilişkisi
Dizi adı ≈ ilk öğenin adresi
| İfade | Eşdeğeri |
|---|---|
list | &list[0] |
list + i | &list[i] (i·sizeof(int) bayt ileri) |
*(list + i) | list[i] |
p = list; p[i] | list[i] ile aynı hücre |
İşaretçi aritmetiği öğe sayar, bayt değil: p + 1 bir sonraki int’i gösterir.
list = p; derlenmez; dizi adı bir değişken değil, sabit adrestir.
Yerel dizide sizeof list tüm diziyi verir; parametrede işaretçi boyutunu.
void f(int a[]) ≡ void f(int *a): dizi kopyalanmaz, adresi geçer; boyut ayrı bir parametreyle gönderilir.
Dizi ADT · Kodu adım adım çalıştır
İşaretçi aritmetiği: *(p + i)
dizi yığında 4 ardışık int olarak ayrılır; ilk öğenin adresine α diyelim.
Çağrıda dizi kopyalanmaz: yalnızca &dizi[0] (α) değeri p'ye geçer.
0 < 4 → döngü sürer. p + 0 = α + 0·4 = α+0.
*(p + 0) o adresteki değeri okur: 10 (≡ p[0]).
1 < 4 → döngü sürer. p + 1 = α + 1·4 = α+4.
*(p + 1) o adresteki değeri okur: 20 (≡ p[1]).
2 < 4 → döngü sürer. p + 2 = α + 2·4 = α+8.
*(p + 2) o adresteki değeri okur: 30 (≡ p[2]).
3 < 4 → döngü sürer. p + 3 = α + 3·4 = α+12.
*(p + 3) o adresteki değeri okur: 40 (≡ p[3]).
4 < 4 yanlış → döngü biter; yazdir döner.
Her satırda adres 4 bayt artar. (Gerçek %p çıktısı onaltılık bir adrestir; burada α ile gösterildi.)
void yazdir(int *p, int n) { int i; for (i = 0; i < n; i++) printf("%p %d\n", (void *)(p + i), *(p + i)); } int main(void) { int dizi[] = {10, 20, 30, 40}; yazdir(dizi, 4); return 0; }
Dinamik Diziler · malloc / free
Boyutu çalışma anında belli olan dizi
int n, i, *a; scanf("%d", &n); a = malloc(n * sizeof *a); /* n int */ if (a == NULL) { fprintf(stderr, "Yetersiz bellek\n"); exit(EXIT_FAILURE); } for (i = 0; i < n; i++) a[i] = i * i; /* dizi gibi */ free(a); /* geri ver */ a = NULL; /* sarkan işaretçi olmasın */
Yerel dizi fonksiyon bitince yok olur; malloc bloğu free edilene dek yaşar.
sizeof *a kalıbıTür değişse de doğru boyutu verir; sizeof(int*) ile karıştırma hatasını önler.
calloc(n, s)Bloğu sıfırlayarak ayırır; realloc bloğu büyütür/küçültür.
Dinamik Diziler · Kodu adım adım çalıştır
Büyüyen dizi: kapasiteyi ikiye katla
Boş dinamik dizi: {NULL, 0, 0}. Sırayla 7, 3, 9, 4, 6 eklenecek.
0 == 0 → dolu, büyütülmeli.
yeni = 1. realloc 1 hücrelik blok verir; eski 0 öğe kopyalanır (NULL'dan realloc = malloc). Önce t'ye alınır: başarısızsa eski blok kaybolmaz.
t != NULL: d->veri = t, kap = 1.
veri[0] = 7, sonra boyut++ → 1. (Son ek: önce kullanılır, sonra artırılır.)
1 == 1 → dolu, büyütülmeli.
yeni = 2. realloc 2 hücrelik blok verir; eski 1 öğe kopyalanır (toplam kopya 1). Önce t'ye alınır: başarısızsa eski blok kaybolmaz.
t != NULL: d->veri = t, kap = 2.
veri[1] = 3, sonra boyut++ → 2. (Son ek: önce kullanılır, sonra artırılır.)
2 == 2 → dolu, büyütülmeli.
yeni = 4. realloc 4 hücrelik blok verir; eski 2 öğe kopyalanır (toplam kopya 3). Önce t'ye alınır: başarısızsa eski blok kaybolmaz.
t != NULL: d->veri = t, kap = 4.
veri[2] = 9, sonra boyut++ → 3. (Son ek: önce kullanılır, sonra artırılır.)
3 == 4 → yer var, doğrudan yazılır.
veri[3] = 4, sonra boyut++ → 4. (Son ek: önce kullanılır, sonra artırılır.)
4 == 4 → dolu, büyütülmeli.
yeni = 8. realloc 8 hücrelik blok verir; eski 4 öğe kopyalanır (toplam kopya 7). Önce t'ye alınır: başarısızsa eski blok kaybolmaz.
t != NULL: d->veri = t, kap = 8.
veri[4] = 6, sonra boyut++ → 5. 5 eklemede toplam yalnız 7 kopya yapıldı.
typedef struct { int *veri; int boyut, kap; } DinDizi; int ekle(DinDizi *d, int x) { if (d->boyut == d->kap) { /* dolu */ int yeni = d->kap ? 2 * d->kap : 1; int *t = realloc(d->veri, yeni * sizeof *t); if (t == NULL) return 0; /* eski blok sağlam */ d->veri = t; d->kap = yeni; } d->veri[d->boyut++] = x; return 1; }
Dinamik Diziler · Analiz
İkiye katlama: ekleme başına amortize O(1)
| n ekleme | katlama: kopya | kopya / n | +1 büyütme: kopya | kopya / n |
|---|---|---|---|---|
| 8 | 7 | 0.88 | 28 | 3.5 |
| 64 | 63 | 0.98 | 2016 | 31.5 |
| 1024 | 1023 | 1.00 | 523776 | 511.5 |
Katlamada kopyalar 1 + 2 + 4 + … + 2k < 2n → toplam O(n) (n yazmayla birlikte toplam maliyet < 3n), ekleme başına O(1) (amortize). Her seferinde 1 (ya da sabit c) büyütmek toplam n(n−1)/2 = O(n²) kopya demektir.
Büyütme anındaki ekleme pahalıdır; amortize analiz uzun bir dizi işlemin ortalamasını garanti eder.
Kapasite en çok 2·boyut olur; en fazla %50 boş hücre bedelidir.
realloc tuzağıa = realloc(a, …) başarısız olursa NULL döner ve eski blok kaybolur (sızıntı). Önce geçici değişkene alınmalı.
Dinamik Diziler · Kodu adım adım çalıştır
2B dinamik dizi: işaretçi dizisi + satırlar
Önce 3 hücrelik işaretçi dizisi ayrılır (sizeof *x = sizeof(int *)); içerikler henüz belirsiz.
x != NULL: ayırma başarılı, devam.
i = 0 < 3 → 0. satır ayrılacak.
x[0] 4 int'lik yeni bloğu gösterir (sizeof **x = sizeof(int)). NULL değil.
i = 1 < 3 → 1. satır ayrılacak.
x[1] 4 int'lik yeni bloğu gösterir (sizeof **x = sizeof(int)). NULL değil.
i = 2 < 3 → 2. satır ayrılacak.
x[2] 4 int'lik yeni bloğu gösterir (sizeof **x = sizeof(int)). NULL değil.
3 < 3 yanlış → döngü biter.
x döner; x[i][j] ≡ *(*(x + i) + j). Satırlar ardışık olmak zorunda değil. Bırakırken önce her x[i], sonra x free edilir.
int **dizi2b(int satir, int sutun) { int i, **x = malloc(satir * sizeof *x); if (x == NULL) return NULL; for (i = 0; i < satir; i++) { x[i] = malloc(sutun * sizeof **x); if (x[i] == NULL) { /* geri al */ while (--i >= 0) free(x[i]); free(x); return NULL; } } return x; }
Çok Boyutlu Diziler
Bellek tek boyutlu, dizi çok boyutlu
A[upper0][upper1]…[uppern−1] olarak tanımlı n boyutlu dizide ∏ upperi öğe vardır. Ör. A[10][10][10] → 1000 öğe.
Bellek ise tek boyutlu bir bayt dizisidir; derleyici öğeleri bir sıraya dizmek zorundadır:
- Satır öncelikli (row major): önce 0. satırın tümü, sonra 1. satır… (C, C++, Python/NumPy varsayılanı)
- Sütun öncelikli (column major): önce 0. sütun… (Fortran, MATLAB)
int x[3][4]; aslında 4'lük dizilerden oluşan 3 öğeli bir dizidir; x[1] 1. satırın başını gösterir.
Çok Boyutlu Diziler
int x[3][4] belleğe iki biçimde dizilebilir
Satır öncelikli: x[i][j]'den önce i tam satır (i·upper1 öğe) ve j öğe vardır → &x[i][j] = α + (i·upper1 + j)·s. C bunu kullanır.
Sütun öncelikli: önce j tam sütun (j·upper0 öğe), sonra i öğe → &x[i][j] = α + (j·upper0 + i)·s. Aynı öğe farklı konumda: x12 → satırda 6, sütunda 7.
α + i·upper1 + j formülü öğe (hücre) birimindedir; bayt adresi için s = sizeof(öğe) ile çarpılır.
Çok Boyutlu Diziler · Kodu adım adım çalıştır
Satır öncelikli adres hesabı
x[1][2]'nin adresi istenir: α = 100, satır uzunluğu upper1 = 4, s = 4 bayt.
Önce 1 tam satır (1·4 = 4 öğe) ve satırında 2 öğe var → k = 6 (mavi hücreler).
100 + 6·4 = 124 döner.
printf 124 yazar. Sütun öncelikli olsaydı k = j·3 + i = 7 → 128 olurdu.
x[2][3]'nin adresi istenir: α = 100, satır uzunluğu upper1 = 4, s = 4 bayt.
Önce 2 tam satır (2·4 = 8 öğe) ve satırında 3 öğe var → k = 11 (mavi hücreler).
100 + 11·4 = 144 döner.
printf 144 yazar. Son öğe: 100 + 11·4 = 144.
/* int x[3][4]: satır öncelikli bayt adresi */ long adres(long alfa, int i, int j, int ust1, int s) { long k = (long)i * ust1 + j; /* öncekiler */ return alfa + k * s; } int main(void) { printf("%ld\n", adres(100, 1, 2, 4, 4)); printf("%ld\n", adres(100, 2, 3, 4, 4)); return 0; }
Çok Boyutlu Diziler
G[2][2][3]: dilim · satır · sütun
| öğe | adres (öğe birimi) |
|---|---|
[0][0][0] | α |
[i][0][0] | α + i·u1·u2 |
[i][j][0] | α + i·u1·u2 + j·u2 |
[i][j][k] | α + i·u1·u2 + j·u2 + k |
Son ifadede 3 çarpma + 3 toplama var. Çarpma toplamadan pahalı olduğundan derleyici paranteze alır:
α + (i·u1 + j)·u2 + k
→ 2 çarpma + 3 toplama. Örnek: [1][0][2] → (1·2 + 0)·3 + 2 = 8.
Çok Boyutlu Diziler
n boyutlu dizide adres formülü
A[u0][u1]…[un−1] için:
| öğe | adres |
|---|---|
| A[0][0]…[0] | α |
| A[i0][0]…[0] | α + i0·u1u2…un−1 |
| A[i0][i1]…[0] | … + i1·u2…un−1 |
| A[i0]…[in−1] | … + in−1 |
&A[i0]…[in−1] = α + Σj=0n−1 ij·aj
aj = ∏k=j+1n−1 uk (0 ≤ j < n−1), an−1 = 1
Çok Boyutlu Diziler · Kodu adım adım çalıştır
n boyutlu bağıl konum: Horner yöntemi
G[1][0][2] aranıyor. t = ind[0] = 1: 1 numaralı dilim.
j = 1 < 3 → bir boyut daha içeri.
t = 1·2 + 0 = 2: bu, dilimler boyunca sayılan “genel satır” numarası (dilim 1, satır 0).
j = 2 < 3 → bir boyut daha içeri.
t = 2·3 + 2 = 8: 8 öğe önde → hücre 8.
3 < 3 yanlış → döngü biter.
8 döner. Toplam 2 çarpma (n−1). Kontrol: 1·2·3 + 0·3 + 2 = 8. ust[0] hiç kullanılmadı!
/* ind: indisler, ust: boyutlar, n: boyut sayısı */ int ofset(const int ind[], const int ust[], int n) { int j, t = ind[0]; for (j = 1; j < n; j++) t = t * ust[j] + ind[j]; /* Horner */ return t; /* adres = alfa + t*s */ }
Üçgen ve Kuşak Matrisler
Matrisin yarısını tek boyutlu dizide tutmak
Kare matris köşegenle ikiye bölünür; bazen bir yarısı yeter (şehirler arası uzaklıklar, periyodik tablodaki atom ağırlıkları…). Veri tek boyutlu ALT dizisinde tutulur; kullanıcıya iki boyutlu bir matrisle çalışıyormuş arayüzü sunulur.
i. satırda i+1 öğe var → toplam n(n+1)/2. aij, önceki satırların öğeleri + satırdaki sırası:
k = i(i+1)/2 + j (j ≤ i)
Üçgen ve Kuşak Matrisler · Kodu adım adım çalıştır
Alt üçgeni okumak: satır başı k
4·5/2 = 10 ≤ 100: ALT dizisine sığar. Girdi: 3 1 4 1 5 9 2 6 5 3.
k = 1·0/2 = 0: 0. satırdan önce 0 öğe yerleşti.
alt[0+0] = alt[0] ← 3 (a00).
k = 2·1/2 = 1: 1. satırdan önce 1 öğe yerleşti.
alt[1+0] = alt[1] ← 1 (a10).
alt[1+1] = alt[2] ← 4 (a11).
k = 3·2/2 = 3: 2. satırdan önce 3 öğe yerleşti.
alt[3+0] = alt[3] ← 1 (a20).
alt[3+1] = alt[4] ← 5 (a21).
alt[3+2] = alt[5] ← 9 (a22).
k = 4·3/2 = 6: 3. satırdan önce 6 öğe yerleşti.
alt[6+0] = alt[6] ← 2 (a30).
alt[6+1] = alt[7] ← 6 (a31).
alt[6+2] = alt[8] ← 5 (a32).
alt[6+3] = alt[9] ← 3 (a33).
10 değer okundu; üst üçgendeki 6 sıfır hiç saklanmadı.
#define MAX_SIZE 100 void AltUcgenOku(int alt[], int n) { int i, j, k; if (n * (n + 1) / 2 > MAX_SIZE) { printf("Alt dizi boyutu yetersiz!\n"); exit(1); } for (i = 0; i < n; i++) { k = (i + 1) * i / 2; /* öncekiler */ for (j = 0; j <= i; j++) /* j <= i */ scanf("%d", &alt[k + j]); } }
Üçgen ve Kuşak Matrisler · Kodu adım adım çalıştır
Erişim: indis mi, hata kodu mu?
a21 isteniyor.
Sınır denetimi: indisler 0…3 aralığında.
2 < 1? Hayır → alt üçgende.
k = 3·2/2 + 1 = 4 → ALT[4] = a21.
printf 4 yazar.
a13 isteniyor.
Sınır denetimi: indisler 0…3 aralığında.
1 < 3? Evet → üst üçgen (sıfırlı bölge): −1.
printf -1 yazar. Hata kodları {−1, −2} ile geçerli dönüşler {0, 1, 2, …} kesişmez: çağıran durumu ayırt edebilir.
a40 isteniyor.
Sınır denetimi: i = 4 ≥ n → geçersiz!
Hata iletisi basılır ve −2 döner.
printf -2 yazar.
int AltUcgenEris(int i, int j, int n) { if (i < 0 || i >= n || j < 0 || j >= n) { printf("Gecersiz indis\n"); return -2; } else if (i < j) return -1; /* üst üçgen: 0 */ else return (i + 1) * i / 2 + j; /* geçerli */ } /* main: */ printf("%d\n", AltUcgenEris(2, 1, 4)); printf("%d\n", AltUcgenEris(1, 3, 4)); printf("%d\n", AltUcgenEris(4, 0, 4));
Üçgen ve Kuşak Matrisler · ÇALIŞMA
ÇALIŞMA çözümü: dört üçgen türü ve simetrik matris
| saklanan bölge | satır i'deki öğe | k (satır öncelikli) | |
|---|---|---|---|
| j ≤ i (alt sol) | i + 1 | i(i+1)/2 + j | |
| j ≥ i (üst sağ) | n − i | i·n − i(i−1)/2 + (j − i) | |
| i + j ≤ n−1 (sol üst) | n − i | i·n − i(i−1)/2 + j | |
| i + j ≥ n−1 (sağ alt) | i + 1 | i(i+1)/2 + j − (n−1−i) |
k = j(j+1)/2 + i. Formüller n = 1…8 için betikle doğrulandı.Üçgen ve Kuşak Matrisler
Kuşak (band) matris: yalnız köşegen şeridi saklanır
Kuşak dışındaki öğeler kullanılmaz ya da hepsi aynı sabit c değerindedir. n: kenar (4), a: alt kuşak sayısı köşegen dâhil (3), b: üst kuşak sayısı köşegen dâhil (2).
öğe sayısı = n(a+b−1) − a(a−1)/2 − b(b−1)/2 = 12
Üçgen ve Kuşak Matrisler
Kuşak numarası j − i ve ARA dizisi
- Aynı kuşaktaki öğelerde j − i sabittir: d20 ve d31 → 0−2 = 1−3 = −2. Ana köşegen 0; negatifler alt, pozitifler üst kuşak.
- Geçerli kuşaklar [1 − a, b − 1] kapalı aralığında; dışı sabit bölge.
- ARA her kuşağın ilk öğesinin
kusak[]içindeki konumunu tutar: a + b − 1 öğe. C'de negatif indis olmadığından kuşak no + a − 1 ile kaydırılır. - Kuşak içindeki sıra: alt kuşakta j, üst kuşakta i (kuşağın ilk öğesi 0. satır/0. sütundadır).
Üçgen ve Kuşak Matrisler · Kodu adım adım çalıştır
Kuşak matrisi kurmak (alt kuşaklar)
4·4 − 3 − 1 = 12 ≤ MAX_SIZE. Girdi kuşak kuşak gelir: 6 10 · 3 7 11 · 1 4 8 12 · 2 5 9.
Kuşak i = -2 (alt): 2 öğe.
ara[-2+2] = ara[0] = 0: kuşak kusak[0]'den başlar.
kusak[0+0] ← 6 (d20).
kusak[0+1] ← 10 (d31).
toplamoge += 2 → 2: sonraki kuşağın başı.
Kuşak i = -1 (alt): 3 öğe.
ara[-1+2] = ara[1] = 2: kuşak kusak[2]'den başlar.
kusak[2+0] ← 3 (d10).
kusak[2+1] ← 7 (d21).
kusak[2+2] ← 11 (d32).
toplamoge += 3 → 5: sonraki kuşağın başı.
void KusakMatrisKur(int kusak[], int ara[], int n, int a, int b) { int i, k, toplamoge = 0; /* önceki kuşaklar */ if (n*(a+b-1) - a*(a-1)/2 - b*(b-1)/2 > MAX_SIZE) { printf("Bellek yetersiz!\n"); exit(-1); } for (i = 1 - a; i <= b - 1; i++) { /* kuşak kuşak */ ara[i + a - 1] = toplamoge; /* kuşak başı */ for (k = 0; k <= n - abs(i) - 1; k++) scanf("%d", &kusak[ara[i + a - 1] + k]); toplamoge += n - abs(i); } }
Üçgen ve Kuşak Matrisler · Kodu adım adım çalıştır
Kuşak matrisi kurmak (devam: köşegen ve üst)
Kuşak i = 0 (ana köşegen): 4 öğe.
ara[0+2] = ara[2] = 5: kuşak kusak[5]'den başlar.
kusak[5+0] ← 1 (d00).
kusak[5+1] ← 4 (d11).
kusak[5+2] ← 8 (d22).
kusak[5+3] ← 12 (d33).
toplamoge += 4 → 9: sonraki kuşağın başı.
Kuşak i = 1 (üst): 3 öğe.
ara[1+2] = ara[3] = 9: kuşak kusak[9]'den başlar.
kusak[9+0] ← 2 (d01).
kusak[9+1] ← 5 (d12).
kusak[9+2] ← 9 (d23).
toplamoge += 3 → 12: sonraki kuşağın başı.
void KusakMatrisKur(int kusak[], int ara[], int n, int a, int b) { int i, k, toplamoge = 0; /* önceki kuşaklar */ if (n*(a+b-1) - a*(a-1)/2 - b*(b-1)/2 > MAX_SIZE) { printf("Bellek yetersiz!\n"); exit(-1); } for (i = 1 - a; i <= b - 1; i++) { /* kuşak kuşak */ ara[i + a - 1] = toplamoge; /* kuşak başı */ for (k = 0; k <= n - abs(i) - 1; k++) scanf("%d", &kusak[ara[i + a - 1] + k]); toplamoge += n - abs(i); } }
Üçgen ve Kuşak Matrisler · Kodu adım adım çalıştır
Kuşak matriste O(1) erişim
d31 isteniyor; indisler geçerli. Kuşak no j − i = -2.
i − j = 2 < a = 3? Evet → alt kuşak.
ara[-2+2] + j = 0 + 1 = 1 → kusak[1] = 10.
d12 isteniyor; indisler geçerli. Kuşak no j − i = 1.
1 ≥ 1 → üst üçgen.
1 < b = 2 → geçerli üst kuşak: ara[1+2] + i = 9 + 1 = 10.
d03 isteniyor; indisler geçerli. Kuşak no j − i = 3.
3 ≥ 1 → üst üçgen.
3 ≥ b = 2 → kuşak dışı, üst sabit bölge: −1 (değer c).
d30 isteniyor; indisler geçerli. Kuşak no j − i = -3.
i − j = 3 < a = 3? Hayır → kuşak dışı.
Alt sabit bölge: −2. Her durumda sabit sayıda işlem → O(1).
int KusakMatrisEris(int i, int j, int n, int a, int b, int ara[]) { if (i < 0 || j < 0 || i >= n || j >= n) { printf("Indis tasmasi\n"); return -3; } else if (j - i >= 1) { /* üst üçgen */ if (j - i < b) /* geçerli kuşak */ return ara[j - i + a - 1] + i; else return -1; /* üst sabit bölge */ } else if (i - j < a) /* köşegen / alt */ return ara[j - i + a - 1] + j; else return -2; /* alt sabit bölge */ }
Üçgen ve Kuşak Matrisler
ARA dizisi mi, formül mü? (ÖDEV 1)
KusakMatrisEris girdiye bağlı hiçbir döngü içermez → O(1). Kuşak başlarını döngüyle saymak karmaşıklığı artırırdı; ARA bunu önler ama bellek harcar ve programı “dinamik olmaktan uzaklaştırır”.
ÖDEV 1: ARA dizisinden yardım almadan kuşak başını hesaplayın. (Örnek çözüm yanda.)
/* t: kuşak no (1-a..b-1); ara[t+a-1] ile aynı */ int kusak_basi(int t, int n, int a) { if (t <= 0) /* alt kuşaklar */ return (t + a - 1) * n - (a * (a - 1) + t * (1 - t)) / 2; return (a - 1) * n - a * (a - 1) / 2 /* t=0 başı */ + t * n - t * (t - 1) / 2; }
n = 4, a = 3: t = −2, −1, 0, 1 → 0, 2, 5, 9 (ARA ile aynı). Betikle n ≤ 11 için tüm a, b denendi. Erişim: kusak_basi(j−i, n, a) + (j > i ? i : j).
Üçgen ve Kuşak Matrisler
if (ifade1 && ifade2) güvenli mi?
Sık duyulan bir kanı: “Derleyicinin sağdan sola mı, soldan sağa mı taradığını bilemeyiz; bu yüzden şu iki eşdeğer ifadeden ikincisi daha güvenlidir.”
/* 1 */ if (i < n && a[i] != 0) ... /* 2 */ if (i < n) if (a[i] != 0) ...
&& ve || için soldan sağa değerlendirmeyi ve kısa devreyi garanti eder (C99 §6.5.13–14): sol taraf yanlışsa sağ taraf hiç hesaplanmaz. Yani iki biçim aynı derecede güvenlidir; 1. biçim yaygın ve doğru bir kalıptır.& (bit düzeyi VE) kısa devre yapmaz; f(x) + g(x) gibi işlenenlerin sırası ise belirtilmemiştir.
Yapılar · Horowitz 2.2
struct: farklı türde alanları tek kayıtta toplamak
typedef struct { char ad[10]; int yas; float maas; } insan; insan k1, k2; strcpy(k1.ad, "Ali"); k1.yas = 21; k1.maas = 3500.0f; k2 = k1; /* alan alan kopyalanır */ /* if (k1 == k2) -> DERLENMEZ */
k1.yas değişkenin alanı; p->yas ≡ (*p).yas (p: insan *).
Yapılar bütün olarak atanabilir ama karşılaştırılamaz; alan alan karşılaştıran bir fonksiyon yazılır.
typedef struct {…} insan; ile her seferinde struct yazmaktan kurtuluruz.
Yapılar · Horowitz 2.2
Yapı içinde yapı · kendine başvuran yapı
typedef struct { int gun, ay, yil; } tarih; typedef struct { char ad[10]; tarih dogum; /* iç içe yapı */ } kisi; /* k.dogum.yil = 2004; */ typedef struct liste { char veri; struct liste *bag; /* kendine başvuru */ } liste;
Kendine başvuran yapılar bağlı listelerin temelidir.
Yapılar · Horowitz 2.2
union: alanlar aynı belleği paylaşır
typedef struct { enum { KADIN, ERKEK } tur; /* etiket */ union { int cocuk_sayisi; /* KADIN ise */ int sakal; /* ERKEK ise */ } u; } cinsiyet; cinsiyet c; c.tur = ERKEK; c.u.sakal = 0; /* yalnız etiketle uyumlu alanı oku */
union boyutu en büyük alan kadardır (+ hizalama). Hangi alanın geçerli olduğunu etiket alanıyla izlemek programcının işidir.
Alan sırası bellek düzenini değiştirir (derleyiciye bağlı; tipik 32/64 bit değerler)
Yapılar · Kodu adım adım çalıştır
Yapı dizisi üzerinde gezinme
Yapı dizisi bellekte kayıt kayıt ardışık durur; her kayıt sizeof(Ogrenci) bayt.
0 < 3 → 0. kayıt.
s[0].ort = 3.2 eklenir → top = 3.2.
1 < 3 → 1. kayıt.
s[1].ort = 2.6 eklenir → top = 5.8.
2 < 3 → 2. kayıt.
s[2].ort = 3.5 eklenir → top = 9.3.
3 < 3 yanlış → döngü biter.
n > 0 → 9.3 / 3 = 3.10 döner. n = 0 olsaydı sıfıra bölme yerine 0.0.
typedef struct { int no; char ad[16]; double ort; } Ogrenci; double sinif_ort(const Ogrenci s[], int n) { double top = 0.0; int i; for (i = 0; i < n; i++) top += s[i].ort; /* . ile alan */ return n > 0 ? top / n : 0.0; /* boş sınıf */ }
const Ogrenci s[]: dizi kopyalanmaz; const fonksiyonun kayıtları değiştirmeyeceğini belgeler.
Dizgiler · Horowitz 2.6 · <string.h>
C'de dizgi: '\0' ile biten char dizisi
char s[] = "dog"; /* 4 bayt: d o g \0 */ char t[10]; char *p = "dog"; /* salt okunur sabit */ strlen(s) /* 3 : \0'a kadar */ sizeof s /* 4 : dizinin boyutu */ strcpy(t, s); /* t yeterince büyük mü? */ snprintf(t, sizeof t, "%s!", s); /* güvenli */ if (strcmp(s, "dog") == 0) ... /* == değil! */
| fonksiyon | iş |
|---|---|
strlen | uzunluk (\0 hariç) |
strcpy / strncpy | kopyala |
strcat | sona ekle |
strcmp | sözlük sırası karşılaştır |
strchr / strstr | karakter / alt dizgi ara |
Dizgiler · Kodu adım adım çalıştır
strlen nasıl çalışır?
p, s ile aynı yeri (ilk karakteri) gösterir.
*p = 'v' ≠ '\0' → devam.
p++: bir karakter (1 bayt) ileri.
*p = 'e' ≠ '\0' → devam.
p++: bir karakter (1 bayt) ileri.
*p = 'r' ≠ '\0' → devam.
p++: bir karakter (1 bayt) ileri.
*p = 'i' ≠ '\0' → devam.
p++: bir karakter (1 bayt) ileri.
*p = '\0' = '\0' → döngü biter.
p − s = 4 döner. Uzunluk için dizgi baştan sona taranır: O(n) — döngü koşulunda strlen çağırmak O(n²) yapar!
size_t uzunluk(const char *s) { const char *p = s; while (*p != '\0') /* sonlandırıcıya dek */ p++; return (size_t)(p - s); /* işaretçi farkı */ } /* uzunluk("veri") */
Matris İşlemleri · Kodu adım adım çalıştır
Matris çarpımı: üç iç içe döngü
c[0][0] için a'nın 0. satırı ile b'nin 0. sütunu kullanılacak; önce 0.
k = 0: += a[0][0]·b[0][0] = 1·5 → 5.
k = 1: += a[0][1]·b[1][0] = 2·7 → 19.
c[0][1] için a'nın 0. satırı ile b'nin 1. sütunu kullanılacak; önce 0.
k = 0: += a[0][0]·b[0][1] = 1·6 → 6.
k = 1: += a[0][1]·b[1][1] = 2·8 → 22.
c[1][0] için a'nın 1. satırı ile b'nin 0. sütunu kullanılacak; önce 0.
k = 0: += a[1][0]·b[0][0] = 3·5 → 15.
k = 1: += a[1][1]·b[1][0] = 4·7 → 43.
c[1][1] için a'nın 1. satırı ile b'nin 1. sütunu kullanılacak; önce 0.
k = 0: += a[1][0]·b[0][1] = 3·6 → 18.
k = 1: += a[1][1]·b[1][1] = 4·8 → 50.
Bitti: [[19, 22], [43, 50]]. En içteki satır N³ = 8 kez çalıştı → O(n³).
#define N 2 void mult(int a[][N], int b[][N], int c[][N]) { int i, j, k; for (i = 0; i < N; i++) for (j = 0; j < N; j++) { c[i][j] = 0; for (k = 0; k < N; k++) c[i][j] += a[i][k] * b[k][j]; } }
Matris İşlemleri · ödev
Ödevler: matris çarpımı ve transpoz
mult(n×n) için adım sayısı tablosunu çıkarın; kaç çarpma yapılır?- m×n ile n×p matrisin çarpımının karmaşıklığı nedir? Sonuç matrisi kaç hücre?
- n×n bir matrisin transpozunu ek dizi kullanmadan (yerinde) alan yordamı yazın.
Çözümler
- for i: n+1 · for j: n(n+1) · c=0: n² · for k: n²(n+1) · +=: n³ → 2n³ + 3n² + 2n + 1 = O(n³); n³ çarpma. (n = 1…9 için betikle doğrulandı.)
- Her c[i][j] için n çarpım: O(m·n·p); m·p hücre.
for (i = 0; i < n; i++) for (j = i + 1; j < n; j++) { t = a[i][j]; a[i][j] = a[j][i]; a[j][i] = t; }— yalnız üst üçgen gezilir (j = 0'dan başlasaydı her çift iki kez takas edilir, matris değişmezdi). n(n−1)/2 takas → O(n²).
Laboratuvar · C kodu adım adım
Laboratuvar: Dinamik Dizi
Kapanış
Karmaşıklık Özeti
| İşlem | Zaman | Ek bellek |
|---|---|---|
| A[i] erişimi; n boyutlu adres (n−1 çarpma) | O(1) | O(1) |
| Dinamik diziye sona ekleme (katlama) | amortize O(1), en kötü O(n) | ≤ 2·boyut |
| Araya ekleme / silme | O(n) | O(1) |
| Alt üçgen / kuşak matris erişimi | O(1) | n(n+1)/2 · kuşak öğeleri |
| KusakMatrisKur, AltUcgenOku | O(saklanan öğe) | ARA: a+b−1 |
| strlen / uzunluk | O(n) | O(1) |
| Matris toplama (m×n) | O(mn) | O(1) |
| Matris çarpımı (n×n) | O(n³) | O(1) |
Kapanış
Sık Yapılan Hatalar
a[n]); C bunu denetlemez.a = realloc(a, …): başarısızlıkta eski bloğu kaybetmek.sizeof(int *) ile sizeof(int)’i karıştırmak; parametrede sizeof a kullanmak.free etmeden yalnız işaretçi dizisini bırakmak.== kullanmak ya da '\0' için yer ayırmamak.Kapanış
Alıştırmalar
double d[5][8]satır öncelikli, α = 2000:&d[3][5]kaçtır? Sütun öncelikli olsaydı?int A[4][5][6]için A[2][3][4]'ün bağıl konumunu Horner ile hesaplayın.- 5×5 alt üçgen matriste a43 ALT dizisinin kaçıncı konumunda?
- n = 6, a = 2, b = 3 kuşak matrisi kaç öğe tutar? ARA dizisi nedir?
- Kapasitesi 1'den başlayıp ikiye katlanan dinamik diziye 100 öğe eklenirse toplam kaç kopya yapılır?
struct { char a; double b; char c; }için alanlar yeniden sıralanarak boyut nasıl küçültülür?
Cevaplar
- Satır: 2000 + (3·8 + 5)·8 = 2232. Sütun: 2000 + (5·5 + 3)·8 = 2224.
- (2·5 + 3)·6 + 4 = 82.
- k = 4·5/2 + 3 = 13.
- 6·4 − 1 − 3 = 20; kuşaklar −1, 0, 1, 2 → uzunluk 5, 6, 5, 4 → ARA = [0, 5, 11, 16].
- 1 + 2 + 4 + … + 64 = 127 (< 2·100).
- double başa alınırsa:
{ double b; char a, c; }→ tipik 16 bayt (24 yerine).
Kapanış
Bölüm Özeti
ADT: ⟨indis, değer⟩; C gerçeklemesi ardışık bellek ve α + i·s adresi.
Satır öncelikli: α + (i·u1 + j)·s; n boyutta Horner ile n−1 çarpma.
Üçgen: n(n+1)/2 öğe, k = i(i+1)/2 + j. Kuşak: j − i numaralı kuşaklar + ARA.
malloc/realloc/free; katlayarak büyüme amortize O(1); 2B dizi = işaretçi dizisi.
struct alanları toplar, union paylaştırır; dizgi '\0' ile biten char dizisidir.
Toplama O(mn), çarpma O(n³).
Kapanış
Kaynaklar ve Devam
- E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, 2. bs., Bölüm 2 (diziler, yapılar, çok boyutlu diziler, dizgiler) ve 1.4.
- B. Kernighan, D. Ritchie, The C Programming Language, 2. bs., Bölüm 5–6 (işaretçiler, diziler, yapılar).
- T. Cormen ve ark., Introduction to Algorithms, 17.4 (dinamik tablolar ve amortize analiz).