Bölüm 8: Kuyruklar ve Deque

Veri Yapıları
FIFOenqueue / dequeueDizi ile kuyrukDairesel kuyruk Bağlı liste ile kuyrukDequeKayan pencereRound robin
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

FIFO modeli

Kuyruğun ilk giren ilk çıkar davranışını açıklamak.

Gerçekleme

Kuyruğu dizi ve bağlı liste kullanarak gerçekleştirmek.

Kuyruk türleri

Dairesel kuyruk ile çift uçlu kuyruğu (deque) ayırt etmek ve uygun yerde kullanmak.

Uygulamalar

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

Ekleme (enqueue)Kuyruğun sonuna (arkasına) yeni bir öğe ekler.Zaman karmaşıklığı: O(1)
Çıkarma (dequeue)Kuyruğun başındaki (önündeki) öğeyi çıkarır ve döndürür. İlk giren öğe ilk çıkar (FIFO ilkesi).Zaman karmaşıklığı: O(1)
Kuyrukta ortadaki bir öğeye doğrudan erişilmez; yalnızca ön ve arka uçlar kullanılır.
← önarka

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)

İşlemAçı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
}
Boş kuyruktan çıkarma alt taşma (underflow), dolu kuyruğa ekleme taşma (overflow) hatasıdır. Goodrich'in arayüzü boş kuyrukta null döndürür; bazı uygulamalar istisna (exception) fırlatır.

Kuyruk ADT

Kuyruk İşlemleri Adım Adım

ÇıkışGirişboşon = arka = null

Başlangıçta kuyruk boştur: on ve arka hiçbir öğeyi göstermez (null).

ÇıkışGirişenqueue(20)20onarkaboyut = 1

İlk öğe eklenir: tek öğe hem ön hem arkadır.

ÇıkışGirişenqueue(10)2010onarkaboyut = 2

10 arkaya eklenir: arka ilerler, on yerinde kalır.

ÇıkışGirişenqueue(15)201015onarkaboyut = 3

15 arkaya eklenir. Kuyruk: 20, 10, 15 (önden arkaya).

ÇıkışGirişdequeue() → 201015onarka20çıkanboyut = 2

Öndeki 20 çıkar; on bir sonraki öğeye (10) ilerler.

ÇıkışGirişdequeue() → 1015onarka10çıkanboyut = 1

10 çıkar. Şimdi on ve arka aynı öğeyi (15) gösterir.

ÇıkışGirişdequeue() → 15boş15çıkanon = arka = null

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

İşlemHata fırlatırÖzel değer döner
Eklemeadd(e)offer(e) → false
Çıkarmaremove()poll() → null
İncelemeelement()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ş)
Kendi sınıflarımızda enqueue/dequeue adları, Java kütüphanesinde ise offer/poll/peek üçlüsü kullanılır.

Kuyruk ADT

Kuyrukların Kullanım Alanları

Üreticiklavye, ağ, süreçenqueueİş 5İş 4İş 3İş 2İş 1tampon kuyruk (FIFO)arkaöndequeueTüketiciCPU, yazıcı, işlemci
İşletim Sistemi ÇizelgeleyicisiÇalışmaya hazır süreçler hazır kuyruğunda bekler; CPU sırayla dağıtılır.
Yazdırma KuyruğuYazıcıya gönderilen belgeler geliş sırasıyla basılır.
Tamponlar (Buffer)Klavye girdisi, ağ paketleri, video akışı: üretici ile tüketici arasındaki hız farkını dengeler.
Genişlik Öncelikli Arama (BFS)Keşfedilen düğümler kuyrukta sıraya alınır; seviye seviye gezinme sağlar.

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, çıkarmada on bir artar. Öğeler yerinden kaydırılmaz.
  • Basit ve hızlıdır, ancak dizinin kapasitesi sabittir.
020110215345onarkaN = 6

İ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

012345boş: on = 0, arka = −1

Boş kuyruk, kapasite N = 6. arka = −1: henüz öğe yok.

01012345on = arkaenqueue(10) → arka = 0

enqueue(10): arka++ → 0, öğe dizi[0]'e yazılır.

0101202345onarkaenqueue(20) → arka = 1

enqueue(20): arka++ → 1, öğe dizi[1]'e yazılır.

010120230345onarkaenqueue(30) → arka = 2

enqueue(30): arka++ → 2, öğe dizi[2]'e yazılır.

01012023034045onarkaenqueue(40) → arka = 3

enqueue(40): arka++ → 3, öğe dizi[3]'e yazılır.

012023034045onarkadequeue() → 10, on = 1

dequeue() → 10: on++ → 1. Boşalan yuva bir daha kullanılmaz.

0123034045onarkadequeue() → 20, on = 2

dequeue() → 20: on++ → 2. Boşalan yuva bir daha kullanılmaz.

01234045on = arkadequeue() → 30, on = 3

dequeue() → 30: on++ → 3. Boşalan yuva bir daha kullanılmaz.

0123404505onarkaenqueue(50) → arka = 4

enqueue(50): arka = 4.

012340450560onarkaenqueue(60) → arka = 5

enqueue(60): arka = 5 = N − 1, dizinin son indisi.

01234045056070?onarkaenqueue(70): arka + 1 = 6 = N → “dolu”!

Yalnızca 3 öğe varken “dolu”: turuncu 3 yuva boşa gidiyor. Kaydırma O(n) sürer; çözüm dairesel kuyruk.

