Ders 06 / 15
Bağlı Yapılar
Bağlı liste ve ikili arama ağacının C gerçeklemesi; bir düğümü takma adla değiştirmenin ve sığ kopyanın paylaşılan görünürlüğü, derin kopyanın ise onu kestiği ölçümü.
Yayın Güncelleme
Bu derste
- Bağlı liste ve ikili arama ağacını ekleme, dolaşma ve bırakma işlemleriyle C'de gerçekler
- Bir işaretçinin takma adla paylaşıldığı düğümü, adres basmadan eşitlik ve değerle ölçer
- Sığ kopya ile derin kopyanın görünürlük farkını (ortak/kopya) aynı listede gösterir
İçindekiler
Önceki ders bir belleği kimin bırakacağını ölçtü. Bu kurs farklı bir eksenle
başlıyor: bir düğümü değiştirdiğinde bu değişikliği kimin göreceği. Aynı bellek
bloğuna iki farklı işaretçi tutulduğunda, birinden yapılan bir değişiklik ötekinden de
görünür — kaynak bunu söylemez, çünkü iki değişkenin bildirimi de aynıdır (struct Dugum *). Bağlı liste bu paylaşımın en açık örneğidir: bir sonraki düğüme giden alan bir
işaretçi taşır ve o işaretçiyi kim tutuyorsa aynı düğümü görür.
Bu ders bağlı listeyi ve ikili arama ağacını sıfırdan kurup dört sınıftan ortak’ı ve kopya‘yı aynı yapı üzerinde karşılaştırıyor: bir düğüme takma adla erişip değiştirmek paylaşılan bir görünürlük üretir (ortak); listenin tamamını yeni düğümlerle çoğaltmak bu paylaşımı keser (kopya). Önceki kursun (**C’de Bellek ve İşaretçiler**) sorduğu “bu düğümü kim bırakacak” sorusu burada da geçerli — ağacı bıraktığımızda hâlâ o sözleşmeye uyuyoruz — ama bu dersin ölçtüğü eksen ayrı: sahiplik değil, görünürlük.
Bağlı Liste: Ekleme, Dolaşma, Bırakma
Liste tek yönlü ve her ekleme başa yapılıyor. liste_toplam bütün düğümleri dolaşıp
değerlerini topluyor, liste_serbest her düğümü bırakmadan önce bir sonrakini saklıyor.
liste_sig_kopyala yeni bir düğüm ayırmıyor, gelen başı olduğu gibi döndürüyor; bu yüzden
döndürdüğü liste orijinaliyle aynı düğümleri paylaşıyor. liste_derin_kopyala ise her
düğüm için yeni bir ayırma yapıp değeri kopyalıyor; döndürdüğü liste bağımsız bir bellek
bloğu zincirine sahip.
// dugum.h — tek yonlu bagli liste ve temel islemler
#ifndef DUGUM_H
#define DUGUM_H
struct Dugum {
int deger;
struct Dugum *sonraki;
};
struct Dugum *liste_ekle(struct Dugum *bas, int deger);
int liste_toplam(const struct Dugum *bas);
void liste_serbest(struct Dugum *bas);
struct Dugum *liste_sig_kopyala(struct Dugum *bas);
struct Dugum *liste_derin_kopyala(const struct Dugum *bas);
#endif
// dugum.c — liste islemlerinin gerceklemesi
#include <stdlib.h>
#include "dugum.h"
struct Dugum *liste_ekle(struct Dugum *bas, int deger) {
struct Dugum *yeni = malloc(sizeof *yeni);
if (yeni == NULL) {
return bas;
}
yeni->deger = deger;
yeni->sonraki = bas;
return yeni;
}
int liste_toplam(const struct Dugum *bas) {
int toplam = 0;
for (const struct Dugum *d = bas; d != NULL; d = d->sonraki) {
toplam += d->deger;
}
return toplam;
}
void liste_serbest(struct Dugum *bas) {
while (bas != NULL) {
struct Dugum *sonraki = bas->sonraki;
free(bas);
bas = sonraki;
}
}
struct Dugum *liste_sig_kopyala(struct Dugum *bas) {
return bas;
}
struct Dugum *liste_derin_kopyala(const struct Dugum *bas) {
struct Dugum *yeni_bas = NULL;
struct Dugum *son = NULL;
for (const struct Dugum *d = bas; d != NULL; d = d->sonraki) {
struct Dugum *yeni = malloc(sizeof *yeni);
if (yeni == NULL) {
liste_serbest(yeni_bas);
return NULL;
}
yeni->deger = d->deger;
yeni->sonraki = NULL;
if (son == NULL) {
yeni_bas = yeni;
} else {
son->sonraki = yeni;
}
son = yeni;
}
return yeni_bas;
}
Takma Ad ile Değişen Düğüm
Liste 1, 2, 3 değerleriyle kuruluyor. ikinci işaretçisi ikinci düğümü işaret ediyor —
yeni bir düğüm değil, listenin kendi düğümlerinden biri. ikinci üzerinden yapılan
değişiklik listenin toplamına yansıyor, çünkü ikisi de aynı bellek bloğuna bakıyor.
// takma_ad.c — listenin kendi dugumune ikinci bir gostergeyle erisim
#include <stdio.h>
#include "dugum.h"
int main(void) {
struct Dugum *bas = NULL;
bas = liste_ekle(bas, 3);
bas = liste_ekle(bas, 2);
bas = liste_ekle(bas, 1);
printf("%s %d\n", "takma adla degismeden once toplam", liste_toplam(bas));
struct Dugum *ikinci = bas->sonraki;
ikinci->deger = 20;
printf("%s %d\n", "takma adla degistikten sonra toplam", liste_toplam(bas));
printf("%s %d\n", "ikinci ile bas->sonraki ayni dugum mu", (int)(ikinci == bas->sonraki));
liste_serbest(bas);
return 0;
}
takma adla degismeden once toplam 6 takma adla degistikten sonra toplam 24 ikinci ile bas->sonraki ayni dugum mu 1
VY1. ikinci değişkeni struct Dugum * tipinde ve bas da öyle — kaynağın hiçbir
yerinde ikinci‘nin listenin kendi bir düğümünü mü gösterdiği yoksa bağımsız bir
kopya mı olduğu yazmıyor. Bunu belirleyen, bildirim değil atamanın kendisi: ikinci = bas->sonraki bir kopyalama değil bir işaretçi kopyalama, yani ikinci ve
bas->sonraki aynı adresi taşıyor. ikinci->deger = 20 satırı bu yüzden bas üzerinden
okunan toplamı da değiştiriyor: 6’dan 24’e. Son satır bunu adres basmadan doğruluyor —
ikinci == bas->sonraki karşılaştırması, iki işaretçinin aynı düğümü gösterdiğini
1 olarak veriyor. Sınıf ortak: değişikliği ikinci bir okuyucu (burada bas
üzerinden yapılan liste_toplam çağrısı) görüyor, çünkü ikisi arasında paylaşılan tek bir
nesne var, kopyalanmış iki nesne değil.
Sığ Kopya, Derin Kopya
liste_sig_kopyala bir düğüm bile ayırmıyor; aldığı başı olduğu gibi döndürüyor. Bu
yüzden döndürdüğü işaretçiyle orijinal liste aynı düğümlere bakıyor ve biri üzerinden
yapılan değişiklik ötekinden görünüyor. liste_derin_kopyala her düğüm için yeni bir
ayırma yapıyor; döndürdüğü liste kopyalama anındaki değerleri taşıyor ama sonrasında
orijinalle hiçbir düğümü paylaşmıyor.
// kopya_karsilastirma.c — sig kopya paylasiyor, derin kopya kesiyor
#include <stdio.h>
#include "dugum.h"
int main(void) {
struct Dugum *bas = NULL;
bas = liste_ekle(bas, 3);
bas = liste_ekle(bas, 2);
bas = liste_ekle(bas, 1);
struct Dugum *sig = liste_sig_kopyala(bas);
bas->sonraki->deger = 200;
printf("%s %d\n", "sig kopyanin gordugu ikinci deger", sig->sonraki->deger);
struct Dugum *derin = liste_derin_kopyala(bas);
bas->sonraki->deger = 2000;
printf("%s %d\n", "derin kopyanin gordugu ikinci deger", derin->sonraki->deger);
liste_serbest(derin);
liste_serbest(bas);
return 0;
}
sig kopyanin gordugu ikinci deger 200 derin kopyanin gordugu ikinci deger 200
VY2. İki satır da aynı sayıyı (200) basıyor ama farklı nedenlerle ve bu fark dersin
ölçtüğü şey. İlk satırda sig, bas ile aynı düğümleri gösteriyor; bas->sonraki->deger = 200 ataması sig->sonraki->deger’i de değiştiriyor, çünkü ikisi tek bir nesneye bakıyor
— sınıf ortak. İkinci satırda derin, kopyalama anında (ikinci düğüm henüz 200
değerini taşırken) yeni düğümler ayırarak kuruldu; kopyalamadan sonra yapılan
bas->sonraki->deger = 2000 ataması derin’i etkilemiyor, çünkü derin‘in düğümleri
bas‘ınkilerden bağımsız bellek blokları. derin->sonraki->deger hâlâ kopyalama anındaki
200’ü taşıyor, 2000’i değil — sınıf kopya: değişiklik ikinci bir okuyucuya hiç
ulaşmıyor, çünkü aralarında paylaşılan bir nesne yok. liste_sig_kopyala’nın döndürdüğü
işaretçi bas ile tıpatıp aynı olduğu için bu derste yalnız derin ve bas ayrı ayrı
bırakılıyor; sig’i ayrıca bırakmak aynı bloğu iki kez bırakmak olurdu.
İkili Arama Ağacı: Ekleme, Dolaşma, Bırakma
Bağlı listenin tek-sonraki-işaretçili paylaşımı ikili ağaçta iki işaretçiye (sol,
sag) çıkıyor ama ilke aynı: bir düğüme birden fazla yoldan erişildiğinde, hangi yoldan
erişildiği kaynakta görünmüyor. agac_ekle özyinelemeli, agac_toplam sıralı (inorder)
dolaşıp değerleri topluyor, agac_serbest önce alt ağaçları sonra kökü bırakıyor —
listedeki “kim bırakacak” sözleşmesi burada da geçerli, bu ders yalnız o sözleşmenin
üzerine değil, ağacın görünürlüğüne odaklanıyor.
// agac.h — ikili arama agaci temel islemleri
#ifndef AGAC_H
#define AGAC_H
struct AgacDugumu {
int deger;
struct AgacDugumu *sol;
struct AgacDugumu *sag;
};
struct AgacDugumu *agac_ekle(struct AgacDugumu *kok, int deger);
int agac_toplam(const struct AgacDugumu *kok);
void agac_serbest(struct AgacDugumu *kok);
#endif
// agac.c — ekleme, siralanmis dolasma ve birakma
#include <stdlib.h>
#include "agac.h"
struct AgacDugumu *agac_ekle(struct AgacDugumu *kok, int deger) {
if (kok == NULL) {
struct AgacDugumu *yeni = malloc(sizeof *yeni);
if (yeni == NULL) {
return NULL;
}
yeni->deger = deger;
yeni->sol = NULL;
yeni->sag = NULL;
return yeni;
}
if (deger < kok->deger) {
kok->sol = agac_ekle(kok->sol, deger);
} else {
kok->sag = agac_ekle(kok->sag, deger);
}
return kok;
}
int agac_toplam(const struct AgacDugumu *kok) {
if (kok == NULL) {
return 0;
}
return agac_toplam(kok->sol) + kok->deger + agac_toplam(kok->sag);
}
void agac_serbest(struct AgacDugumu *kok) {
if (kok == NULL) {
return;
}
agac_serbest(kok->sol);
agac_serbest(kok->sag);
free(kok);
}
// agac_takma_ad.c — agacin kendi dugumune ikinci bir gostergeyle erisim
#include <stdio.h>
#include "agac.h"
int main(void) {
struct AgacDugumu *kok = NULL;
int degerler[] = {5, 3, 8, 1, 4};
for (size_t i = 0; i < sizeof degerler / sizeof degerler[0]; i++) {
kok = agac_ekle(kok, degerler[i]);
}
printf("%s %d\n", "agactaki degerlerin toplami once", agac_toplam(kok));
struct AgacDugumu *sol_alt = kok->sol;
sol_alt->deger = 30;
printf("%s %d\n", "sol_alt uzerinden degisince toplam", agac_toplam(kok));
agac_serbest(kok);
return 0;
}
agactaki degerlerin toplami once 21 sol_alt uzerinden degisince toplam 48
VY3. sol_alt, kok->sol ile aynı düğümü gösteriyor — ağaca yeni bir dal eklemedi,
var olan bir düğüme ikinci bir yoldan (kökün sol alanı yerine kendi değişkeni üzerinden)
eriştik. sol_alt->deger = 30 ataması bu düğümün değerini 3’ten 30’a çıkarıyor ve
agac_toplam(kok) bunu görüyor: 21’den 48’e. Bağlı listedeki ikinci örneğiyle aynı
ilke — sınıf yine ortak. Ağacı bırakırken (agac_serbest) uyduğumuz sözleşme ayrı bir
soru: her düğümü kim bırakacak, önceki kursun (C’de Bellek ve İşaretçiler) ekseni.
Bu ders o soruyu yeniden açmıyor; yalnız bir değişikliğin ikinci bir işaretçiden görünüp
görünmediğini ölçüyor.
Struct Atamasıyla Kısmi Sığ Kopya
liste_sig_kopyala ile liste_derin_kopyala iki uç örnekti: biri hiçbir şeyi
kopyalamıyordu, öteki her düğümü kopyalıyordu. Ama C’de bir yapıyı kopyalamanın üçüncü,
daha sinsi bir yolu var: struct atamasi (=). struct Dugum kopya = *bas; yazıldığında
derleyici bas’ın gösterdiği yapının her alanını yeni bir değişkene kopyalıyor — ama
“her alan”ın içinde bir işaretçi de varsa, o işaretçi de bir değer olarak kopyalanıyor,
gösterdiği yer değil.
// struct_atama.c — degisken tipi atamayla kismi sig kopya
#include <stdio.h>
#include "dugum.h"
int main(void) {
struct Dugum *bas = NULL;
bas = liste_ekle(bas, 3);
bas = liste_ekle(bas, 2);
bas = liste_ekle(bas, 1);
struct Dugum kopya = *bas;
printf("%s %d\n", "atamadan hemen sonra kopyanin deger alani", kopya.deger);
bas->deger = 100;
printf("%s %d\n", "bas->deger degisince kopya.deger", kopya.deger);
bas->sonraki->deger = 200;
printf("%s %d\n", "ikinci dugum uzerinden degisince kopya.sonraki->deger", kopya.sonraki->deger);
liste_serbest(bas);
return 0;
}
atamadan hemen sonra kopyanin deger alani 1 bas->deger degisince kopya.deger 1 ikinci dugum uzerinden degisince kopya.sonraki->deger 200
VY14. struct Dugum kopya = *bas; satırı iki farklı şey yapıyor ve ikisi de aynı
atamanın içinde: deger alanı bir int, atama onu değerle kopyalıyor — bu yüzden
bas->deger = 100 çalıştığında kopya.deger hâlâ ilk değerinde, 1’de kalıyor. Ama
sonraki alanı bir struct Dugum *, atama onu da değerle kopyalıyor — kopyalanan değer
bir adres, yani kopya.sonraki ile bas->sonraki atamadan sonra aynı adresi
taşıyor. bas->sonraki->deger = 200 çalıştığında bu değişiklik kopya.sonraki->deger
üzerinden de görünüyor: 200. Tek bir atama satırı içinde deger alanı için kopya
sınıfı, sonraki alanının gösterdiği düğüm için ortak sınıfı aynı anda üretiliyor —
“sığ kopya” ile “derin kopya” arasındaki fark bazen bir işlev seçimi değil, bir yapının
hangi alanlarının işaretçi taşıdığı meselesi. liste_sig_kopyala’nın döndürdüğü işaretçinin
tamamı paylaşılıyordu; burada yalnız sonraki’nin gösterdiği düğüm paylaşılıyor, deger
alanının kendisi paylaşılmıyor — aynı struct atamasının içinde iki ayrı sınıf bir arada.
Sınırlayıcı Ölçüm
Bir işaretçinin paylaşımı kaynakta görünmez. İki liste başı ya da iki ağaç dalı aynı
düğümü gösterdiğinde bunu yapının tipi söylemez — struct Dugum * bildirimi hem
listenin kendi bir düğümünü hem bağımsız bir kopyayı gösterebilir, aralarında kaynak
düzeyinde hiçbir ayrım yok. Bu derste her iki durumda da işaretçileri adres basmadan
karşılaştırdık: ikinci == bas->sonraki eşitliği, iki değişkenin aynı bellek bloğuna mı
baktığını, farklı bloklara mı baktığını söyledi. liste_sig_kopyala ile liste_derin_ kopyala’nın farkı da kaynakta bir imzayla değil, gövdelerinin ne yaptığıyla ortaya
çıkıyor: biri hiç ayırma yapmıyor, öteki her düğüm için yeni bir ayırma yapıyor. Bir
işlevin dönüş tipine bakarak paylaşıp paylaşmadığını söylemek mümkün değil; bu yalnız
gövdeyi okuyarak ya da ölçerek anlaşılıyor.
Özet
- Bağlı liste ve ikili arama ağacı aynı ilkeyle kuruldu: bir düğüme birden fazla işaretçi aynı anda erişebilir ve kaynak bunu söylemez.
- Bir düğüme takma adla erişip değiştirmek listede de ağaçta da ortak sınıfını
üretti: ikinci bir okuyucu (
liste_toplam,agac_toplam) değişikliği gördü. liste_sig_kopyaladüğüm ayırmadan başı döndürdüğü için ortak kaldı;liste_derin_kopyalaher düğüm için yeni bir ayırma yaptığı için kopya üretti.- Struct atamasıyla (
=) yapılan kısmi kopya, bir yapının skaler alanlarında kopya, işaretçi alanlarının işaret ettiği yerde ortak sınıfını aynı anda üretebiliyor. - İki işaretçinin aynı düğümü gösterip göstermediği adres basılmadan, yalnız eşitlikle
(
==) ölçülebiliyor. - Bir yapının kim tarafından bırakılacağı (K02’nin sorusu) ile bir değişikliğin kim tarafından görüleceği (bu kursun sorusu) ayrı eksenler; bu ders ikincisini ölçüyor.
Sonraki Adım
Bu derste bir düğüme erişmek için hep bir işaretçi tuttuk ve o işaretçi kalıcıydı. Bir sonraki ders, Halka Arabellek ve Kuyruklar, işaretçinin kendisi değil bir konumun (indeksin) paylaşıldığı bir yapıya bakıyor: bir üretici ile bir tüketici aynı diziyi kullanıyor ama biri yazarken diğeri okuyor, ve yazılan değer okunmadan üzerine yazılabiliyor. Sınıf orada ortak değil, aktarım: değer okuyucuya bir kez ulaşıyor, sonra kaybolabiliyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.