SEN2211 · 7. hafta · Queues

Kuyruk (queue): dairesel dizi ve bağlı liste ile

FIFO mantığı, enqueue/dequeue/peek, dizinin başa doğru kaymasını önleyen dairesel tampon ve head/tail'li bağlı kuyruk. Sınavda işlem dizisinden sonra dizinin ham hali ve front/rear değerleri sorulur.

queueFIFOenqueuedequeuefrontrearcircular bufferwrap-aroundmodulolinked queuequeue overflowqueue underflow

Kuyruk (queue) markette kasa sırasıdır: sona girersin, öndeki çıkar. İlk gelen ilk hizmeti alır: FIFO (First In, First Out). Yazıcı kuyruğu, işletim sisteminin iş sırası, ağdaki paketler, oyunlardaki eşleşme sırası hep kuyruktur. Yığından tek farkı hangi uçtan çıkardığın, ama gerçeklemesi daha zahmetlidir: iki ucu da takip etmek gerekir. Sınavda dairesel dizinin ham hali (hangi hücrede ne var, front ve rear kaç) neredeyse her yıl sorulur.

Altın kurallar:

  1. Arkadan ekle (enqueue), önden çıkar (dequeue).
  2. Dizide eleman kaydırma. front ve rear indekslerini ilerlet, sona gelince % ile başa sar.
  3. front == rear boş da olabilir dolu da. Ayırt etmek için count tut.
  4. Bağlı kuyrukta son eleman çıkınca tail’i de null yap.

Kuyruk ADT

İşlemNe yaparSüre
enqueue(x)x’i arkaya eklerO(1)
dequeue()öndeki elemanı siler ve döndürürO(1)
peek() (veya front())öndekini döndürür, silmezO(1)
isEmpty(), size()durumO(1)

Yığınla karşılaştırma: 1, 2, 3 sırasıyla eklenip hepsi çıkarılırsa

stack: 3 2 1 | queue: 1 2 3

Neden basit dizi yetmez?

İlk akla gelen: önü hep indeks 0’da tut, çıkarınca herkesi bir sola kaydır. Doğru çalışır ama her dequeue n − 1 taşıma yapar. n elemanı sırayla çekmenin toplam maliyeti:

n=1000 shifts=499500
n=2000 shifts=1999000
n=4000 shifts=7998000

n iki katına çıkınca maliyet dört katına çıkıyor: O(n²) toplam, işlem başına O(n). Kaydırmak yerine front indeksini ilerletirsen bu sefer dizinin başı boşa gider ve rear sona dayanınca yer kalmamış gibi görünür. Çözüm: diziyi bir halka gibi düşün.

Dairesel dizi (circular buffer)

İndeks sona gelince 0’a döner: i = (i + 1) % capacity. Kapasite 4 için indeks sırası 0, 1, 2, 3, 0, 1, …

class ArrayQueue {
    private int[] data;
    private int front = 0;   // ilk elemanın indeksi
    private int rear = 0;    // bir sonraki boş yer
    private int count = 0;

    ArrayQueue(int capacity) { data = new int[capacity]; }

    boolean isEmpty() { return count == 0; }
    boolean isFull()  { return count == data.length; }

    void enqueue(int x) {
        if (isFull()) throw new RuntimeException("Queue overflow");
        data[rear] = x;
        rear = (rear + 1) % data.length;
        count++;
    }

    int dequeue() {
        if (isEmpty()) throw new RuntimeException("Queue underflow");
        int x = data[front];
        front = (front + 1) % data.length;
        count--;
        return x;
    }

    int peek() {
        if (isEmpty()) throw new RuntimeException("Queue underflow");
        return data[front];
    }
}

TUZAK · rear’ın anlamı. Bazı kitaplarda rear son elemanın indeksidir, bazılarında bir sonraki boş yerin. Bu sayfada bir sonraki boş yer. Sınavda sorudaki tanımı oku; formüller ona göre bir kayar.

Çözümlü iz (kapasite 4)