Çözüm 1: KaydırmaHer dequeue'da öğeleri sola kaydır → dequeue O(n).
Çözüm 2: Dairesel diziİndisler sona gelince başa sarar → her işlem O(1).

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.
100201302403504567basarka

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) % Nk = 2 → (6 + 2) % 8 = 0
dequeue sonrası bas(bas + 1) % N(6 + 1) % 8 = 7
C0D12345A6B7▲ bas▲ arkason öğesonra 0'a sarar →
Sıra önden arkaya: A (6), B (7), C (0), D (1). Mod işlemi, dizinin sonundan başına geçişi tek bir ifadeyle çözer.

Dizi ile Kuyruk

Dolu mu, Boş mu?

Boş0123▲ bas▲ arkabas == arkaDoluE0B1C2D3▲ bas▲ arkabas == arka (!)Bir yuva boş0B1C2D3▲ bas▲ arka(arka + 1) % N == bas

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:

1. Öğe sayacı (boyut)Boş: boyut == 0 · Dolu: boyut == N
Dizinin N yuvasının tamamı kullanılır. Bu derste kullandığımız yöntem.
2. Bir yuvayı boş bırakmakBoş: bas == arka · Dolu: (arka + 1) % N == bas
Sayaç gerekmez, ama en fazla N − 1 öğe saklanır.

Dizi ile Kuyruk

Dairesel Kuyruk Adım Adım (N = 5)

0123401234▲sonbas = 0boyut = 0son = (bas + boyut) % 5 = (0 + 0) % 5 = 0 · boyut = 0 → BOŞ

Boş kuyruk: boyut = 0. Halkada kırmızı kenarlı dilim bas, dizide ▲son sonraki yazma yeridir.

10012340101234▲bas▲sonbas = 0boyut = 1yazılan yer = (0 + 0) % 5 = 0

ekle(10) → dizi[0]; boyut = 1.

100201234010120234▲bas▲sonbas = 0boyut = 2yazılan yer = (0 + 1) % 5 = 1

ekle(20) → dizi[1]; boyut = 2.

1002013023401012023034▲bas▲sonbas = 0boyut = 3yazılan yer = (0 + 2) % 5 = 2

ekle(30) → dizi[2]; boyut = 3.

020130234012023034▲bas▲sonbas = 1boyut = 2bas = (0 + 1) % 5 = 1

cikar() → 10; bas = (0 + 1) % 5 = 1. Öğeler yerinden oynamaz.

01302340123034▲bas▲sonbas = 2boyut = 1bas = (1 + 1) % 5 = 2

cikar() → 20; bas = (1 + 1) % 5 = 2. Öğeler yerinden oynamaz.

013024034012303404▲bas▲sonbas = 2boyut = 2yazılan yer = (2 + 1) % 5 = 3

ekle(40) → dizi[3].

0130240350401230340450▲bas▲sonbas = 2boyut = 3yazılan yer = (2 + 2) % 5 = 4

ekle(50) → dizi[4].

60013024035040601230340450▲bas▲sonbas = 2boyut = 4yazılan yer = (2 + 3) % 5 = 0

ekle(60) → dizi[0]. Dizinin sonu aşıldı, başa sardı: basit dizide “dolu” denecek yerde boş yuva kullanıldı.

600701302403504060170230340450▲bas, sonbas = 2boyut = 5yazılan yer = (2 + 4) % 5 = 1

ekle(70) → dizi[1]. boyut == N → dolu; bas == son olsa da boyut sayesinde boş sanılmaz.

60070124035040601702340450▲bas▲sonbas = 3boyut = 4bas = (2 + 1) % 5 = 3

cikar() → 30; bas = 3.

6007012350406017023450▲bas▲sonbas = 4boyut = 3bas = (3 + 1) % 5 = 4

cikar() → 40; bas = 4.

600701234060170234▲bas▲sonbas = 0boyut = 2bas = (4 + 1) % 5 = 0

cikar() → 50; bas = 0. (4 + 1) % 5 = 0 → bas da başa sardı.

07012340170234▲bas▲sonbas = 1boyut = 1bas = (0 + 1) % 5 = 1

cikar() → 60; bas = 1.

0123401234▲sonbas = 2boyut = 0bas = (1 + 1) % 5 = 2

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()
}
Arka indisi ayrıca saklanmaz; gerektiğinde (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;
}
Ekleme yeriN = 5, bas = 3, boyut = 2son = (3 + 2) % 5 = 0
Önü ilerletmeN = 5, bas = 4bas = (4 + 1) % 5 = 0
MaliyetHer iki işlem de sabit sayıda adım: O(1). Döngü veya kaydırma yok.

Dizi ile Kuyruk · Kodu adım adım çalıştır

Kod İzleme: Dairesel ekle()

013024034012303404▲bas▲sonbas = 2boyut = 2

Başlangıç: dizi.length = 5, bas = 2, boyut = 2 (30 ve 40). Şimdi 50, 60, 70, 80 eklenecek.

013024034012303404▲bas▲sonbas = 2boyut = 2

ekle(50): doluMu() → boyut 2 == 5? hayır.

013024034012303404▲bas▲sonbas = 2boyut = 2

son = (2 + 2) % 5 = 4 % 5 = 4.

0130240350401230340450▲basbas = 2boyut = 2

dizi[4] = 50. Diğer öğeler yerinden oynamaz.

0130240350401230340450▲bas▲sonbas = 2boyut = 3

boyut++ → 3. bas değişmedi; ön hâlâ dizi[2].

0130240350401230340450▲bas▲sonbas = 2boyut = 3

ekle(60): doluMu() → boyut 3 == 5? hayır.

0130240350401230340450▲bas▲sonbas = 2boyut = 3

