Veri Yapıları ve Programlama

8 - Kuyruk İşlemleri ve Örnekleri

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2026

Bu Derste Odak Noktamız

Geçen hafta kuyruğun temel mantığını ve enqueue işlemini gördük.

Bu hafta özellikle şunları netleştireceğiz:

  • kuyruktan eleman çıkarma: dequeue
  • baştaki elemana bakma: peek
  • boşluk kontrolü: isEmpty
  • son eleman çıkarıldığında neden hem head hem tail güncellenmelidir?
  • hata durumu için özel değer döndürmek neden her zaman güvenli değildir?

Bu derste yalnızca işlemleri değil, kuyruğun durumunu doğru yönetmeyi ele alacağız.

Bağlı Liste ile Kuyruk Yapısı (Hatırlatma)

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

struct Node {
    int veri;
    struct Node *sonraki;
};

struct Node *head = NULL; // Kuyruğun başı: çıkarma buradan yapılır
struct Node *tail = NULL; // Kuyruğun sonu: ekleme buraya yapılır

void enqueue(int sayi) {
    struct Node *yeniDugum = malloc(sizeof(struct Node));
    if (yeniDugum == NULL) {
        printf("Hata: Bellek ayrılamadi!\n");
        return;
    }
    yeniDugum->veri = sayi;
    yeniDugum->sonraki = NULL;

    if (tail == NULL) {
        head = yeniDugum;
        tail = yeniDugum;
    } else {
        tail->sonraki = yeniDugum;
        tail = yeniDugum;
    }
    printf("%d kuyruğa eklendi.\n", sayi);
}
  • head ilk düğümü gösterir.
  • tail son düğümü gösterir.
  • ekleme sonda, çıkarma başta yapılır.
  • bu örnekte işlemleri görünür kılmak için printf kullanıyoruz.

dequeue İşlemi Nedir?

dequeue, kuyruğun başındaki (head) elemanı çıkarır ve bu elemanın verisini döndürür.

Örnek:

Başlangıç:

head                  tail
 |                     |
 v                     v
[10] -> [20] -> [30] -> NULL

dequeue() sonrası:

head           tail
 |              |
 v              v
[20] -> [30] -> NULL

Bu işlemde kuyruktan çıkan değer 10 olur.

Kuyruk boşsa önce bunu kontrol etmek gerekir.

dequeue Fonksiyonunun Mantığı

  1. Kuyruk boş mu kontrol et.
  2. Eski head düğümünü geçici bir işaretçide tut.
  3. Çıkarılacak veriyi sakla.
  4. head işaretçisini bir sonraki düğüme kaydır.
  5. Eğer kuyruk boşaldıysa tail = NULL yap.
  6. Eski düğümü free ile serbest bırak.
  7. Çıkarılan veriyi döndür.

Buradaki en kritik nokta, son eleman çıkarıldığında yapının tamamen boşaldığını doğru yansıtmaktır.

dequeue Fonksiyonu

int dequeue() {
    if (head == NULL) {
        printf("Hata: Kuyruk boş!\n");
        return INT_MIN;
    }

    struct Node *temp = head;
    int cikarilanVeri = temp->veri;

    head = head->sonraki;

    if (head == NULL) {
        tail = NULL;
    }

    free(temp);

    printf("%d kuyruktan çıkarıldı.\n", cikarilanVeri);
    return cikarilanVeri;
}

Tek Elemanlı Durum Neden Özel?

Tek elemanlı kuyrukta head ve tail aynı düğümü gösterir:

head, tail
    |
    v
  [42] -> NULL

Bu eleman dequeue() ile çıkarıldığında yapı tamamen boşalır.

Bu yüzden yalnızca head = NULL yapmak yetmez.

tail = NULL da yapılmalıdır.

Aksi halde tail, artık geçerli olmayan bir düğümü gösteriyor gibi kalır.

peek İşlemi: Çıkarmadan Bakmak

peek, kuyruğun başındaki elemanı çıkarmadan döndürür.

int peek() {
    if (head == NULL) {
        printf("Hata: Kuyruk boş!\n");
        return INT_MIN;
    }

    printf("Kuyruğun başındaki eleman: %d\n", head->veri);
    return head->veri;
}

Burada yapı değişmez.

Yani peek() ile dequeue() arasındaki temel fark şudur:

  • peek() sadece bakar,
  • dequeue() elemanı gerçekten çıkarır.

isEmpty İşlemi: Kuyruk Boş mu?

int isEmpty() {
    return head == NULL;
}
  • boşsa 1
  • boş değilse 0

Boşluk kontrolü için head == NULL yazmak yeterlidir.

Yapı doğru yönetiliyorsa kuyruk boş olduğunda tail de zaten NULL olur.

Ders Kodu ile Gerçek Program Arasındaki Küçük Fark

Bu örneklerde enqueue, dequeue ve peek içinde printf kullandık.

Bunu derste özellikle tercih ediyoruz; çünkü işlemin etkisini ekranda hemen görebiliyoruz.

Ama daha gerçekçi programlarda veri yapısı fonksiyonları çoğu zaman:

  • yalnızca işlemi yapar,
  • sonucu döndürür,
  • ekrana yazdırma işini üstlenmez.

Yazdırma işi genellikle main gibi istemci kodda yapılır.

INT_MIN ile Hata Bildirme

dequeue() ve peek() normalde bir int döndürür.

Ama kuyruk boşsa ortada döndürülecek gerçek bir eleman yoktur.

Bu örnekte hata durumunu göstermek için INT_MIN kullanıyoruz.

