7 - Kuyruk (Queue) Veri Yapısı
2026
Bazı problemlerde önemli olan en son gelen değil, önce gelen elemanın önce işlenmesidir.
Kuyruk, elemanların bir uçtan eklendiği ve diğer uçtan çıkarıldığı doğrusal bir veri yapısıdır.
rearfrontGündelik benzetim: banka sırası, yazıcı kuyruğu, müşteri hizmetleri bekleme hattı.
Bu dersin sonunda şunları yapabiliyor olmanız beklenir:
front ve rear kavramlarını karıştırmadan kullanmak,enqueue ve dequeue işlemlerinin mantığını açıklamak,Geçen hafta bağlı listelerde düğümler arasında bağlantı kurmayı ve bağlantıyı bozmadan işlem yapmayı gördük. Bu hafta o bilgiyi yeni bir amaç için kullanıyoruz:
Bağlı listeyi kullanarak FIFO davranışı gösteren bir kuyruk oluşturmak.
frontrearKuyruk, İlk Giren İlk Çıkar kuralı ile çalışır.
Yukarıdaki görselde 1 önce geldiği için kuyruktan ilk çıkacak eleman da odur. FIFO mantığında önemli olan değerlerin büyüklüğü değil, geliş sırasıdır.
enqueue(40) olursa yeni eleman en sağa eklenirdequeue() olursa en soldaki eleman çıkarÖğrencilerin en sık karıştırdığı nokta şudur:
Bu derste ağırlıklı olarak bağlı liste ile gerçekleştirme üzerinde duracağız.
Not
front = -1, rear = -1 gibi başlangıç değerleri genellikle dizi tabanlı kuyruklar için kullanılır. Bağlı listede ise çoğunlukla head = NULL, tail = NULL yaklaşımı kullanılır.
front ve rearhead ve tailBu derste çoğu zaman şu eşleşmeyi kullanacağız:
front = headrear = tailBu eşleşmeyi net tutmak, iki farklı kavramı karıştırmamanızı sağlar.
Kuyruk, verilerin hangi sırayla işleneceğini kontrol etmek istediğimiz her yerde karşımıza çıkar.
enqueue: kuyruğun sonuna eleman eklerdequeue: kuyruğun başından eleman çıkarırpeek: baştaki elemana çıkarmadan bakarisEmpty: kuyruk boş mu kontrol ederfront: ilk elemanın bulunduğu konumu gösterirrear: son elemanın bulunduğu konumu gösterirDikkat
isFull kavramı her gerçekleştirmede aynı anlamı taşımaz. Sabit boyutlu dizi tabanlı kuyruklarda anlamlıdır; bağlı listede ise kuyruk genellikle bellek yettiği sürece büyüyebilir.
Bağlı liste ile kuyruk gerçekleştireceksek en az iki göstericiye ihtiyaç duyarız:
head: kuyruktaki ilk düğüm, yani fronttail: kuyruktaki son düğüm, yani rearBaşlangıç durumu:
head == NULLtail == NULLBu durum kuyruğun boş olduğunu gösterir.
head Hem tail Tutuyoruz?Eğer sadece head tutarsak, sona eleman eklemek için her seferinde listenin sonuna kadar gitmemiz gerekir.
head + tail birlikte tutulursa enqueue işlemi çoğu durumda \(O(1)\) olur.dequeue işlemi de baştan çıkarma yaptığı için \(O(1)\) olur.Bu yüzden kuyruk, bağlı liste ile gerçeklenirken çoğu zaman iki uç da tutulur.
Bağlı liste ile kuyrukta sona ekleme adımları şöyledir:
sonraki alanını NULL yap.head ve tail aynı düğümü göstersin.tail yeni düğümü göstersin, sonra tail güncellensin.Kritik fikir: ekleme her zaman sonda yapılır.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int veri;
struct Node *sonraki;
};
struct Node *head = NULL;
struct Node *tail = NULL;Ders İçi Sadeleştirme
Bu derste mantığı sade göstermek için head ve tail işaretçilerini global tuttuk. Daha sonraki aşamalarda bunları ayrı bir Queue yapısı içinde toplamak da mümkündür.
enqueue GerçeklemesiBaştan çıkarma sırasında şu sıra izlenir:
head düğümünü geçici bir işaretçide tut.head işaretçisini bir sonraki düğüme kaydır.tail değerini de NULL yap.free ile serbest bırak.En kritik kenar durum: kuyrukta tek eleman varken dequeue yapılmasıdır.
dequeue Gerçeklemesiint dequeue() {
struct Node *eskiHead;
int veri;
if (head == NULL) {
printf("Kuyruk bos.\n");
return -1;
}
eskiHead = head;
veri = eskiHead->veri;
head = head->sonraki;
if (head == NULL) {
tail = NULL;
}
free(eskiHead);
return veri;
}Küçük Teknik Not
Bu örnekte boş kuyruk durumunu basit tutmak için -1 döndürüyoruz. Gerçek uygulamalarda hata yönetimi, dönüş değeri ile karışmayacak şekilde farklı tasarlanabilir.
enqueue ve dequeue AkışıBaşlangıç:
front/rear
|
v
+----+
| 10 |
+----+
enqueue(20):
front rear
| |
v v
+----+ -> +----+
| 10 | | 20 |
+----+ +----+
dequeue():
önce: [10] -> [20]
sonra: [20]Bu akışta görmeniz gereken temel şey şudur: enqueue kuyruğu büyütür, dequeue ise head işaretçisini sağa kaydırır.
Başlangıçta kuyruk boş olsun.
enqueue(10) -> head = 10, tail = 10enqueue(20) -> 10 -> 20enqueue(30) -> 10 -> 20 -> 30dequeue() -> 10 çıkar, kuyruk 20 -> 30 olurdequeue() -> 20 çıkar, kuyruk 30 olurdequeue() -> 30 çıkar, artık head = NULL, tail = NULLBu örnek, tail değerinin neden son eleman çıkarıldığında da güncellenmesi gerektiğini gösterir.
void kuyrukYaz() {
struct Node *etkin = head;
while (etkin != NULL) {
printf("%d ", etkin->veri);
etkin = etkin->sonraki;
}
printf("\n");
}Bu fonksiyon kuyruktaki elemanları head’den başlayarak sırayla ekrana yazdırır. Yapıyı değiştirmez; sadece dolaşır.
enqueue: \(O(1)\)dequeue: \(O(1)\)peek: \(O(1)\)Buradaki hız avantajı, head ve tail işaretçilerinin birlikte tutulmasından gelir.
Eğer burada sadece head = NULL yapıp tail değerini eski düğümü göstermeye bırakırsanız, kuyruk boş olduğu halde programınız son düğüm varmış gibi davranabilir.
dequeue sonrasında kuyruk boşaldığında tail değerini güncellememekmalloc sonucunu kontrol etmemekfree etmeyi unutmakvoid foo() {
struct Node *etkin = head;
while (etkin != NULL) {
printf("%d ", etkin->veri);
etkin = etkin->sonraki;
}
printf("\n");
}Sorular:
head üzerinde ilerlemek yerine etkin adında geçici bir işaretçi kullanıyoruz?enqueue işlemi hangi uçta yapılır?
frontrearhead = NULLtail = NULLhead = NULL ve tail = NULLhead = tailhead ve tail birlikte tutulursa hangi işlem genellikle \(O(1)\) olur?
enqueue ve dequeueBir sonraki derste şunları daha ayrıntılı ele alacağız:
peek ve isEmpty fonksiyonlarını ekleme,main içinde test etme,front ve rear güncellemelerini inceleme,