son = (2 + 3) % 5 = 5 % 5 = 0 → dizinin sonu aşıldı, başa sardı.

60013024035040601230340450▲basbas = 2boyut = 3

dizi[0] = 60. Diğer öğeler yerinden oynamaz.

60013024035040601230340450▲bas▲sonbas = 2boyut = 4

boyut++ → 4. bas değişmedi; ön hâlâ dizi[2].

60013024035040601230340450▲bas▲sonbas = 2boyut = 4

ekle(70): doluMu() → boyut 4 == 5? hayır.

60013024035040601230340450▲bas▲sonbas = 2boyut = 4

son = (2 + 4) % 5 = 6 % 5 = 1 → dizinin sonu aşıldı, başa sardı.

600701302403504060170230340450▲basbas = 2boyut = 4

dizi[1] = 70. Diğer öğeler yerinden oynamaz.

600701302403504060170230340450▲bas, sonbas = 2boyut = 5

boyut++ → 5. bas değişmedi; ön hâlâ dizi[2]. Kuyruk artık dolu.

600701302403504060170230340450▲bas, sonbas = 2boyut = 5

ekle(80): doluMu() → boyut 5 == 5? evet.

600701302403504060170230340450▲bas, sonbas = 2boyut = 5

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()

600701302403504060170230340450▲bas, sonbas = 2boyut = 5

Başlangıç: dolu kuyruk, bas = 2. Sıra önden arkaya: 30, 40, 50, 60, 70. Üç kez cikar() çağrılacak.

600701302403504060170230340450▲bas, sonbas = 2boyut = 5

bosMu(): boyut = 5 ≠ 0 → boş değil, devam.

600701302403504060170230340450▲bas, sonbas = 2boyut = 5

oge = dizi[2] = 30: öndeki (en eski) öğe.

60070124035040601702340450▲bas, sonbas = 2boyut = 5

dizi[2] = null: yuva boşalır; referans bırakılınca nesne çöp toplayıcıya kalabilir.

60070124035040601702340450▲bas, sonbas = 3boyut = 5

bas = (2 + 1) % 5 = 3.

60070124035040601702340450▲bas▲sonbas = 3boyut = 4

boyut-- → 4; return 30.

60070124035040601702340450▲bas▲sonbas = 3boyut = 4

bosMu(): boyut = 4 ≠ 0 → boş değil, devam.

60070124035040601702340450▲bas▲sonbas = 3boyut = 4

oge = dizi[3] = 40: öndeki (en eski) öğe.

6007012350406017023450▲bas▲sonbas = 3boyut = 4

dizi[3] = null: yuva boşalır; referans bırakılınca nesne çöp toplayıcıya kalabilir.

6007012350406017023450▲bas▲sonbas = 4boyut = 4

bas = (3 + 1) % 5 = 4.

6007012350406017023450▲bas▲sonbas = 4boyut = 3

boyut-- → 3; return 40.

6007012350406017023450▲bas▲sonbas = 4boyut = 3

bosMu(): boyut = 3 ≠ 0 → boş değil, devam.

6007012350406017023450▲bas▲sonbas = 4boyut = 3

oge = dizi[4] = 50: öndeki (en eski) öğe.

600701234060170234▲bas▲sonbas = 4boyut = 3

dizi[4] = null: yuva boşalır; referans bırakılınca nesne çöp toplayıcıya kalabilir.

600701234060170234▲bas▲sonbas = 0boyut = 3

bas = (4 + 1) % 5 = 0 → 5 % 5 = 0, bas başa sardı.

600701234060170234▲bas▲sonbas = 0boyut = 2

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

dizi0C1D2A3B▲ basbas = 2 · boyut = 4 · N = 4sıra: A B C D

Dolu kuyruk (bas = 2): sıra A, B, C, D, dizide ise C, D, A, B. Şimdi ekle(E).

dizi0C1D2A3B▲ basbas = 2 · boyut = 4 · N = 4dolu!

4 == 4 → dolu. Hata yerine iki katı kapasiteyle büyüt.

dizi0C1D2A3B▲ basyenibas = 2 · boyut = 4 · N = 4

8 hücrelik yeni dizi ayrılır.

dizi0C1D2A3B▲ basyeniAbas = 2 · boyut = 4 · N = 4

k = 0: yeni[0] = dizi[(2 + 0) % 4] = dizi[2] = A.

dizi0C1D2A3B▲ basyeniABbas = 2 · boyut = 4 · N = 4

k = 1: yeni[1] = dizi[(2 + 1) % 4] = dizi[3] = B.

dizi0C1D2A3B▲ basyeniABCbas = 2 · boyut = 4 · N = 4

k = 2: yeni[2] = dizi[(2 + 2) % 4] = dizi[0] = C. Mod ile başa sarıldı.

dizi0C1D2A3B▲ basyeniABCDbas = 2 · boyut = 4 · N = 4

k = 3: yeni[3] = dizi[(2 + 3) % 4] = dizi[1] = D.

dizi0A1B2C3D4567▲ basbas = 2 · boyut = 4 · N = 8bas henüz eski değer!

dizi = yeni: öğeler artık sıralı (Arrays.copyOf sırayı bozardı).

dizi0A1B2C3D4567▲ basbas = 0 · boyut = 4 · N = 8

bas = 0: ön öğe (A) artık dizi[0]'da.

dizi0A1B2C3D4E567▲ basbas = 0 · boyut = 4 · N = 8

ekle'ye dönülür: (0 + 4) % 8 = 4 → dizi[4] = E.