Bu yaklaşım basittir:

  • normal durumda gerçek veri döner,
  • hata durumunda özel bir değer döner.

Fakat önemli bir sınırlaması vardır:

Eğer kuyrukta gerçekten INT_MIN değeri tutuluyorsa, program bunun hata mı yoksa gerçek veri mi olduğunu ayırt edemez.

Daha Güvenli Tasarım Fikri

Bu karışıklığı önlemek için bazı programlarda:

  • işlem başarılı mı, ayrı bir dönüş değeriyle bildirilir
  • gerçek veri ise ayrı bir değişkene yazılır

Böylece hata bilgisi ile gerçek veri birbirine karışmaz.

dequeueSafe Örneği

int dequeueSafe(int *sonuc) {
    if (head == NULL) {
        return 0;
    }

    struct Node *temp = head;
    *sonuc = temp->veri;

    head = head->sonraki;

    if (head == NULL) {
        tail = NULL;
    }

    free(temp);
    return 1;
}

Burada:

  • return 1 -> işlem başarılı
  • return 0 -> kuyruk boş
  • *sonuc -> gerçekten çıkarılan veri

Bu örnekte sonuc için geçerli bir adres verildiğini varsayıyoruz.

Örnek Senaryo

Aşağıdaki işlem sırasını düşünelim:

  1. enqueue(10)
  2. enqueue(20)
  3. enqueue(30)
  4. peek()
  5. dequeue()
  6. peek()
  7. dequeue()
  8. enqueue(40)

Burada yalnızca dönen değerlere değil, her adımdan sonra kuyruğun yapısının nasıl değiştiğine dikkat etmek gerekir.

Örnek Kullanım (main fonksiyonu)

int main() {
    printf("Başlangıçta kuyruk boş mu? %s\n", isEmpty() ? "Evet" : "Hayır");

    enqueue(10);
    enqueue(20);
    enqueue(30);

    printf("Kuyruk boş mu? %s\n", isEmpty() ? "Evet" : "Hayır");

    peek();
    dequeue();
    peek();

    dequeue();
    enqueue(40);

    peek();
    dequeue();
    dequeue();

    printf("Son durumda kuyruk boş mu? %s\n", isEmpty() ? "Evet" : "Hayır");
    dequeue();

    return 0;
}

Bu Akışta Neye Bakıyoruz?

Bu örnekte asıl amaç, programın sadece çalışması değildir.

Şunları gözlemlemek istiyoruz:

  • peek() yapıyı değiştirmez
  • dequeue() her zaman baştan eleman çıkarır
  • son eleman çıktığında kuyruk tamamen boşalır
  • boş kuyrukta işlem yapılırsa hata durumu oluşur

Yani burada odak noktamız çıktı ezberlemek değil, yapının davranışını takip etmektir.

Yapının Adım Adım Değişimi

İşlem Kuyruğun durumu Not
başlangıç boş head = NULL, tail = NULL
enqueue(10) 10 ilk eleman
enqueue(20) 10 -> 20 sona ekleme
enqueue(30) 10 -> 20 -> 30 sona ekleme
peek() 10 -> 20 -> 30 yapı değişmez
dequeue() 20 -> 30 baştaki eleman çıkar
peek() 20 -> 30 yapı değişmez
dequeue() 30 tekrar baştan çıkarma
enqueue(40) 30 -> 40 sona ekleme

Bu tablo şunu açık gösterir:

  • peek() yapıyı değiştirmez,
  • dequeue() baştaki elemanı çıkarır,
  • kuyruk her işlemden sonra yeni bir duruma geçer.

Sık Yapılan Hatalar

  • dequeue sonrası eski düğümü free etmemek
  • tek elemanlı durumda tail = NULL güncellemesini unutmak
  • peek ile dequeue işlemini aynı şey sanmak
  • INT_MIN yaklaşımını her durumda güvenli zannetmek
  • dizi tabanlı kuyrukla bağlı liste tabanlı kuyruğu karıştırmak

Örnek Kod

https://gist.github.com/ecylmz/dd3b7086fc80582ccce7b09034275574

Bu bağlantıda, derste gördüğümüz enqueue, dequeue, peek ve isEmpty fonksiyonlarının tam hali birlikte yer alır.

Quiz

1. peek() işlemi hangisini yapar?

  1. Kuyruğun sonundaki elemanı siler
  2. Kuyruğun başındaki elemanı siler
  3. Kuyruğun başındaki elemanı silmeden gösterir
  4. Kuyruğu tamamen boşaltır

2. Kuyrukta yalnızca 42 varken dequeue() çağrılıyor. İşlem tamamlandıktan sonra tail hangi değeri almalıdır?

  1. 42
  2. Eski düğümün adresi
  3. NULL
  4. head + 1

3. Aşağıdaki işlem sırası uygulandıktan sonra peek() hangi değeri döndürür?

enqueue(10)
enqueue(20)
dequeue()
enqueue(30)
peek()
  1. 10
  2. 20
  3. 30
  4. Kuyruk boştur

Alıştırma / Tartışma

  1. dequeue fonksiyonu boş kuyruk durumunda INT_MIN döndürmek yerine void olsa ve sadece hata mesajı bassa nasıl olurdu? Avantajları ve dezavantajları nelerdir?
  2. dequeueSafe fonksiyonunu kullanan kısa bir main fonksiyonu yazın. İşlem başarılıysa çıkarılan değeri yazdırın, başarısızsa "Kuyruk boş" mesajı verin.

Teşekkürler