6 - Bağlı Liste İşlemleri
2026
Geçen hafta bağlı listenin ne olduğunu, neden diziye alternatif olduğunu ve düğümlerin işaretçilerle bağlandığını gördük. Bu hafta odağımız şudur:
Bu işlemleri tek yönlü bağlı liste üzerinde göstereceğiz. Öğrendiğiniz mantık, çift yönlü ve dairesel listelere de temel oluşturur.
Bu dersin sonunda şunları yapabiliyor olmanız beklenir:
head’in değişip değişmeyeceğini yorumlamak,Bu haftayı anlamak için iki önceki dersin fikrini birleştiriyoruz:
malloc ile heap’te bellek ayırıyor, işimiz bitince free ile geri veriyoruz.next bağlantılarını takip ederek ulaşıyoruz.Bu yüzden bu haftanın ana sorusu şudur:
Bir düğümün bağlantısını kaybetmeden listeyi nasıl değiştiririz?
head, bağlı listenin ilk düğümünü gösterir.next alanı NULL olur.head == NULL ise liste boştur.Not
Bu dersteki kod parçaları kavramı göstermek için sadeleştirilmiştir. Kodları doğrudan derlemek istediğinizde uygun yerlerde stdio.h, stdlib.h ve main fonksiyonu eklemeyi unutmayın.
Bu derste örnek başlangıç durumunu çoğu zaman şöyle düşüneceğiz:
head -> 1 -> 2 -> 3 -> NULL
Bir bağlı liste işlemini ezberlemek yerine önce şu üç soruyu sorun:
head değerini değiştirebilir mi?Bu üç soru, haftanın neredeyse tüm kodlarını anlamanızı sağlar.
head’dir.NULL’a ulaşmaktır.Traversal, sonraki haftalarda göreceğiniz birçok işlem için temel kalıptır; çünkü arama, sona ekleme ve sondan silme gibi işlemler çoğu zaman önce dolaşmayı gerektirir.
void printList(struct node *head) {
struct node *current = head;
printf("Liste: ");
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}Basit gezme işleminde liste yapısını değiştirmeyiz; bu yüzden doğrudan head üzerinde ilerlemek yerine genellikle geçici bir işaretçi kullanırız.
Neden temp veya current kullanıyoruz?
head ilk düğümü göstermeye devam etmelidir. Eğer doğrudan head = head->next yaparsanız, listenin başlangıcını kaybedersiniz.
Tam dolaşım yapıldığında bütün düğümler tek tek ziyaret edildiği için maliyet genellikle \(O(n)\) olur.
Ekleme işlemlerinde temel düşünce aynıdır:
head’in bağlantısını güncelle.Burada en kritik nokta şudur:
Bağlantıyı yanlış sırada değiştirirseniz listenin bir kısmını kaybedebilirsiniz.
Başa ekleme, bağlı listede en sezgisel ve en verimli işlemlerden biridir.
newNode->next, eski head’i gösterecek şekilde ayarlanır.head, yeni düğümü gösterecek şekilde güncellenir.Bu işlemde listenin geri kalanını aramamız gerekmez.
void insertAtBeginning(struct node **headRef, int newData) {
struct node *newNode = malloc(sizeof *newNode);
if (newNode == NULL) {
printf("Bellek ayirma basarisiz.\n");
return;
}
newNode->data = newData;
newNode->next = *headRef;
*headRef = newNode;
}struct node **headRef Kullanıyoruz?Başa ekleme ve baştan silme gibi işlemler, head değişkeninin kendisini değiştirebilir. Bu nedenle fonksiyona yalnızca head’in değeri değil, head’in adresi gönderilir.
headRef: head değişkeninin adresi*headRef: gerçek head değeriFonksiyon içinde *headRef = newNode; dediğimizde, çağıran koddaki gerçek head güncellenmiş olur.
Karşılaştırma
Sadece mevcut bir düğümün sonrasını değiştiriyorsak çoğu zaman struct node ** gerekmez. Ama head değişebiliyorsa genellikle gerekir.
Yeni düğümü listenin en sonuna eklemek için önce son düğümü bulmamız gerekir.
next alanı NULL yapılır.head olur.next alanı yeni düğümü gösterecek şekilde ayarlanır.void insertAtEnd(struct node **headRef, int newData) {
struct node *newNode = malloc(sizeof *newNode);
if (newNode == NULL) {
printf("Bellek ayirma basarisiz.\n");
return;
}
newNode->data = newData;
newNode->next = NULL;
if (*headRef == NULL) {
*headRef = newNode;
return;
}
struct node *last = *headRef;
while (last->next != NULL) {
last = last->next;
}
last->next = newNode;
}tail tutulmuyorsa son düğümü aramayı gerektirir; bu yüzden çoğu zaman \(O(n)\)’dir.Bu karşılaştırma, geçen haftaki “neden bazen tail tutulur?” sorusunu da açıklar.
Bu işlem aslında “belirli bir düğümden sonra ekleme” işlemidir. Dolayısıyla kritik bilgi, ekleme yapılacak yerin önceki düğümünü bilmektir.
next alanı, önceki düğümün eski next değerine bağlanır.next alanı yeni düğüme çevrilir.Sıra önemlidir: önce yeni düğümün bağlantısı kurulur, sonra eski düğüm yeni düğüme bağlanır.
Önemli nüans: prevNode zaten biliniyorsa bu bağlantı güncellemesi genellikle \(O(1)\)’dir. Ama önce uygun düğümü aramak gerekiyorsa toplam maliyet çoğu zaman \(O(n)\) olur.
void insertAfter(struct node *prevNode, int newData) {
if (prevNode == NULL) {
printf("Onceki dugum NULL olamaz.\n");
return;
}
struct node *newNode = malloc(sizeof *newNode);
if (newNode == NULL) {
printf("Bellek ayirma basarisiz.\n");
return;
}
newNode->data = newData;
newNode->next = prevNode->next;
prevNode->next = newNode;
}insertAfterBaşlangıç durumu:
found = search(head, 2) ise found, değeri 2 olan düğümü gösterir.
insertAfter(found, 99) sonrasında:
Burada yapılan şey yeni düğümü “2 ile 3 arasına” eklemektir.Dikkat etmeniz gereken asıl nokta, 2 düğümünün eski next bilgisinin kaybedilmeden önce 99 düğümüne aktarılmasıdır.
Silme, eklemeden biraz daha risklidir; çünkü yanlış sırada işlem yaparsanız hem düğümü hem de bağlantıyı kaybedebilirsiniz.
Silmede tipik düşünce şudur:
free ile serbest bırak.Kritik kural
free ettikten sonra o düğüme tekrar erişmeye çalışmayın. O bellek artık programınıza ait değildir.
Baştaki düğümü silmek için head değişeceği için yine işaretçinin işaretçisine ihtiyaç duyarız.
head, bir sonraki düğümü gösterecek şekilde güncellenir.free edilir.Tek yönlü listede son düğümü silmek için son düğümün bir öncesine kadar gitmemiz gerekir.
head = NULL yap.next alanını NULL yap.free et.void deleteFromEnd(struct node **headRef) {
if (*headRef == NULL) {
return;
}
if ((*headRef)->next == NULL) {
free(*headRef);
*headRef = NULL;
return;
}
struct node *secondLast = *headRef;
while (secondLast->next->next != NULL) {
secondLast = secondLast->next;
}
struct node *temp = secondLast->next;
secondLast->next = NULL;
free(temp);
}Tek yönlü listede aradan silme işlemi de çoğu zaman silinecek düğümün önceki düğümünü bilmeyi gerektirir.
Bu yüzden aşağıdaki fonksiyonun adı özellikle deleteAfter seçildi; çünkü fonksiyon doğrudan verilen düğümden sonraki düğümü siler.
Önemli nüans: önceki düğüm zaten elinizdeyse silme bağlantısı genellikle \(O(1)\)’dir. Ama o düğümü bulmak için listeyi dolaşmanız gerekiyorsa toplam maliyet çoğu zaman \(O(n)\) olur.
void deleteAfter(struct node *prevNode) {
if (prevNode == NULL || prevNode->next == NULL) {
printf("Silinecek dugum bulunamadi.\n");
return;
}
struct node *temp = prevNode->next;
prevNode->next = temp->next;
free(temp);
}Neden adı değişti?
Önceki haliyle fonksiyon adı deleteNode idi; ancak aslında herhangi bir düğümü değil, yalnızca verilen düğümün sonrasındaki düğümü siliyordu.
deleteAfterBaşlangıç durumu:
found = search(head, 2) ise found, değeri 2 olan düğümü gösterir.
deleteAfter(found) sonrasında:
Bu işlemde 2 düğümünün next alanı, doğrudan 3 düğümünü gösterecek şekilde güncellenir; sonra aradaki düğüm free edilir.
Arama, geçen haftadaki temel fikirle tamamen aynıdır: düğümleri sırayla dolaşırız.
head’den başlanır.key ile karşılaştırılır.NULL döndürülür.struct node *search(struct node *head, int key) {
struct node *current = head;
while (current != NULL) {
if (current->data == key) {
return current;
}
current = current->next;
}
return NULL;
}Arama, istenen düğüm sonda olabilir diye genellikle en kötü durumda \(O(n)\) zaman alır.
Bazen insertAfter veya deleteAfter gibi işlemlerden önce uygun düğümü bulmak için arama yaparız.
insertAtBeginning(&head, 4);
insertAtEnd(&head, 5);
struct node *found = search(head, 2);
if (found != NULL) {
insertAfter(found, 6);
}
deleteFromBeginning(&head);
deleteFromEnd(&head);Bu örnekler, iki tür parametre farkını netleştirir:
&head: fonksiyon head’i değiştirebilir.head veya found: fonksiyon sadece mevcut düğümler üzerinden işlem yapar.| İşlem | Maliyet | Neden? |
|---|---|---|
| Gezme | \(O(n)\) | Tüm düğümler sırayla gezilebilir |
| Başa ekleme | \(O(1)\) | Doğrudan head üzerinden yapılır |
| Sona ekleme | \(O(n)\) | tail yoksa son düğüm aranır |
| Baştan silme | \(O(1)\) | Sadece head güncellenir |
| Sondan silme | \(O(n)\) | Sondan bir önceki düğüm aranır |
insertAfter / deleteAfter |
\(O(1)\) veya \(O(n)\) | Önceki düğüm biliniyorsa \(O(1)\), aranıyorsa toplam maliyet artar |
| Arama | \(O(n)\) | İndeksle doğrudan erişim yok |
Bu tablo, geçen haftaki “bağlı listede doğrudan erişim yok” bilgisinin doğal sonucudur.
malloc sonucunu kontrol etmeden alanı kullanmakhead->next erişimi yapmakhead değişmesi gereken yerde sadece head kopyasıyla işlem yapmakfree edilen düğümü tekrar kullanmakhead’i değiştiriyorsa çoğu zaman struct node ** gerekir.Bir sonraki adımda, bu işlemleri tek tek ezberlemek yerine küçük izleme örnekleri üzerinde elle yürütmek en etkili öğrenme yöntemi olacaktır.