dizi0A1B2C3D4E567▲ basbas = 0 · boyut = 5 · N = 8

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ı).
  • on listenin başını, arka sonunu 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;
108115nullonarka

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ı

enqueue(x)
  1. Kuyruk dolu mu? (sabit kapasiteli dizide)
  2. Doluysa taşma (overflow) hatası ver, bitir.
  3. Değilse x'i arka konuma yaz.
  4. Arkayı bir sonraki konuma ilerlet.
  5. Öğe sayısını artır, başarılı dön.
dequeue()
  1. Kuyruk boş mu?
  2. Boşsa alt taşma (underflow) hatası ver, bitir.
  3. Değilse öndeki öğeyi al.
  4. Önü bir sonraki öğeye ilerlet.
  5. Öğe sayısını azalt, alınan öğeyi döndür.
AdımDairesel diziBağlı liste
Arkaya yazdizi[(bas + boyut) % N] = xarka.sonraki = yeni; arka = yeni
Önü ilerletbas = (bas + 1) % Non = on.sonraki
Dolu kontrolüboyut == Ngerekmez (bellek yettikçe)

Bağlı Liste ile Kuyruk · Kodu adım adım çalıştır

Kod İzleme: Bağlı Liste Kuyruğu enqueue

on = null · arka = null · uzunluk = 0

Boş kuyruk: on = arka = null. Sırayla enqueue(10), enqueue(15), enqueue(20).

10gecicion = null · arka = null · uzunluk = 0

new Dugum(10): sonraki = null olan yeni düğüm; henüz listeye bağlı değil.

10gecicion = null · arka = null · uzunluk = 0

bosMu() → true (uzunluk 0).

10on, geciciarka = null · uzunluk = 0

İlk düğüm: on = gecici.

10on, geciciarkauzunluk = 0

arka = gecici: arka artık yeni düğüm. Tek düğüm hem ön hem arka.

10on, geciciarkauzunluk = 1

uzunluk++ → 1.

1015ongeciciarkauzunluk = 1

new Dugum(15): sonraki = null olan yeni düğüm; henüz listeye bağlı değil.

1015ongeciciarkauzunluk = 1

bosMu() → false; else dalına gidilir.

1015ongeciciarkauzunluk = 1

arka.sonraki = gecici: eski arka (10) yeni düğüme bağlanır.

1015ongeciciarkauzunluk = 1

arka = gecici: arka artık yeni düğüm.

1015ongeciciarkauzunluk = 2

uzunluk++ → 2.

101520ongeciciarkauzunluk = 2

new Dugum(20): sonraki = null olan yeni düğüm; henüz listeye bağlı değil.

101520ongeciciarkauzunluk = 2

bosMu() → false; else dalına gidilir.

101520ongeciciarkauzunluk = 2

arka.sonraki = gecici: eski arka (15) yeni düğüme bağlanır.

101520ongeciciarkauzunluk = 2

arka = gecici: arka artık yeni düğüm.

101520ongeciciarkauzunluk = 3

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

101520onarkauzunluk = 3

Kuyruk: 10, 15, 20. Üç kez dequeue() çağrılacak.

101520onarkauzunluk = 3

bosMu() → false (uzunluk 3).

101520onarkauzunluk = 3

sonuc = on.veri = 10.

101520onarkauzunluk = 3

on = on.sonraki → düğüm(15). Eski düğüme artık kimse bakmıyor (çöp toplayıcı temizler).

101520onarkauzunluk = 3

on == null → false; arka aynı kalır.

1520onarkauzunluk = 2

uzunluk-- → 2; return 10.

1520onarkauzunluk = 2

bosMu() → false (uzunluk 2).

1520onarkauzunluk = 2

sonuc = on.veri = 15.

1520onarkauzunluk = 2

on = on.sonraki → düğüm(20). Eski düğüme artık kimse bakmıyor (çöp toplayıcı temizler).

1520onarkauzunluk = 2

on == null → false; arka aynı kalır.

20onarkauzunluk = 1

uzunluk-- → 1; return 15.

20onarkauzunluk = 1

bosMu() → false (uzunluk 1).

20onarkauzunluk = 1

sonuc = on.veri = 20.

20arkaon = null · uzunluk = 1

on = on.sonraki → null. Ama arka hâlâ silinen düğümü gösteriyor!

20arkaon = null · uzunluk = 1

on == null → true: kuyruk boşaldı.

20on = null · arka = null · uzunluk = 1

arka = null: bu satır olmasa kuyruk boşken arka silinen düğümü göstermeye devam ederdi (tutarsız durum).

on = null · arka = null · uzunluk = 0

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.
İşlemDairesel diziBağlı liste
enqueueO(1)O(1)
dequeueO(1)O(1)
first / size / isEmptyO(1)O(1)
BellekO(N) (kapasite kadar)O(n) (öğe sayısı kadar)

Kuyruk Türleri

Kuyruk Türleri

TürEklemeÇıkarmaNot
Basit kuyrukarkaönFIFO
Giriş sınırlı (input restricted)yalnızca arkaön ve arkakısıtlı deque
Çıkış sınırlı (output restricted)ön ve arkayalnızca önkısıtlı deque
Dairesel (circular)arkaöndizi yuvaları yeniden kullanılır
Çift uçlu (deque)ön ve arkaön ve arkaFIFO'ya bağlı değil
Öncelikli (priority)herhangien yüksek önceliksıra önceliğe göre
Deque, İngilizce double-ended queue kısaltmasıdır ve “dek” diye okunur. dequeue ise “kuyruktan çıkar” işleminin adıdır; ikisini karıştırmayın.

