Queue (Kuyruk) Veri Yapısı
📋 Genel Tanım ve Kavram
Queue (Kuyruk), bilgisayar bilimlerinde kullanılan temel veri yapılarından biridir ve FIFO (First-In-First-Out) prensibine göre çalışır.
💡 Gerçek Hayat Analojisi: Market kasasındaki kuyruk gibi düşünün. İlk sıraya giren müşteri, ilk hizmet alan müşteridir. Yeni müşteriler her zaman kuyruğun sonuna eklenir.
Temel Prensip
- İlk eklenen eleman, ilk çıkarılan elemandır
- Elemanlar sadece bir uçtan eklenir (back/rear)
- Elemanlar sadece diğer uçtan çıkarılır (front)
┌───────────────────────────────────────────┐
│ QUEUE │
│ │
│ front/head back/rear/tail │
│ ↓ ↓ │
│ [a] → [b] → [c] → [d] → [e] │
│ ↑ ↑ │
│ ÇIKIŞ GİRİŞ │
│ (dequeue) (enqueue) │
└───────────────────────────────────────────┘
🔧 Temel Operasyonlar
| Operasyon | Açıklama | Karmaşıklık |
|---|---|---|
enqueue(e) |
Kuyruğun sonuna eleman ekler | O(1) |
dequeue() |
Kuyruğun başındaki elemanı çıkarır ve döndürür | O(1) |
first() / peek() |
Başındaki elemana bakar (çıkarmaz) | O(1) |
size() |
Eleman sayısını döndürür | O(1) |
isEmpty() |
Kuyruğun boş olup olmadığını kontrol eder | O(1) |
💻 Bağlı Liste ile İmplementasyon
Neden Bağlı Liste?
✅ Dinamik boyut - önceden boyut belirlemeye gerek yok
✅ Tüm operasyonlar O(1) zamanda çalışır
✅ Overflow problemi yok
Java Kodu
class Node<E> {
E data;
Node<E> next;
public Node(E data) {
this.data = data;
this.next = null;
}
}
public class LinkedListQueue<E> {
private Node<E> front;
private Node<E> back;
private int size;
public LinkedListQueue() {
front = null;
back = null;
size = 0;
}
// Kuyruğun sonuna eleman ekle
public void enqueue(E element) {
Node<E> newNode = new Node<>(element);
if (isEmpty()) {
// İlk eleman
front = newNode;
back = newNode;
} else {
// Mevcut back'in next'ini yeni node yap
back.next = newNode;
back = newNode;
}
size++;
}
// Kuyruğun başından eleman çıkar
public E dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
E data = front.data;
front = front.next;
size--;
// Eğer kuyruk boşaldıysa back'i de null yap
if (isEmpty()) {
back = null;
}
return data;
}
// Başındaki elemana bak (çıkarma)
public E first() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return front.data;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
}
Adım Adım Çalışma Örneği
Başlangıç: front → null, back → null, size = 0
enqueue(5):
front → [5] → null
back → [5]
size = 1
enqueue(2):
front → [5] → [2] → null
back → [2]
size = 2
enqueue(8):
front → [5] → [2] → [8] → null
back → [8]
size = 3
dequeue() → 5:
front → [2] → [8] → null
back → [8]
size = 2
first() → 2 (kuyruk değişmez)
🔄 Döngüsel Array ile İmplementasyon
Neden Döngüsel Array?
✅ Tüm operasyonlar O(1)
✅ Bellek verimliliği (boşluklar tekrar kullanılır)
✅ Cache-friendly (bellek lokalitesi iyi)
Döngüsel Mantık
Normal Array Problemi:
[x][x][c][d][e][ ][ ][ ] → dequeue sonrası boşluklar kullanılamaz
↑ ↑
front back
Döngüsel Çözüm:
[ ][g][c][d][e][f][ ][ ] → Array sonu başa döner
↑ ↑
back front
Java Kodu
public class CircularArrayQueue<E> {
private E[] data;
private int front;
private int size;
private static final int DEFAULT_CAPACITY = 10;
@SuppressWarnings("unchecked")
public CircularArrayQueue() {
data = (E[]) new Object[DEFAULT_CAPACITY];
front = 0;
size = 0;
}
// Kuyruğun sonuna eleman ekle
public void enqueue(E element) {
if (size == data.length) {
resize(2 * data.length);
}
// back pozisyonunu hesapla: (front + size) % capacity
int back = (front + size) % data.length;
data[back] = element;
size++;
}
// Kuyruğun başından eleman çıkar
public E dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
E element = data[front];
data[front] = null; // Garbage collection için
front = (front + 1) % data.length; // Döngüsel artış
size--;
// Bellek optimizasyonu: %25 doluysa boyutu yarıya indir
if (size > 0 && size == data.length / 4) {
resize(data.length / 2);
}
return element;
}
// Başındaki elemana bak
public E first() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return data[front];
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
// Array'i yeniden boyutlandır
@SuppressWarnings("unchecked")
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
// Elemanları yeni array'e kopyala
for (int i = 0; i < size; i++) {
newData[i] = data[(front + i) % data.length];
}
data = newData;
front = 0;
}
}
Adım Adım Çalışma Örneği
Başlangıç: [ ][ ][ ][ ][ ] front=0, size=0
enqueue(5):
[5][ ][ ][ ][ ] front=0, size=1
↑
front
enqueue(2):
[5][2][ ][ ][ ] front=0, size=2
↑ ↑
front back
enqueue(8):
[5][2][8][ ][ ] front=0, size=3
dequeue() → 5:
[ ][2][8][ ][ ] front=1, size=2
↑
front
enqueue(9):
[ ][2][8][9][ ] front=1, size=3
↑ ↑
front back
enqueue(4):
[ ][2][8][9][4] front=1, size=4
enqueue(7): (Döngüsel olarak başa döner)
[7][2][8][9][4] front=1, size=5
↑ ↑
back front
📊 İmplementasyon Karşılaştırması
| Özellik | Bağlı Liste | Döngüsel Array |
|---|---|---|
| enqueue() | O(1) | O(1) amortized* |
| dequeue() | O(1) | O(1) |
| Dinamik Boyut | ✅ Her zaman | ⚠️ Resize gerekir |
| Bellek Kullanımı | Her düğüm için extra pointer | Sürekli alan |
| Cache Locality | ❌ Düşük | ✅ Yüksek |
| Overflow | ❌ Olmaz | ⚠️ Resize ile çözülür |
| Kullanım | Boyut önceden bilinmiyorsa | Boyut tahmini yapılabiliyorsa |
*Amortized: Bazı durumlarda O(n) (resize), ama ortalama O(1)
🌍 Kullanım Alanları
İşletim Sistemleri
- Process scheduling (CPU görev sırası)
- Print queue (yazıcı kuyruğu)
Ağ ve İletişim
- Router’larda paket yönetimi
- Message queues (RabbitMQ, Kafka)
Algoritmalarda
- BFS (Breadth-First Search): Graf gezinme
- Buffer management
Günlük Uygulamalar
- Müşteri hizmetleri çağrı merkezi
- Online bilet satış sistemleri
- Restoran sipariş yönetimi
💡 Önemli Notlar
⚠️ Edge Cases
| Durum | Davranış |
|---|---|
Boş kuyruğa dequeue() |
Exception fırlat |
Tek elemana dequeue() |
front ve back null/reset |
İlk enqueue() |
front ve back aynı konumu gösterecek |
🎯 Ne Zaman Kullanmalı?
Queue Kullan:
- FIFO davranışı gerekiyorsa
- İşlem sırasını korumak önemliyorsa
- BFS algoritması yapılacaksa
Queue Kullanma:
- LIFO gerekiyorsa → Stack kullan
- Öncelik bazlı işlem → Priority Queue kullan
- Rastgele erişim → Array/List kullan
📚 Özet
Queue, FIFO prensibine göre çalışan temel bir veri yapısıdır. İki ana implementasyonu vardır:
- Bağlı Liste: Dinamik, overflow yok, her operasyon gerçek O(1)
- Döngüsel Array: Cache-friendly, bellek verimli, amortized O(1)
Her ikisi de avantajlı, seçim kullanım senaryosuna bağlıdır.