ArrayQueue q = new ArrayQueue(4);
q.enqueue(10); q.enqueue(20); q.enqueue(30);
System.out.println(q.dequeue());
System.out.println(q.raw());
q.enqueue(40); q.enqueue(50);
System.out.println(q.raw());
System.out.println(q.dequeue() + " " + q.dequeue());
System.out.println(q.raw());
10
[10, 20, 30, 0] front=1 rear=3 count=2
[50, 20, 30, 40] front=1 rear=1 count=4
20 30
[50, 20, 30, 40] front=3 rear=1 count=2
enqueue 10,20,30   [10,20,30, _]  front=0 rear=3 count=3
dequeue → 10       [10,20,30, _]  front=1 rear=3 count=2   (10 dizide kaldı)
enqueue 40         [10,20,30,40]  front=1 rear=0 count=3   (rear 3 → (3+1)%4 = 0)
enqueue 50         [50,20,30,40]  front=1 rear=1 count=4   (10'un üstüne yazıldı, dolu)
dequeue → 20       front=2
dequeue → 30       front=3, count=2; kuyruk mantıken: 40, 50

Son durumda kuyruk 40 ve 50’den oluşur. Önden okumak için front’tan başla ve count kadar (i + 1) % cap ile ilerle: indeks 3 (40), indeks 0 (50).

İPUCU · Ham dizi soruları. Sınavda “dizinin içeriği ne?” diye sorulduğunda çıkarılan elemanlar da dizide yazılı kalır. Sadece yeni bir enqueue onların üstüne yazar. Her adımda dört şeyi yaz: dizi, front, rear, count.

Bağlı liste ile kuyruk (linked queue)

Tek yönlü listede başa ekleme/baştan silme O(1), sona ekleme tail varsa O(1), sondan silme ise O(n). Bu yüzden: ekleme tail’de, çıkarma head’de.

class LinkedQueue {
    private static class Node { int data; Node next; Node(int d) { data = d; } }
    private Node head, tail;   // head = ön, tail = arka

    boolean isEmpty() { return head == null; }

    void enqueue(int x) {
        Node n = new Node(x);
        if (tail == null) head = tail = n;   // boş kuyruk
        else { tail.next = n; tail = n; }
    }

    int dequeue() {
        if (head == null) throw new RuntimeException("Queue underflow");
        int x = head.data;
        head = head.next;
        if (head == null) tail = null;       // kuyruk boşaldı
        return x;
    }
}

if (head == null) tail = null; satırı unutulursa: kuyruk boşaldıktan sonra tail eski düğümü göstermeye devam eder, sonraki enqueue o ölü düğümün arkasına bağlanır ve head null kalır. Kuyruk sonsuza kadar boş görünür.

Dairesel diziBağlı kuyruk
enqueue / dequeueO(1)O(1)
Kapasitesabit (veya büyütme)sınırsız
Bellekardışık, boş hücrelerdüğüm başına next
Hata riski% unutmak, boş/dolu karışıklığıtail’i güncellemeyi unutmak

Kuyruk kalıpları

Döndürme (rotate). q.enqueue(q.dequeue()) öndeki elemanı arkaya alır. k kez yaparsan kuyruk k adım döner: 10 20 30 40 50 iki kez → 30 40 50 10 20.

Ters çevirme. Kuyruğu bir yığına boşalt, sonra yığından geri kuyruğa doldur: 1 2 3 4 → 4 3 2 1.

Sırayla işleme. Öndeki iki elemanı çekip sonucu arkaya eklemek bir “turnuva” ya da birleştirme işlemi kurar. Eleman sayısı her turda bir azalır; tek eleman kalınca dur.

Sık yapılan hatalar

YanlışDoğrusu
dequeue’da elemanları kaydırmakfront’u ilerlet: O(1)
rear = rear + 1rear = (rear + 1) % data.length
front == rear ise boşcount’a bak; dolu da olabilir
Çıkarılan değer diziden silinirDurur, sonra üstüne yazılır
Bağlı kuyrukta tail’den çıkarmakÇıkarma head’den, ekleme tail’den
Son eleman çıkınca tail’i bırakmakif (head == null) tail = null;
Boş bağlı kuyruğa eklerken sadece tail’i ayarlamakhead = tail = n
Kuyruk ve yığın çıkış sırasını karıştırmakKuyruk sırayı korur, yığın ters çevirir

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

What is printed? (q is an empty queue.)

q.enqueue(5); q.enqueue(3);
System.out.print(q.dequeue() + " ");
q.enqueue(8); q.enqueue(1);
System.out.print(q.dequeue() + " ");
System.out.print(q.dequeue() + " ");
q.enqueue(6);
System.out.println(q.peek());
Cevabı göster
5 3 8 1

[5,3] → dequeue 5 → [3] → [3,8,1] → dequeue 3 → dequeue 8 → [1] → [1,6], peek önü verir: 1. Aynı işlemler yığınla yapılsaydı (5. haftadaki soru) 3 1 8 6 basılırdı.

Soru 2kolay

The values 1, 2, 3 are inserted into a stack and into a queue, then everything is removed. What is the removal order for each?

Cevabı göster
stack: 3 2 1, queue: 1 2 3

Yığın LIFO: son giren 3 ilk çıkar. Kuyruk FIFO: ilk giren 1 ilk çıkar. Kuyruk sırayı korur, yığın ters çevirir.

Soru 3orta

A circular array queue has capacity 5, front = rear = count = 0. enqueue writes at rear then rear = (rear + 1) % 5; dequeue reads at front then front = (front + 1) % 5. After the calls below, give the array, front, rear and count.

enqueue(1); enqueue(2); enqueue(3); enqueue(4);
dequeue(); dequeue(); dequeue();
enqueue(5); enqueue(6); enqueue(7);
Cevabı göster
[6, 7, 3, 4, 5], front = 3, rear = 2, count = 4

Dört enqueue: [1,2,3,4,_], rear 4. Üç dequeue: front 3, count 1 (sadece 4 kaldı). enqueue(5) indeks 4'e, rear (4+1)%5 = 0. enqueue(6) indeks 0'a (1'in üstüne), rear 1. enqueue(7) indeks 1'e, rear 2. Eski 3 dizide duruyor ama kuyrukta değil. Çıkış sırası: 4 5 6 7.

Soru 4zor

Same circular queue design with capacity 4. What are the array, front, rear and count after these calls? Is the queue full?

for (int i = 1; i <= 4; i++) enqueue(i);
dequeue(); dequeue();
enqueue(5); enqueue(6);
Cevabı göster
[5, 6, 3, 4], front = 2, rear = 2, count = 4; yes, full

[1,2,3,4], rear (3+1)%4 = 0. İki dequeue: front 2. 5 indeks 0'a, 6 indeks 1'e, rear 2. front == rear ama count = 4 olduğu için dolu. front == rear tek başına boş mu dolu mu söylemez; count bu yüzden tutuluyor.

Soru 5kolay

A naive array queue keeps the front element at index 0 and shifts all elements left on every dequeue. What is the cost of one dequeue, and why is the circular version better?

Cevabı göster
O(n) per dequeue; circular dequeue is O(1)

Kaydırma n − 1 eleman taşır. n elemanı tek tek çekmek toplam n(n−1)/2 taşıma: n = 1000 için 499 500, n = 2000 için 1 999 000 (n² büyümesi). Dairesel dizide sadece front indeksi ilerler, hiçbir eleman kaymaz: O(1).

Soru 6orta

Write void enqueue(int x) and int dequeue() for a circular array queue with fields int[] data; int front, rear, count; (rear = next free slot).

Cevabı göster
enqueue: check full, data[rear] = x, rear = (rear+1) % length, count++. dequeue: check empty, read data[front], front = (front+1) % length, count--.

void enqueue(int x) {
    if (count == data.length)
        throw new RuntimeException("Queue overflow");
    data[rear] = x;
    rear = (rear + 1) % data.length;   // sona gelince 0'a sar
    count++;
}

int dequeue() {
    if (count == 0)
        throw new RuntimeException("Queue underflow");
    int x = data[front];
    front = (front + 1) % data.length;
    count--;
    return x;
}
% data.length olmadan rear dizinin sonunu geçince ArrayIndexOutOfBoundsException alırsın, oysa başta boş yer vardır.

Soru 7orta

Write int dequeue() for a linked queue with Node head (front) and Node tail (rear). Make sure the queue still works after it becomes empty.

Cevabı göster
Take head.data, head = head.next; if head became null set tail = null.

int dequeue() {
    if (head == null)
        throw new RuntimeException("Queue underflow");
    int x = head.data;
    head = head.next;
    if (head == null)   // son eleman çıktı
        tail = null;
    return x;
}
Ekleme sonda (tail), çıkarma başta (head) yapılır. İkisi de O(1). Tersini yapsaydın (sondan çıkarma) tek yönlü listede O(n) olurdu.

Soru 8zor

In this buggy linked queue, dequeue forgets to set tail = null when the queue becomes empty. What does the queue contain after the calls below?

void enqueue(int x) {
    Node n = new Node(x);
    if (tail == null) head = tail = n;
    else { tail.next = n; tail = n; }
}
int dequeue() { int x = head.data; head = head.next; return x; }

enqueue(1); dequeue(); enqueue(2); enqueue(3);
Cevabı göster
[] (appears empty; 2 and 3 are lost)

dequeue sonrası head null ama tail hâlâ eski 1 düğümünü gösteriyor. enqueue(2) tail null olmadığı için 2'yi eski düğümün arkasına bağlar, head null kalır. 3 de aynı zincire eklenir. head'den bakınca kuyruk boş: 2 ve 3 kayboldu.

Soru 9orta

The queue contains (front first) 10 20 30 40 50. What does it contain after this loop?

for (int k = 0; k < 2; k++)
    q.enqueue(q.dequeue());
Cevabı göster
30 40 50 10 20

Her turda öndeki eleman arkaya taşınır. 10 arkaya: 20 30 40 50 10. 20 arkaya: 30 40 50 10 20. Kuyruğu k kez döndürme (rotate) kalıbı.

Soru 10orta

The queue contains 1 2 3 4 (front first). What does it contain after this code?

Deque<Integer> st = new ArrayDeque<>();
while (!q.isEmpty()) st.push(q.dequeue());
while (!st.isEmpty()) q.enqueue(st.pop());
Cevabı göster
4 3 2 1

Kuyruktan 1 2 3 4 sırasıyla çıkar, yığına itilir; tepe 4. Yığından 4 3 2 1 sırasıyla çıkar ve kuyruğa girer. Yığın, kuyruğu ters çevirmenin standart yolu.

Soru 11kolay

A circular array queue of capacity 3 runs this loop. What is printed?

try {
    for (int i = 1; i <= 4; i++) {
        q.enqueue(i * 10);
        System.out.print(i * 10 + " ");
    }
} catch (RuntimeException e) {
    System.out.println("-> " + e.getMessage());
}
Cevabı göster
10 20 30 -> Queue overflow

Üç ekleme başarılı, count 3 = kapasite. Dördüncü enqueue dolu kontrolüne takılır; 40 basılmadan catch çalışır.

Soru 12zor

The queue contains 4 9 2 7 (front first). What is printed?

int total = 0;
while (q.size() > 1) {
    int x = q.dequeue();
    int y = q.dequeue();
    q.enqueue(x + y);
    total += x + y;
}
System.out.println(q.peek() + " " + total);
Cevabı göster
22 44

4 + 9 = 13 → [2,7,13], total 13. 2 + 7 = 9 → [13,9], total 22. 13 + 9 = 22 → [22], total 44. Çıktı: 22 44.

Doğrulama: ArrayQueue (front, rear, count ile dairesel dizi), LinkedQueue ve kaydırmalı NaiveQueue verify/SEN2211/W07.java içinde JDK ile çalıştırıldı (java W07.java). Dizilerin ham hali ve front/rear/count değerleri programın raw() çıktısından birebir alındı; kaydırmalı kuyruğun maliyeti n = 1000, 2000, 4000 için sayılıp n²/2 ile karşılaştırıldı.

Deftere not kartı

QUEUE = FIFO (First In, First Out): arkadan girer, önden çıkar enqueue(x) arkaya ekle | dequeue() önden al ve sil | peek() öne bak | isEmpty() hepsi O(1) Kaydırmalı dizi: dequeue O(n) (toplam n²/2) → KULLANMA DAİRESEL DİZİ: front = ilk eleman, rear = sonraki boş yer, count enqueue: data[rear] = x; rear = (rear + 1) % cap; count++ dequeue: x = data[front]; front = (front + 1) % cap; count-- boş: count == 0 dolu: count == cap front == rear hem boşta hem dolu olabilir → count tut (veya 1 hücre boş bırak, 8. hafta) dequeue değeri diziden silmez; yeni enqueue üstüne yazar BAĞLI KUYRUK: head = ön (çıkar), tail = arka (ekle) enqueue: tail == null ise head = tail = n; değilse tail.next = n; tail = n dequeue: head = head.next; head null olduysa tail = null (UNUTMA) Döndür: q.enqueue(q.dequeue()) Ters çevir: kuyruk → yığın → kuyruk