Kuyruk Türleri

Çift Uçlu Kuyruk (Deque)

2726222120232425ön (front / head)arka (rear / tail)ekleçıkarekleçıkar
  • 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.
İşlemJava ArrayDequeSü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: bas = son = nullboyut = 0

Boş deque. Çift yönlü bağlı liste ile uygulayacağız: her düğümde onceki ve sonraki.

5nullnullbassonsonaEkle(5)boyut = 1

İlk düğüm: bas = son = yeni düğüm.

58nullnullbassonsonaEkle(8)boyut = 2

Sona ekleme: yeni.onceki = son; son.sonraki = yeni; son = yeni.

358nullnullbassonbasaEkle(3)boyut = 3

Başa ekleme: yeni.sonraki = bas; bas.onceki = yeni; bas = yeni.

3589nullnullbassonsonaEkle(9)boyut = 4

Her iki uç da referansla tutulduğu için tüm eklemeler O(1).

358nullnullbassonsondanCikar()9 döndü · boyut = 3

Sondan çıkarma: son = son.onceki; son.sonraki = null. Önceki referansı sayesinde O(1).

58nullnullbassonbastanCikar()3 döndü · boyut = 2

Baştan çıkarma: bas = bas.sonraki; bas.onceki = null.

Tek yönlü listede sondan çıkarma O(n) olurdu (sondan bir önceki düğümü bulmak için). 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()

bas = null · son = null · boyut = 0

Boş deque. Önce basaEkle(5), sonra basaEkle(3): 3 başa geçecek.

5dbas = null · son = null · boyut = 0

new Dugum<>(5): onceki = sonraki = null.

5dbas = null · son = null · boyut = 0

bosMu() → true.

5bas, d, sonboyut = 0

bas = son = d: tek düğüm iki ucu birden oluşturur.

5bas, d, sonboyut = 1

boyut++ → 1.

53bas, sondboyut = 1

new Dugum<>(3): onceki = sonraki = null.

53bas, sondboyut = 1

bosMu() → false.

53bas, sondboyut = 1

d.sonraki = bas: yeni düğüm eski başı (5) gösterir.

53bas, sondboyut = 1

bas.onceki = d: geri bağlantı; artık iki yönde de gezilebilir.

53bas, dsonboyut = 1

bas = d: baş referansı yeni düğüme geçer.

53bas, dsonboyut = 2

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;
}
Son öğe çıkarıldığında iki uç da null yapılmalıdır. Aksi hâlde 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()

53bassonboyut = 2

Deque: 3 ⇄ 5. İki kez sondanCikar(): önce 5, sonra 3 çıkacak ve deque boşalacak.

53bassonboyut = 2

bosMu() → false (boyut 2).

53bassonboyut = 2

veri = son.veri = 5.

53bas, sonboyut = 2

son = son.onceki → düğüm(3). onceki sayesinde sondan bir öncekini aramaya gerek yok.

53bas, sonboyut = 2

son == null → false: deque boşalmadı.

53bas, sonboyut = 2

son.sonraki = null: 5 düğümüne giden bağ kopar; düğüm listeden ayrılır.

3bas, sonboyut = 1

boyut-- → 1; return 5.

3bas, sonboyut = 1

bosMu() → false (boyut 1).

3bas, sonboyut = 1

veri = son.veri = 3.

3basson = null · boyut = 1

son = son.onceki → null (tek düğüm vardı). Ama bas hâlâ onu gösteriyor!

3basson = null · boyut = 1

son == null → true: deque boşaldı.

3bas = null · son = null · boyut = 1

bas = null: iki uç da null olmalı; yoksa bas silinmiş düğümü gösterirdi.

bas = null · son = null · boyut = 0

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

a01132-13-3455366d: indis:değerd (ön)boşenb = []

k = 3. d indis tutar; değerler önden arkaya azalır, önde en büyük durur.

a01132-13-3455366d: indis:değerd (ön)0:1enb = []

a[0] = 1: 0 arkaya.

a01132-13-3455366d: indis:değerd (ön)1:3enb = []

a[1] = 3: arkadan at 0:1. 1 arkaya.

a01132-13-3455366d: indis:değerd (ön)1:32:-1enb = [3]

a[2] = -1: 2 arkaya. En büyük = 3.

a01132-13-3455366d: indis:değerd (ön)1:32:-13:-3enb = [3, 3]

a[3] = -3: 3 arkaya. En büyük = 3.

a01132-13-3455366d: indis:değerd (ön)4:5enb = [3, 3, 5]

a[4] = 5: önden at 1; arkadan at 3:-3; arkadan at 2:-1. 4 arkaya. En büyük = 5.

a01132-13-3455366d: indis:değerd (ön)4:55:3enb = [3, 3, 5, 5]

a[5] = 3: 5 arkaya. En büyük = 5.

a01132-13-3455366d: indis:değerd (ön)6:6enb = [3, 3, 5, 5, 6]

a[6] = 6: arkadan at 5:3; arkadan at 4:5. 6 arkaya. En büyük = 6.

