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.
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:
- Arkadan ekle (enqueue), önden çıkar (dequeue).
- Dizide eleman kaydırma. front ve rear indekslerini ilerlet, sona gelince
%ile başa sar. - front == rear boş da olabilir dolu da. Ayırt etmek için
counttut. - Bağlı kuyrukta son eleman çıkınca tail’i de null yap.
Kuyruk ADT
| İşlem | Ne yapar | Süre |
|---|---|---|
enqueue(x) | x’i arkaya ekler | O(1) |
dequeue() | öndeki elemanı siler ve döndürür | O(1) |
peek() (veya front()) | öndekini döndürür, silmez | O(1) |
isEmpty(), size() | durum | O(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 dizi | Bağlı kuyruk | |
|---|---|---|
| enqueue / dequeue | O(1) | O(1) |
| Kapasite | sabit (veya büyütme) | sınırsız |
| Bellek | ardışık, boş hücreler | düğü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ırmak | front’u ilerlet: O(1) |
rear = rear + 1 | rear = (rear + 1) % data.length |
front == rear ise boş | count’a bak; dolu da olabilir |
| Çıkarılan değer diziden silinir | Durur, 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ırakmak | if (head == null) tail = null; |
| Boş bağlı kuyruğa eklerken sadece tail’i ayarlamak | head = tail = n |
| Kuyruk ve yığın çıkış sırasını karıştırmak | Kuyruk sırayı korur, yığın ters çevirir |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
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] → 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ı.
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
Yığın LIFO: son giren 3 ilk çıkar. Kuyruk FIFO: ilk giren 1 ilk çıkar. Kuyruk sırayı korur, yığın ters çevirir.
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
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.
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
[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.
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
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).
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
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.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
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.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
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.
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
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ı.
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
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.
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
Üç ekleme başarılı, count 3 = kapasite. Dördüncü enqueue dolu kontrolüne takılır; 40 basılmadan catch çalışır.
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
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ı.