Bölüm 8: Kuyruklar ve Deque
Giriş
Öğrenme Çıktıları
Kuyruğun ilk giren ilk çıkar davranışını açıklamak.
Kuyruğu dizi ve bağlı liste kullanarak gerçekleştirmek.
Dairesel kuyruk ile çift uçlu kuyruğu (deque) ayırt etmek ve uygun yerde kullanmak.
Uygun kuyruk türünü gerçek yaşam problemlerinde seçmek.
Kuyruk ADT
Kuyruk (Queue)
- Her iki ucu açık, doğrusal bir veri yapısıdır.
- İşlemler ilk giren, ilk çıkar (FIFO - First-In, First-Out) sırasıyla yapılır.
- Ekleme bir uçtan (arka, rear), çıkarma diğer uçtan (ön, front) yapılır.
- Market kasasındaki, bilet gişesindeki sıra gibi: önce gelen önce hizmet alır.
Kuyruk ADT
Temel Kuyruk İşlemleri
Tek sıra yürüyen atlar: öndeki önce geçer, yeni gelen sona katılır.
Kuyruk ADT
Kuyruk Soyut Veri Tipi (Queue ADT)
| İşlem | Açıklama |
|---|---|
enqueue(e) | e'yi arkaya ekler |
dequeue() | öndeki öğeyi çıkarır ve döndürür |
first() / peek() | öndeki öğeyi döndürür, çıkarmaz |
rear() | arkadaki öğeyi döndürür, çıkarmaz |
size() | öğe sayısı |
isEmpty() | kuyruk boş mu? |
isFull() | dolu mu? (yalnızca sabit kapasitede) |
public interface Queue<E> { int size(); boolean isEmpty(); void enqueue(E e); E first(); // boşsa null E dequeue(); // boşsa null }
null döndürür; bazı uygulamalar istisna (exception) fırlatır.Kuyruk ADT
Kuyruk İşlemleri Adım Adım
Başlangıçta kuyruk boştur: on ve arka hiçbir öğeyi göstermez (null).
İlk öğe eklenir: tek öğe hem ön hem arkadır.
10 arkaya eklenir: arka ilerler, on yerinde kalır.
15 arkaya eklenir. Kuyruk: 20, 10, 15 (önden arkaya).
Öndeki 20 çıkar; on bir sonraki öğeye (10) ilerler.
10 çıkar. Şimdi on ve arka aynı öğeyi (15) gösterir.
Son öğe çıkınca ikisi de null olur. Çıkış sırası 20, 10, 15 = giriş sırası (FIFO).
Kuyruk ADT
Java'da Kuyruk: java.util.Queue
| İşlem | Hata fırlatır | Özel değer döner |
|---|---|---|
| Ekleme | add(e) | offer(e) → false |
| Çıkarma | remove() | poll() → null |
| İnceleme | element() | peek() → null |
ArrayDeque: dairesel dizi ile uygulanır, genellikle en hızlı seçenektir.LinkedList: bağlı liste ile uygulanır.
Queue<Integer> q = new ArrayDeque<>(); q.offer(20); q.offer(10); q.offer(15); // [20, 10, 15] q.peek(); // 20, çıkarmaz q.poll(); // 20 → [10, 15] q.poll(); // 10 → [15] q.size(); // 1 q.poll(); // 15 → [] q.poll(); // null (boş)
enqueue/dequeue adları, Java kütüphanesinde ise offer/poll/peek üçlüsü kullanılır.Kuyruk ADT
Kuyrukların Kullanım Alanları
Dizi ile Kuyruk
Dizi ile Kuyruk
- Öğeler bir dizide tutulur; iki indis kuyruğun uçlarını gösterir:
- on (ön, front): ilk öğenin indisi.
- arka (rear): son öğenin indisi.
- Eklemede
arka, çıkarmadaonbir artar. Öğeler yerinden kaydırılmaz. - Basit ve hızlıdır, ancak dizinin kapasitesi sabittir.
İndis 0'daki öğe daha önce çıkarıldı; o yuva artık boş ama basit dizi kuyruğu onu yeniden kullanamaz.
Dizi ile Kuyruk
Basit Dizi Kuyruğunun Sorunu
Boş kuyruk, kapasite N = 6. arka = −1: henüz öğe yok.
enqueue(10): arka++ → 0, öğe dizi[0]'e yazılır.
enqueue(20): arka++ → 1, öğe dizi[1]'e yazılır.
enqueue(30): arka++ → 2, öğe dizi[2]'e yazılır.
enqueue(40): arka++ → 3, öğe dizi[3]'e yazılır.
dequeue() → 10: on++ → 1. Boşalan yuva bir daha kullanılmaz.
dequeue() → 20: on++ → 2. Boşalan yuva bir daha kullanılmaz.
dequeue() → 30: on++ → 3. Boşalan yuva bir daha kullanılmaz.
enqueue(50): arka = 4.
enqueue(60): arka = 5 = N − 1, dizinin son indisi.
Yalnızca 3 öğe varken “dolu”: turuncu 3 yuva boşa gidiyor. Kaydırma O(n) sürer; çözüm dairesel kuyruk.
Dizi ile Kuyruk
Dairesel Kuyruk (Circular Queue)
- Normal kuyruk gibi FIFO çalışır; ama dizinin son yuvasından sonra ilk yuvaya dönülür.
- Dizinin başı ve sonu mantıksal olarak birleşip bir halka oluşturur.
- İndisler mod (%) işlemiyle ilerletilir:
(i + 1) % N. - Boşalan yuvalar yeniden kullanılır; öğe kaydırmaya gerek kalmaz.
- Halka tampon (ring buffer) olarak da bilinir.
Burada arka, bir sonraki öğenin yazılacağı boş yuvadır: arka = (bas + boyut) % N = (0 + 5) % 8 = 5.
Dizi ile Kuyruk
İndislerin Mod ile Gezinmesi
| Ne? | Formül | Örnek: N = 8, bas = 6, boyut = 4 |
|---|---|---|
| Sonraki boş yuva (ekleme yeri) | (bas + boyut) % N | (6 + 4) % 8 = 2 |
| Son öğe | (bas + boyut − 1) % N | (6 + 3) % 8 = 1 |
| Baştan k. öğe (k = 0, 1, …) | (bas + k) % N | k = 2 → (6 + 2) % 8 = 0 |
| dequeue sonrası bas | (bas + 1) % N | (6 + 1) % 8 = 7 |
Dizi ile Kuyruk
Dolu mu, Boş mu?
Yalnızca bas ve arka indisleri tutulursa, boş ve dolu kuyrukta ikisi de aynı yuvayı gösterir; durumlar ayırt edilemez. İki yaygın çözüm:
boyut == 0 · Dolu: boyut == NDizinin N yuvasının tamamı kullanılır. Bu derste kullandığımız yöntem.
bas == arka · Dolu: (arka + 1) % N == basSayaç gerekmez, ama en fazla N − 1 öğe saklanır.
Dizi ile Kuyruk
Dairesel Kuyruk Adım Adım (N = 5)
Boş kuyruk: boyut = 0. Halkada kırmızı kenarlı dilim bas, dizide ▲son sonraki yazma yeridir.
ekle(10) → dizi[0]; boyut = 1.
ekle(20) → dizi[1]; boyut = 2.
ekle(30) → dizi[2]; boyut = 3.
cikar() → 10; bas = (0 + 1) % 5 = 1. Öğeler yerinden oynamaz.
cikar() → 20; bas = (1 + 1) % 5 = 2. Öğeler yerinden oynamaz.
ekle(40) → dizi[3].
ekle(50) → dizi[4].
ekle(60) → dizi[0]. Dizinin sonu aşıldı, başa sardı: basit dizide “dolu” denecek yerde boş yuva kullanıldı.
ekle(70) → dizi[1]. boyut == N → dolu; bas == son olsa da boyut sayesinde boş sanılmaz.
cikar() → 30; bas = 3.
cikar() → 40; bas = 4.
cikar() → 50; bas = 0. (4 + 1) % 5 = 0 → bas da başa sardı.
cikar() → 60; bas = 1.
cikar() → 70; bas = 2. boyut = 0 → kuyruk yine boş; çıkış sırası 30, 40, 50, 60, 70 = giriş sırası (FIFO).
Dizi ile Kuyruk
Dairesel Kuyruk: Sınıf ve Durum Sorguları
public class DaireselKuyruk<E> { private E[] dizi; // öğeleri saklar private int bas = 0; // ilk öğenin indisi (front) private int boyut = 0; // öğe sayısı (size) @SuppressWarnings("unchecked") public DaireselKuyruk(int kapasite) { dizi = (E[]) new Object[kapasite]; } public int boyut() { return boyut; } public boolean bosMu() { return boyut == 0; } public boolean doluMu() { return boyut == dizi.length; } public E ilk() { return bosMu() ? null : dizi[bas]; } // ekle(), cikar() }
(bas + boyut) % dizi.length ile hesaplanır. Böylece iki alanın birbiriyle tutarsız kalma riski ortadan kalkar.Dizi ile Kuyruk
Dairesel Kuyruk: ekle ve cikar
public void ekle(E oge) { // enqueue if (doluMu()) throw new IllegalStateException("dolu"); int son = (bas + boyut) % dizi.length; dizi[son] = oge; boyut++; }
public E cikar() { // dequeue if (bosMu()) throw new IllegalStateException("boş"); E oge = dizi[bas]; dizi[bas] = null; // referansı bırak bas = (bas + 1) % dizi.length; boyut--; return oge; }
Dizi ile Kuyruk · Kodu adım adım çalıştır
Kod İzleme: Dairesel ekle()
Başlangıç: dizi.length = 5, bas = 2, boyut = 2 (30 ve 40). Şimdi 50, 60, 70, 80 eklenecek.
ekle(50): doluMu() → boyut 2 == 5? hayır.
son = (2 + 2) % 5 = 4 % 5 = 4.
dizi[4] = 50. Diğer öğeler yerinden oynamaz.
boyut++ → 3. bas değişmedi; ön hâlâ dizi[2].
ekle(60): doluMu() → boyut 3 == 5? hayır.
son = (2 + 3) % 5 = 5 % 5 = 0 → dizinin sonu aşıldı, başa sardı.
dizi[0] = 60. Diğer öğeler yerinden oynamaz.
boyut++ → 4. bas değişmedi; ön hâlâ dizi[2].
ekle(70): doluMu() → boyut 4 == 5? hayır.
son = (2 + 4) % 5 = 6 % 5 = 1 → dizinin sonu aşıldı, başa sardı.
dizi[1] = 70. Diğer öğeler yerinden oynamaz.
boyut++ → 5. bas değişmedi; ön hâlâ dizi[2]. Kuyruk artık dolu.
ekle(80): doluMu() → boyut 5 == 5? evet.
Kuyruk dolu → IllegalStateException("dolu") fırlatılır; dizi değişmez. Kapasite 5 öğede tükendi, hiçbir yuva boşa gitmedi.
public void ekle(E oge) { if (doluMu()) throw new IllegalStateException("dolu"); int son = (bas + boyut) % dizi.length; dizi[son] = oge; boyut++; }
Dizi ile Kuyruk · Kodu adım adım çalıştır
Kod İzleme: Dairesel cikar()
Başlangıç: dolu kuyruk, bas = 2. Sıra önden arkaya: 30, 40, 50, 60, 70. Üç kez cikar() çağrılacak.
bosMu(): boyut = 5 ≠ 0 → boş değil, devam.
oge = dizi[2] = 30: öndeki (en eski) öğe.
dizi[2] = null: yuva boşalır; referans bırakılınca nesne çöp toplayıcıya kalabilir.
bas = (2 + 1) % 5 = 3.
boyut-- → 4; return 30.
bosMu(): boyut = 4 ≠ 0 → boş değil, devam.
oge = dizi[3] = 40: öndeki (en eski) öğe.
dizi[3] = null: yuva boşalır; referans bırakılınca nesne çöp toplayıcıya kalabilir.
bas = (3 + 1) % 5 = 4.
boyut-- → 3; return 40.
bosMu(): boyut = 3 ≠ 0 → boş değil, devam.
oge = dizi[4] = 50: öndeki (en eski) öğe.
dizi[4] = null: yuva boşalır; referans bırakılınca nesne çöp toplayıcıya kalabilir.
bas = (4 + 1) % 5 = 0 → 5 % 5 = 0, bas başa sardı.
boyut-- → 2; return 50. Sabit sayıda adım: O(1).
public E cikar() { if (bosMu()) throw new IllegalStateException("boş"); E oge = dizi[bas]; dizi[bas] = null; // referansı bırak bas = (bas + 1) % dizi.length; boyut--; return oge; }
Dizi ile Kuyruk · Kodu adım adım çalıştır
Kod İzleme: Dolu Dairesel Kuyruğu Büyütme
Dolu kuyruk (bas = 2): sıra A, B, C, D, dizide ise C, D, A, B. Şimdi ekle(E).
4 == 4 → dolu. Hata yerine iki katı kapasiteyle büyüt.
8 hücrelik yeni dizi ayrılır.
k = 0: yeni[0] = dizi[(2 + 0) % 4] = dizi[2] = A.
k = 1: yeni[1] = dizi[(2 + 1) % 4] = dizi[3] = B.
k = 2: yeni[2] = dizi[(2 + 2) % 4] = dizi[0] = C. Mod ile başa sarıldı.
k = 3: yeni[3] = dizi[(2 + 3) % 4] = dizi[1] = D.
dizi = yeni: öğeler artık sıralı (Arrays.copyOf sırayı bozardı).
bas = 0: ön öğe (A) artık dizi[0]'da.
ekle'ye dönülür: (0 + 4) % 8 = 4 → dizi[4] = E.
boyut → 5. Büyütme O(n), ama 2 katına çıktığı için ekle amortize O(1).
private void buyut(int yeniKap) { E[] yeni = (E[]) new Object[yeniKap]; for (int k = 0; k < boyut; k++) yeni[k] = dizi[(bas + k) % dizi.length]; dizi = yeni; bas = 0; } public void ekle(E oge) { if (boyut == dizi.length) buyut(2 * dizi.length); dizi[(bas + boyut) % dizi.length] = oge; boyut++; }
Bağlı Liste ile Kuyruk
Bağlı Liste ile Kuyruk
- Her kuyruk öğesi bir düğümdür (veri + sonraki referansı).
onlistenin başını,arkasonunu gösterir: ekleme sona, çıkarma baştan.- Boyut dinamiktir; kapasite sınırı (dolu kuyruk) yoktur.
- Düğüm başına fazladan bir referans ve nesne oluşturma maliyeti vardır.
class Dugum { int veri; Dugum sonraki; Dugum(int veri) { this.veri = veri; } } // alanlar: Dugum on, arka; int uzunluk;
Neden ekleme sona, çıkarma başa? Tek yönlü listede baştan silmek O(1), ama sondan silmek için sondan bir önceki düğümü bulmak O(n) gerekir.
Bağlı Liste ile Kuyruk
Enqueue ve Dequeue Algoritması
- Kuyruk dolu mu? (sabit kapasiteli dizide)
- Doluysa taşma (overflow) hatası ver, bitir.
- Değilse x'i arka konuma yaz.
- Arkayı bir sonraki konuma ilerlet.
- Öğe sayısını artır, başarılı dön.
- Kuyruk boş mu?
- Boşsa alt taşma (underflow) hatası ver, bitir.
- Değilse öndeki öğeyi al.
- Önü bir sonraki öğeye ilerlet.
- Öğe sayısını azalt, alınan öğeyi döndür.
| Adım | Dairesel dizi | Bağlı liste |
|---|---|---|
| Arkaya yaz | dizi[(bas + boyut) % N] = x | arka.sonraki = yeni; arka = yeni |
| Önü ilerlet | bas = (bas + 1) % N | on = on.sonraki |
| Dolu kontrolü | boyut == N | gerekmez (bellek yettikçe) |
Bağlı Liste ile Kuyruk · Kodu adım adım çalıştır
Kod İzleme: Bağlı Liste Kuyruğu enqueue
Boş kuyruk: on = arka = null. Sırayla enqueue(10), enqueue(15), enqueue(20).
new Dugum(10): sonraki = null olan yeni düğüm; henüz listeye bağlı değil.
bosMu() → true (uzunluk 0).
İlk düğüm: on = gecici.
arka = gecici: arka artık yeni düğüm. Tek düğüm hem ön hem arka.
uzunluk++ → 1.
new Dugum(15): sonraki = null olan yeni düğüm; henüz listeye bağlı değil.
bosMu() → false; else dalına gidilir.
arka.sonraki = gecici: eski arka (10) yeni düğüme bağlanır.
arka = gecici: arka artık yeni düğüm.
uzunluk++ → 2.
new Dugum(20): sonraki = null olan yeni düğüm; henüz listeye bağlı değil.
bosMu() → false; else dalına gidilir.
arka.sonraki = gecici: eski arka (15) yeni düğüme bağlanır.
arka = gecici: arka artık yeni düğüm.
uzunluk++ → 3. Liste hiç gezinilmedi: her enqueue O(1).
public void enqueue(int veri) { Dugum gecici = new Dugum(veri); if (bosMu()) { on = gecici; } else { arka.sonraki = gecici; } arka = gecici; uzunluk++; }
Bağlı Liste ile Kuyruk · Kodu adım adım çalıştır
Kod İzleme: Bağlı Liste Kuyruğu dequeue
Kuyruk: 10, 15, 20. Üç kez dequeue() çağrılacak.
bosMu() → false (uzunluk 3).
sonuc = on.veri = 10.
on = on.sonraki → düğüm(15). Eski düğüme artık kimse bakmıyor (çöp toplayıcı temizler).
on == null → false; arka aynı kalır.
uzunluk-- → 2; return 10.
bosMu() → false (uzunluk 2).
sonuc = on.veri = 15.
on = on.sonraki → düğüm(20). Eski düğüme artık kimse bakmıyor (çöp toplayıcı temizler).
on == null → false; arka aynı kalır.
uzunluk-- → 1; return 15.
bosMu() → false (uzunluk 1).
sonuc = on.veri = 20.
on = on.sonraki → null. Ama arka hâlâ silinen düğümü gösteriyor!
on == null → true: kuyruk boşaldı.
arka = null: bu satır olmasa kuyruk boşken arka silinen düğümü göstermeye devam ederdi (tutarsız durum).
uzunluk-- → 0; return 20. Çıkış sırası 10, 15, 20 (FIFO), her işlem O(1).
public int dequeue() { if (bosMu()) throw new NoSuchElementException(); int sonuc = on.veri; on = on.sonraki; if (on == null) arka = null; uzunluk--; return sonuc; }
Bağlı Liste ile Kuyruk
Dairesel Dizi mi, Bağlı Liste mi?
Dairesel dizi
- Bellekte ardışık; önbellek dostu ve hızlı.
- Öğe başına ek referans yok.
- Sabit kapasite: dolunca taşma ya da daha büyük diziye kopyalama (O(n), amortize O(1)).
Bağlı liste
- Kapasite sınırı yok; gerektiği kadar büyür.
- Her ekleme/çıkarma kesin O(1).
- Her düğüm için ek referans ve nesne oluşturma maliyeti.
| İşlem | Dairesel dizi | Bağlı liste |
|---|---|---|
| enqueue | O(1) | O(1) |
| dequeue | O(1) | O(1) |
| first / size / isEmpty | O(1) | O(1) |
| Bellek | O(N) (kapasite kadar) | O(n) (öğe sayısı kadar) |
Kuyruk Türleri
Kuyruk Türleri
| Tür | Ekleme | Çıkarma | Not |
|---|---|---|---|
| Basit kuyruk | arka | ön | FIFO |
| Giriş sınırlı (input restricted) | yalnızca arka | ön ve arka | kısıtlı deque |
| Çıkış sınırlı (output restricted) | ön ve arka | yalnızca ön | kısıtlı deque |
| Dairesel (circular) | arka | ön | dizi yuvaları yeniden kullanılır |
| Çift uçlu (deque) | ön ve arka | ön ve arka | FIFO'ya bağlı değil |
| Öncelikli (priority) | herhangi | en yüksek öncelik | sıra önceliğe göre |
dequeue ise “kuyruktan çıkar” işleminin adıdır; ikisini karıştırmayın.Kuyruk Türleri
Çift Uçlu Kuyruk (Deque)
- Double-ended queue: hem ön hem arka uçtan ekleme ve çıkarma yapılır.
- FIFO kuralına bağlı değildir: yalnızca arka uçtan çalışan bir deque yığın (LIFO), arkadan ekleyip önden çıkaran bir deque kuyruk olur.
| İşlem | Java ArrayDeque | Süre |
|---|---|---|
| basaEkle(e) | offerFirst(e) | O(1) |
| sonaEkle(e) | offerLast(e) | O(1) |
| bastanCikar() | pollFirst() | O(1) |
| sondanCikar() | pollLast() | O(1) |
| ilk() / son() | peekFirst() / peekLast() | O(1) |
Kuyruk Türleri
Deque İşlemleri Adım Adım
Boş deque. Çift yönlü bağlı liste ile uygulayacağız: her düğümde onceki ve sonraki.
İlk düğüm: bas = son = yeni düğüm.
Sona ekleme: yeni.onceki = son; son.sonraki = yeni; son = yeni.
Başa ekleme: yeni.sonraki = bas; bas.onceki = yeni; bas = yeni.
Her iki uç da referansla tutulduğu için tüm eklemeler O(1).
Sondan çıkarma: son = son.onceki; son.sonraki = null. Önceki referansı sayesinde O(1).
Baştan çıkarma: bas = bas.sonraki; bas.onceki = null.
onceki referansı bunu O(1) yapar.Kuyruk Türleri
Deque: Başa ve Sona Ekleme
public void basaEkle(E veri) { Dugum<E> d = new Dugum<>(veri); if (bosMu()) { bas = son = d; } else { d.sonraki = bas; bas.onceki = d; bas = d; } boyut++; }
public void sonaEkle(E veri) { Dugum<E> d = new Dugum<>(veri); if (bosMu()) { bas = son = d; } else { d.onceki = son; son.sonraki = d; son = d; } boyut++; }
class Dugum<E> { E veri; Dugum<E> onceki, sonraki; Dugum(E veri) { this.veri = veri; } }
İki metot birbirinin aynadaki görüntüsüdür: bas ↔ son, onceki ↔ sonraki.
Kuyruk Türleri · Kodu adım adım çalıştır
Kod İzleme: Deque basaEkle()
Boş deque. Önce basaEkle(5), sonra basaEkle(3): 3 başa geçecek.
new Dugum<>(5): onceki = sonraki = null.
bosMu() → true.
bas = son = d: tek düğüm iki ucu birden oluşturur.
boyut++ → 1.
new Dugum<>(3): onceki = sonraki = null.
bosMu() → false.
d.sonraki = bas: yeni düğüm eski başı (5) gösterir.
bas.onceki = d: geri bağlantı; artık iki yönde de gezilebilir.
bas = d: baş referansı yeni düğüme geçer.
boyut++ → 2. Hiç gezinme yok: O(1). sonaEkle bunun aynadaki görüntüsüdür.
public void basaEkle(E veri) { Dugum<E> d = new Dugum<>(veri); if (bosMu()) { bas = son = d; } else { d.sonraki = bas; bas.onceki = d; bas = d; } boyut++; }
Kuyruk Türleri
Deque: Baştan ve Sondan Çıkarma
public E bastanCikar() { if (bosMu()) return null; E veri = bas.veri; bas = bas.sonraki; if (bas == null) son = null; // boşaldı else bas.onceki = null; boyut--; return veri; }
public E sondanCikar() { if (bosMu()) return null; E veri = son.veri; son = son.onceki; if (son == null) bas = null; // boşaldı else son.sonraki = null; boyut--; return veri; }
bas null iken son silinmiş düğümü göstermeye devam eder; düğüm bellekten atılamaz ve son() yanlış değer döndürür. Boş deque'de Goodrich'teki gibi null döndürülür.Kuyruk Türleri · Kodu adım adım çalıştır
Kod İzleme: Deque sondanCikar()
Deque: 3 ⇄ 5. İki kez sondanCikar(): önce 5, sonra 3 çıkacak ve deque boşalacak.
bosMu() → false (boyut 2).
veri = son.veri = 5.
son = son.onceki → düğüm(3). onceki sayesinde sondan bir öncekini aramaya gerek yok.
son == null → false: deque boşalmadı.
son.sonraki = null: 5 düğümüne giden bağ kopar; düğüm listeden ayrılır.
boyut-- → 1; return 5.
bosMu() → false (boyut 1).
veri = son.veri = 3.
son = son.onceki → null (tek düğüm vardı). Ama bas hâlâ onu gösteriyor!
son == null → true: deque boşaldı.
bas = null: iki uç da null olmalı; yoksa bas silinmiş düğümü gösterirdi.
boyut-- → 0; return 3. Her iki durumda da O(1).
public E sondanCikar() { if (bosMu()) return null; E veri = son.veri; son = son.onceki; if (son == null) bas = null; // boşaldı else son.sonraki = null; boyut--; return veri; }
Kuyruk Türleri · Kodu adım adım çalıştır
Deque Uygulaması: Kayan Pencerede En Büyük
k = 3. d indis tutar; değerler önden arkaya azalır, önde en büyük durur.
a[0] = 1: 0 arkaya.
a[1] = 3: arkadan at 0:1. 1 arkaya.
a[2] = -1: 2 arkaya. En büyük = 3.
a[3] = -3: 3 arkaya. En büyük = 3.
a[4] = 5: önden at 1; arkadan at 3:-3; arkadan at 2:-1. 4 arkaya. En büyük = 5.
a[5] = 3: 5 arkaya. En büyük = 5.
a[6] = 6: arkadan at 5:3; arkadan at 4:5. 6 arkaya. En büyük = 6.
Her indis bir kez girer, en çok bir kez çıkar → O(n) (kaba kuvvet O(n·k)).
int[] pencereMax(int[] a, int k) { int[] enb = new int[a.length - k + 1]; Deque<Integer> d = new ArrayDeque<>(); // indis for (int i = 0; i < a.length; i++) { if (!d.isEmpty() && d.peekFirst() <= i - k) d.pollFirst(); // pencere dışı while (!d.isEmpty() && a[d.peekLast()] <= a[i]) d.pollLast(); // aday değil d.offerLast(i); if (i >= k - 1) enb[i - k + 1] = a[d.peekFirst()]; } return enb; }
Uygulamalar
Uygulama: Round Robin Çizelgeleyici
İşletim sistemi CPU'yu süreçler arasında sırayla paylaştırır: kuyruğun önündeki süreç en fazla q birim çalışır, bitmediyse kuyruğun arkasına döner.
Üç süreç hazır kuyruğunda; kalan süreleri 5, 3, 2 birim. Zaman dilimi (quantum) q = 2.
P1 önden alınır (dequeue), 2 birim çalışır, kalan 3 ile arkaya eklenir (enqueue).
P2 2 birim çalışır, kalan 1 ile arkaya döner.
P3'ün 2 birimi bir dilime sığar → t = 6'da biter, kuyruğa dönmez.
P1 yine 2 birim çalışır; kalan 1.
P2'nin kalan 1 birimi biter (t = 9); dilimin tamamı kullanılmaz.
Tümü bitti. Tamamlanma zamanları: P3 = 6, P2 = 9, P1 = 10.
Simülatör: Round Robin Scheduler (ayarlanabilir quantum ve süreçler).
Uygulamalar · Kodu adım adım çalıştır
Kod İzleme: roundRobin(q, 2)
roundRobin(q, 2): kalan süreleri 5, 3, 2 olan üç süreç (Surec: ad, kalan). t = 0.
Kuyruk boş değil → p = q.poll() = P1 (kalan 5); önden çıkar.
calis = min(2, 5) = 2; P1 2 birim çalışır → t = 2, kalan 3.
kalan 3 > 0 → q.offer(p): P1 kuyruğun arkasına döner.
Kuyruk boş değil → p = q.poll() = P2 (kalan 3); önden çıkar.
calis = min(2, 3) = 2; P2 2 birim çalışır → t = 4, kalan 1.
kalan 1 > 0 → q.offer(p): P2 kuyruğun arkasına döner.
Kuyruk boş değil → p = q.poll() = P3 (kalan 2); önden çıkar.
calis = min(2, 2) = 2; P3 2 birim çalışır → t = 6, kalan 0.
kalan 0 → P3 bitti (t = 6); kuyruğa dönmez.
Kuyruk boş değil → p = q.poll() = P1 (kalan 3); önden çıkar.
calis = min(2, 3) = 2; P1 2 birim çalışır → t = 8, kalan 1.
kalan 1 > 0 → q.offer(p): P1 kuyruğun arkasına döner.
Kuyruk boş değil → p = q.poll() = P2 (kalan 1); önden çıkar.
calis = min(2, 1) = 1; P2 1 birim çalışır → t = 9, kalan 0. Dilimin tamamı kullanılmadı.
kalan 0 → P2 bitti (t = 9); kuyruğa dönmez.
Kuyruk boş değil → p = q.poll() = P1 (kalan 1); önden çıkar.
calis = min(2, 1) = 1; P1 1 birim çalışır → t = 10, kalan 0. Dilimin tamamı kullanılmadı.
kalan 0 → P1 bitti (t = 10); kuyruğa dönmez.
Kuyruk boş → döngü biter. Tamamlanma: P3 = 6, P2 = 9, P1 = 10. Her tur bir poll + en çok bir offer: O(1).
void roundRobin(Queue<Surec> q, int dilim) { int t = 0; while (!q.isEmpty()) { Surec p = q.poll(); int calis = Math.min(dilim, p.kalan); t += calis; p.kalan -= calis; if (p.kalan > 0) q.offer(p); else System.out.println(p.ad + " t=" + t); } }
Uygulamalar · Kodu adım adım çalıştır
Kod İzleme: Josephus Problemi (n = 5, k = 3)
5 kişi halkada. Her turda öndeki k − 1 = 2 kişi arkaya geçer, 3. kişi elenir. Kuyruk, halkayı döndürmenin en doğal yoludur.
i = 1: q.offer(q.poll()) → A önden alınıp arkaya eklenir.
i = 2: q.offer(q.poll()) → B önden alınıp arkaya eklenir.
Öndeki C çıkarılır ve elenir. Kalan: 4 kişi.
i = 1: q.offer(q.poll()) → D önden alınıp arkaya eklenir.
i = 2: q.offer(q.poll()) → E önden alınıp arkaya eklenir.
Öndeki A çıkarılır ve elenir. Kalan: 3 kişi.
i = 1: q.offer(q.poll()) → B önden alınıp arkaya eklenir.
i = 2: q.offer(q.poll()) → D önden alınıp arkaya eklenir.
Öndeki E çıkarılır ve elenir. Kalan: 2 kişi.
i = 1: q.offer(q.poll()) → B önden alınıp arkaya eklenir.
i = 2: q.offer(q.poll()) → D önden alınıp arkaya eklenir.
Öndeki B çıkarılır ve elenir. Kalan: 1 kişi.
Tek kişi kaldı: kazanan D. n kişi, k adım: O(n·k) kuyruk işlemi.
String josephus(Queue<String> q, int k) { while (q.size() > 1) { for (int i = 1; i < k; i++) q.offer(q.poll()); // öndekini arkaya String elenen = q.poll(); System.out.println(elenen + " elendi"); } return q.poll(); }
Uygulamalar
Örnek: 1'den n'e Kadar İkilik Sayılar
Kuyruk kullanarak 1'den n'e kadar sayıların ikilik (binary) gösterimlerini üretin.
x + "0" (2x) ve x + "1" (2x + 1)'dir. Kuyruktan bir sayı alıp sonuca yazar, bu iki sayıyı arkaya eklersek sayılar seviye seviye, yani artan sırada üretilir (genişlik öncelikli gezinme).Uygulamalar · Kodu adım adım çalıştır
Kod İzleme: ikilikSayiUret(4)
sonuc: 4 boş yuvalı dizi.
Boş bir kuyruk oluşturulur (LinkedList, Queue arayüzüyle).
Kök “1” kuyruğa eklenir.
i = 0 < 4 → döngü gövdesi.
sonuc[0] = q.poll() = “1”: kuyruğun önündeki en küçük sayı.
“10” ve “11” arkaya eklenir: “1” dizesinin ardılları, sıraları gelince çıkacak.
i = 1 < 4 → döngü gövdesi.
sonuc[1] = q.poll() = “10”: kuyruğun önündeki en küçük sayı.
“100” ve “101” arkaya eklenir: “10” dizesinin ardılları, sıraları gelince çıkacak.
i = 2 < 4 → döngü gövdesi.
sonuc[2] = q.poll() = “11”: kuyruğun önündeki en küçük sayı.
“110” ve “111” arkaya eklenir: “11” dizesinin ardılları, sıraları gelince çıkacak.
i = 3 < 4 → döngü gövdesi.
sonuc[3] = q.poll() = “100”: kuyruğun önündeki en küçük sayı.
“1000” ve “1001” arkaya eklenir: “100” dizesinin ardılları, sıraları gelince çıkacak.
i = 4 < 4 değil → döngü biter.
return {“1”, “10”, “11”, “100”}. n poll + 2n offer → O(n) kuyruk işlemi (dize uzunluğu O(log n) olduğundan toplam karakter işi O(n log n)).
String[] ikilikSayiUret(int n) { String[] sonuc = new String[n]; Queue<String> q = new LinkedList<>(); q.offer("1"); for (int i = 0; i < n; i++) { sonuc[i] = q.poll(); q.offer(sonuc[i] + "0"); q.offer(sonuc[i] + "1"); } return sonuc; }
Uygulamalar · Kodu adım adım çalıştır
Kod İzleme: İki Yığınla Kuyruk
gelen eklemeler, giden çıkarmalar için (yığınların üstü yukarıda).
enqueue(1): gelen.push(1) → O(1).
enqueue(2): gelen.push(2) → O(1).
enqueue(3): gelen.push(3) → O(1).
dequeue: giden boş → gelen'deki her şey giden'e aktarılacak.
3: gelen'den pop, giden'e push. Aktarım sırayı ters çevirir; en eski öğe üste çıkar.
2: gelen'den pop, giden'e push. Aktarım sırayı ters çevirir; en eski öğe üste çıkar.
1: gelen'den pop, giden'e push. Aktarım sırayı ters çevirir; en eski öğe üste çıkar.
giden.pop() → 1 (FIFO: ilk eklenenlerden).
enqueue(4): gelen.push(4) → O(1).
dequeue: giden boş değil → aktarım yok.
giden.pop() → 2 (FIFO: ilk eklenenlerden).
dequeue: giden boş değil → aktarım yok.
giden.pop() → 3 (FIFO: ilk eklenenlerden).
dequeue: giden boş → gelen'deki her şey giden'e aktarılacak.
4: gelen'den pop, giden'e push. Aktarım sırayı ters çevirir; en eski öğe üste çıkar.
giden.pop() → 4. Çıkış 1, 2, 3, 4 = giriş sırası. Her öğe en çok bir kez aktarılır → dequeue amortize O(1) (tek çağrı en kötü O(n)).
Deque<Integer> gelen = new ArrayDeque<>(); Deque<Integer> giden = new ArrayDeque<>(); void enqueue(int x) { gelen.push(x); } int dequeue() { if (giden.isEmpty()) while (!gelen.isEmpty()) giden.push(gelen.pop()); return giden.pop(); }
Özet
Sık Yapılan Hatalar
on = null olur; arka = null yapılmazsa silinmiş düğümü gösterir.bas == arka olur; boyut sayacı ya da bir boş yuva gerekir.Arrays.copyOfBaşa sarmış dairesel kuyrukta sırayı bozar; öğeler (bas + k) % N sırasıyla kopyalanmalı.add, remove, element hata fırlatır; offer, poll, peek false / null döner.PriorityQueue FIFO değildirQueue<Integer> q = new PriorityQueue<>() öğeleri önceliğe göre verir, geliş sırasına göre değil.ArrayDeque ve nullArrayDeque null öğe kabul etmez (NullPointerException); null, “boş” sonucuyla karışır.Özet
Özet: Kuyruk Uygulamalarının Maliyetleri
| Uygulama | Ekleme | Çıkarma | Ön öğe | Not |
|---|---|---|---|---|
| Basit dizi (kaydırmalı) | O(1) | O(n) | O(1) | her çıkarmada kaydırma |
| Dairesel dizi | O(1) | O(1) | O(1) | sabit kapasite, % N |
| Bağlı liste | O(1) | O(1) | O(1) | on + arka referansı |
| Deque (çift yönlü liste) | O(1) iki uç | O(1) iki uç | O(1) | onceki referansı |
| İki yığınla kuyruk | O(1) | O(1) amortize | O(1) amortize | her öğe bir kez aktarılır |
size sayacı ya da bir boş yuva.arka = null.Özet
Etkileşimli Simülatörler
Özet
Örnek Problemler
- Boş kuyrukta: enqueue(4), enqueue(7), dequeue(), enqueue(1), enqueue(9), dequeue(). first() nedir, kuyrukta ne kalır?
- N = 6, bas = 4, boyut = 3: öğeler hangi indislerde, sonraki enqueue nereye yazılır?
- “Bir yuva boş” yönteminde N = 10 ise en fazla kaç öğe saklanır? Dolu koşulu nedir?
- dequeue'dan
if (on == null) arka = null;silinirse ne olur? - İki yığınla kuyruk nasıl yapılır? dequeue'nun amortize maliyeti nedir?
- Deque ile bir kelimenin palindrom olduğu nasıl kontrol edilir?
Cevapları göster
- first() = 1; kuyruk (önden arkaya): 1, 9.
- İndis 4, 5, 0. Sonraki yazma: (4 + 3) % 6 = 1.
- 9 öğe; dolu: (arka + 1) % 10 == bas.
- Boş kuyrukta arka eski düğümü gösterir: rear() yanlış değer döndürür, düğüm bellekten atılamaz (tutarsız durum).
- Ekle: gelen'e push. Çıkar: giden boşsa gelen'i giden'e aktar, giden'den pop. Her öğe en çok bir kez aktarılır → amortize O(1).
- Harfleri deque'ye ekle; iki uçtan birer harf çıkarıp karşılaştır. O(n).
Özet
Alıştırmalar
- Bir kuyruğu yalnızca kuyruk işlemleri ve bir yığınla ters çevirin.
- Tek bir kuyrukla yığın yapın: push'u nasıl yazarsınız, maliyeti nedir?
- Josephus: 1..7 numaralı kişiler, k = 3. Eleme sırası ve kazanan?
- N = 5, bas = 3, boyut = 5 olan dairesel kuyruk büyütülünce yeni dizide öğeler hangi sırada olur?
- Bir kuyruğun ilk k öğesini, kalanların sırasını bozmadan ters çevirin.
- Kayan pencere en küçüğü için deque algoritmasında ne değişir?
İpuçları / cevaplar
- Hepsini dequeue edip yığına push, sonra pop edip enqueue → O(n).
- push(x): enqueue(x), sonra önceki size − 1 öğeyi dequeue+enqueue ile arkaya at → push O(n), pop O(1).
- Elenenler 3, 6, 2, 7, 5, 1; kazanan 4.
- dizi[3], dizi[4], dizi[0], dizi[1], dizi[2] → yeni[0..4]; bas = 0.
- İlk k'yı yığına al, geri enqueue et, sonra kalan n − k öğeyi döndür.
- Karşılaştırma tersine döner:
a[d.peekLast()] >= a[i]olanlar atılır.
Özet
Kaynaklar
- Goodrich, Tamassia ve Goldwasser, Data Structures and Algorithms in Java, 6. baskı, 2014.
- Lafore, Data Structures and Algorithms in Java, 2. baskı, 2002.
- Oracle, Java SE 17
Queue,Deque,ArrayDequevePriorityQueueAPI belgeleri.