a01132-13-3455366d: indis:değerd (ön)6:6enb = [3, 3, 5, 5, 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.

Hazır kuyruğu (FIFO)P1 · 5P2 · 3P3 · 2CPUBiten—Zaman çizelgesi (Gantt)012345678910t = 0 · quantum q = 2

Üç süreç hazır kuyruğunda; kalan süreleri 5, 3, 2 birim. Zaman dilimi (quantum) q = 2.

Hazır kuyruğu (FIFO)P2 · 3P3 · 2P1 · 3CPUP1Biten—Zaman çizelgesi (Gantt)P1012345678910t = 2 · quantum q = 2

P1 önden alınır (dequeue), 2 birim çalışır, kalan 3 ile arkaya eklenir (enqueue).

Hazır kuyruğu (FIFO)P3 · 2P1 · 3P2 · 1CPUP2Biten—Zaman çizelgesi (Gantt)P1P2012345678910t = 4 · quantum q = 2

P2 2 birim çalışır, kalan 1 ile arkaya döner.

Hazır kuyruğu (FIFO)P1 · 3P2 · 1CPUP3BitenP3Zaman çizelgesi (Gantt)P1P2P3012345678910t = 6 · quantum q = 2

P3'ün 2 birimi bir dilime sığar → t = 6'da biter, kuyruğa dönmez.

Hazır kuyruğu (FIFO)P2 · 1P1 · 1CPUP1BitenP3Zaman çizelgesi (Gantt)P1P2P3P1012345678910t = 8 · quantum q = 2

P1 yine 2 birim çalışır; kalan 1.

Hazır kuyruğu (FIFO)P1 · 1CPUP2BitenP3, P2Zaman çizelgesi (Gantt)P1P2P3P1P2012345678910t = 9 · quantum q = 2

P2'nin kalan 1 birimi biter (t = 9); dilimin tamamı kullanılmaz.

Hazır kuyruğu (FIFO)boşCPUP1BitenP3, P2, P1Zaman çizelgesi (Gantt)P1P2P3P1P2P1012345678910t = 10 · quantum q = 2

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)

hazır kuyruğu (ön → arka)P1:5P2:3P3:2CPUt = 0012345678910

roundRobin(q, 2): kalan süreleri 5, 3, 2 olan üç süreç (Surec: ad, kalan). t = 0.

hazır kuyruğu (ön → arka)P2:3P3:2CPUP1:5t = 0012345678910

Kuyruk boş değil → p = q.poll() = P1 (kalan 5); önden çıkar.

hazır kuyruğu (ön → arka)P2:3P3:2CPUP1:3t = 2P1012345678910

calis = min(2, 5) = 2; P1 2 birim çalışır → t = 2, kalan 3.

hazır kuyruğu (ön → arka)P2:3P3:2P1:3CPUt = 2P1012345678910

kalan 3 > 0 → q.offer(p): P1 kuyruğun arkasına döner.

hazır kuyruğu (ön → arka)P3:2P1:3CPUP2:3t = 2P1012345678910

Kuyruk boş değil → p = q.poll() = P2 (kalan 3); önden çıkar.

hazır kuyruğu (ön → arka)P3:2P1:3CPUP2:1t = 4P1P2012345678910

calis = min(2, 3) = 2; P2 2 birim çalışır → t = 4, kalan 1.

hazır kuyruğu (ön → arka)P3:2P1:3P2:1CPUt = 4P1P2012345678910

kalan 1 > 0 → q.offer(p): P2 kuyruğun arkasına döner.

hazır kuyruğu (ön → arka)P1:3P2:1CPUP3:2t = 4P1P2012345678910

Kuyruk boş değil → p = q.poll() = P3 (kalan 2); önden çıkar.

hazır kuyruğu (ön → arka)P1:3P2:1CPUP3:0t = 6P1P2P3012345678910

calis = min(2, 2) = 2; P3 2 birim çalışır → t = 6, kalan 0.

hazır kuyruğu (ön → arka)P1:3P2:1CPUt = 6P1P2P3012345678910

kalan 0 → P3 bitti (t = 6); kuyruğa dönmez.

hazır kuyruğu (ön → arka)P2:1CPUP1:3t = 6P1P2P3012345678910

Kuyruk boş değil → p = q.poll() = P1 (kalan 3); önden çıkar.

hazır kuyruğu (ön → arka)P2:1CPUP1:1t = 8P1P2P3P1012345678910

calis = min(2, 3) = 2; P1 2 birim çalışır → t = 8, kalan 1.

hazır kuyruğu (ön → arka)P2:1P1:1CPUt = 8P1P2P3P1012345678910

kalan 1 > 0 → q.offer(p): P1 kuyruğun arkasına döner.

hazır kuyruğu (ön → arka)P1:1CPUP2:1t = 8P1P2P3P1012345678910

Kuyruk boş değil → p = q.poll() = P2 (kalan 1); önden çıkar.

hazır kuyruğu (ön → arka)P1:1CPUP2:0t = 9P1P2P3P1P2012345678910

calis = min(2, 1) = 1; P2 1 birim çalışır → t = 9, kalan 0. Dilimin tamamı kullanılmadı.

hazır kuyruğu (ön → arka)P1:1CPUt = 9P1P2P3P1P2012345678910

kalan 0 → P2 bitti (t = 9); kuyruğa dönmez.

hazır kuyruğu (ön → arka)boşCPUP1:1t = 9P1P2P3P1P2012345678910

Kuyruk boş değil → p = q.poll() = P1 (kalan 1); önden çıkar.

hazır kuyruğu (ön → arka)boşCPUP1:0t = 10P1P2P3P1P2P1012345678910

calis = min(2, 1) = 1; P1 1 birim çalışır → t = 10, kalan 0. Dilimin tamamı kullanılmadı.

hazır kuyruğu (ön → arka)boşCPUt = 10P1P2P3P1P2P1012345678910

kalan 0 → P1 bitti (t = 10); kuyruğa dönmez.

hazır kuyruğu (ön → arka)boşCPUt = 10P1P2P3P1P2P1012345678910

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)

ABCDEhalkakuyruk (ön → arka)ABCDEelenenler—

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.

ABCDEhalkakuyruk (ön → arka)BCDEAA öne gelip arkaya taşındı (poll + offer)elenenler—

i = 1: q.offer(q.poll()) → A önden alınıp arkaya eklenir.

ABCDEhalkakuyruk (ön → arka)CDEABB öne gelip arkaya taşındı (poll + offer)elenenler—

i = 2: q.offer(q.poll()) → B önden alınıp arkaya eklenir.

ABCDEhalkakuyruk (ön → arka)DEABC elendielenenlerC

Öndeki C çıkarılır ve elenir. Kalan: 4 kişi.

ABCDEhalkakuyruk (ön → arka)EABDD öne gelip arkaya taşındı (poll + offer)elenenlerC

i = 1: q.offer(q.poll()) → D önden alınıp arkaya eklenir.

ABCDEhalkakuyruk (ön → arka)ABDEE öne gelip arkaya taşındı (poll + offer)elenenlerC

i = 2: q.offer(q.poll()) → E önden alınıp arkaya eklenir.

ABCDEhalkakuyruk (ön → arka)BDEA elendielenenlerC A

Öndeki A çıkarılır ve elenir. Kalan: 3 kişi.

ABCDEhalkakuyruk (ön → arka)DEBB öne gelip arkaya taşındı (poll + offer)elenenlerC A

i = 1: q.offer(q.poll()) → B önden alınıp arkaya eklenir.

ABCDEhalkakuyruk (ön → arka)EBDD öne gelip arkaya taşındı (poll + offer)elenenlerC A

i = 2: q.offer(q.poll()) → D önden alınıp arkaya eklenir.

ABCDEhalkakuyruk (ön → arka)BDE elendielenenlerC A E

Öndeki E çıkarılır ve elenir. Kalan: 2 kişi.

ABCDEhalkakuyruk (ön → arka)DBB öne gelip arkaya taşındı (poll + offer)elenenlerC A E

i = 1: q.offer(q.poll()) → B önden alınıp arkaya eklenir.

ABCDEhalkakuyruk (ön → arka)BDD öne gelip arkaya taşındı (poll + offer)elenenlerC A E

i = 2: q.offer(q.poll()) → D önden alınıp arkaya eklenir.

ABCDEhalkakuyruk (ön → arka)DB elendielenenlerC A E B

Öndeki B çıkarılır ve elenir. Kalan: 1 kişi.

ABCDEhalkakuyruk (ön → arka)DelenenlerC A E B

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.

Örnek 1n = 3 → {"1", "10", "11"}
Örnek 2n = 5 → {"1", "10", "11", "100", "101"}
01010111011100101110111
Fikir: x'in iki ardılı 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)

q (ön →)sonucboş0123

sonuc: 4 boş yuvalı dizi.

q (ön →)sonucboş0123

Boş bir kuyruk oluşturulur (LinkedList, Queue arayüzüyle).

q (ön →)sonuc10123

Kök “1” kuyruğa eklenir.

q (ön →)sonuc10123

i = 0 < 4 → döngü gövdesi.

q (ön →)sonucboş01123

sonuc[0] = q.poll() = “1”: kuyruğun önündeki en küçük sayı.

q (ön →)sonuc101101123

“10” ve “11” arkaya eklenir: “1” dizesinin ardılları, sıraları gelince çıkacak.

q (ön →)sonuc101101123

i = 1 < 4 → döngü gövdesi.

q (ön →)sonuc110111023

sonuc[1] = q.poll() = “10”: kuyruğun önündeki en küçük sayı.

q (ön →)sonuc111001010111023

“100” ve “101” arkaya eklenir: “10” dizesinin ardılları, sıraları gelince çıkacak.

q (ön →)sonuc111001010111023

i = 2 < 4 → döngü gövdesi.

q (ön →)sonuc100101011102113

sonuc[2] = q.poll() = “11”: kuyruğun önündeki en küçük sayı.

q (ön →)sonuc100101110111011102113

“110” ve “111” arkaya eklenir: “11” dizesinin ardılları, sıraları gelince çıkacak.

q (ön →)sonuc100101110111011102113

i = 3 < 4 → döngü gövdesi.

q (ön →)sonuc101110111011102113100

sonuc[3] = q.poll() = “100”: kuyruğun önündeki en küçük sayı.

q (ön →)sonuc10111011110001001011102113100

“1000” ve “1001” arkaya eklenir: “100” dizesinin ardılları, sıraları gelince çıkacak.

q (ön →)sonuc10111011110001001011102113100

i = 4 < 4 değil → döngü biter.

q (ön →)sonuc10111011110001001011102113100

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

boşgelenboşgidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: —

gelen eklemeler, giden çıkarmalar için (yığınların üstü yukarıda).

1gelenboşgidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: —

enqueue(1): gelen.push(1) → O(1).

12gelenboşgidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: —

enqueue(2): gelen.push(2) → O(1).

123gelenboşgidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: —

enqueue(3): gelen.push(3) → O(1).

123gelenboşgidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: —

dequeue: giden boş → gelen'deki her şey giden'e aktarılacak.

12gelen3gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: —

3: gelen'den pop, giden'e push. Aktarım sırayı ters çevirir; en eski öğe üste çıkar.

1gelen32gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: —

2: gelen'den pop, giden'e push. Aktarım sırayı ters çevirir; en eski öğe üste çıkar.

boşgelen321gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: —

1: gelen'den pop, giden'e push. Aktarım sırayı ters çevirir; en eski öğe üste çıkar.

boşgelen32gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1

giden.pop() → 1 (FIFO: ilk eklenenlerden).

4gelen32gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1

enqueue(4): gelen.push(4) → O(1).

4gelen32gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1

dequeue: giden boş değil → aktarım yok.

4gelen3gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1 2

giden.pop() → 2 (FIFO: ilk eklenenlerden).

4gelen3gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1 2

dequeue: giden boş değil → aktarım yok.

4gelenboşgidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1 2 3

giden.pop() → 3 (FIFO: ilk eklenenlerden).

4gelenboşgidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1 2 3

dequeue: giden boş → gelen'deki her şey giden'e aktarılacak.

boşgelen4gidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1 2 3

4: gelen'den pop, giden'e push. Aktarım sırayı ters çevirir; en eski öğe üste çıkar.

boşgelenboşgidenenqueue(1)enqueue(2)enqueue(3)dequeue()enqueue(4)dequeue()dequeue()dequeue()çıkış sırası: 1 2 3 4

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

Son öğede arka'yı unutmakBağlı liste kuyruğunda son düğüm çıkınca on = null olur; arka = null yapılmazsa silinmiş düğümü gösterir.
Dolu / boş karışıklığıYalnızca bas ve arka tutulursa boşta da dolu da bas == arka olur; boyut sayacı ya da bir boş yuva gerekir.
Büyütmede Arrays.copyOfBaşa sarmış dairesel kuyrukta sırayı bozar; öğeler (bas + k) % N sırasıyla kopyalanmalı.
add / offer, remove / polladd, 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

UygulamaEklemeÇıkarmaÖn öğeNot
Basit dizi (kaydırmalı)O(1)O(n)O(1)her çıkarmada kaydırma
Dairesel diziO(1)O(1)O(1)sabit kapasite, % N
Bağlı listeO(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 kuyrukO(1)O(1) amortizeO(1) amortizeher öğe bir kez aktarılır
FIFOArkadan ekle, önden çıkar.
Dairesel diziBoş/dolu ayrımı: size sayacı ya da bir boş yuva.
Uç durumlarBoşta çıkarma (underflow), son öğe çıkınca arka = null.

Özet

Etkileşimli Simülatörler

Kuyruk (Queue)enqueue, dequeue, front, rear ve arama işlemleri, adım adım.
Çift Uçlu Kuyruk (Deque)Her iki uçtan ekleme ve çıkarmanın etkisi.
Dairesel KuyrukDizi ve halka görünümünde bas, boyut, mod ile başa sarma ve dolu dizinin büyütülmesi.
Round Robin ÇizelgeleyiciFIFO hazır kuyruğu, ayarlanabilir quantum ve süreç ölçümleri.

Özet

Örnek Problemler

  1. Boş kuyrukta: enqueue(4), enqueue(7), dequeue(), enqueue(1), enqueue(9), dequeue(). first() nedir, kuyrukta ne kalır?
  2. N = 6, bas = 4, boyut = 3: öğeler hangi indislerde, sonraki enqueue nereye yazılır?
  3. “Bir yuva boş” yönteminde N = 10 ise en fazla kaç öğe saklanır? Dolu koşulu nedir?
  4. dequeue'dan if (on == null) arka = null; silinirse ne olur?
  5. İki yığınla kuyruk nasıl yapılır? dequeue'nun amortize maliyeti nedir?
  6. Deque ile bir kelimenin palindrom olduğu nasıl kontrol edilir?
Cevapları göster
  1. first() = 1; kuyruk (önden arkaya): 1, 9.
  2. İndis 4, 5, 0. Sonraki yazma: (4 + 3) % 6 = 1.
  3. 9 öğe; dolu: (arka + 1) % 10 == bas.
  4. Boş kuyrukta arka eski düğümü gösterir: rear() yanlış değer döndürür, düğüm bellekten atılamaz (tutarsız durum).
  5. 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).
  6. Harfleri deque'ye ekle; iki uçtan birer harf çıkarıp karşılaştır. O(n).

Özet

Alıştırmalar

  1. Bir kuyruğu yalnızca kuyruk işlemleri ve bir yığınla ters çevirin.
  2. Tek bir kuyrukla yığın yapın: push'u nasıl yazarsınız, maliyeti nedir?
  3. Josephus: 1..7 numaralı kişiler, k = 3. Eleme sırası ve kazanan?
  4. N = 5, bas = 3, boyut = 5 olan dairesel kuyruk büyütülünce yeni dizide öğeler hangi sırada olur?
  5. Bir kuyruğun ilk k öğesini, kalanların sırasını bozmadan ters çevirin.
  6. Kayan pencere en küçüğü için deque algoritmasında ne değişir?
İpuçları / cevaplar
  1. Hepsini dequeue edip yığına push, sonra pop edip enqueue → O(n).
  2. push(x): enqueue(x), sonra önceki size − 1 öğeyi dequeue+enqueue ile arkaya at → push O(n), pop O(1).
  3. Elenenler 3, 6, 2, 7, 5, 1; kazanan 4.
  4. dizi[3], dizi[4], dizi[0], dizi[1], dizi[2] → yeni[0..4]; bas = 0.
  5. İlk k'yı yığına al, geri enqueue et, sonra kalan n − k öğeyi döndür.
  6. 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, ArrayDeque ve PriorityQueue API belgeleri.

Son

Bölüm 8: Kuyruklar ve Deque
Sonraki sunum: Bölüm 9: Ağaçlar · Çalışma soruları · Simülatörler: Queue Simulator, Deque Simulator, Circular Queue Simulator, Round Robin Scheduler · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.