Bölüm 5: Kuyruklar

Veri Yapıları
FIFOenqueue / dequeueDizi ile kuyrukDairesel kuyruk Bağlı liste ile kuyrukÖncelikli kuyrukDequeZamanlayıcılar
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, çift uçlu ve öncelikli kuyrukları ayırt etmek.

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.
Bir patika üzerinde arka arkaya, tek sıra halinde yürüyen atlar.

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şfront = rear = null

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

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

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

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

10 arkaya eklenir: rear ilerler, front yerinde kalır.

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

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

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

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

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

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

ÇıkışGirişdequeue() → 15boş15çıkanfront = rear = 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ş)
Ders boyunca kendi sınıflarımızda enqueue/dequeue adlarını, Java kütüphanesinde ise offer/poll/peek üçlüsünü kullanacağız.

Kuyruk ADT

Kuyrukların Kullanım Alanları

İşletim Sistemi ZamanlayıcısıÇalışmaya hazır süreçler hazır kuyruğunda bekler; CPU sırayla dağıtılır (bu bölümün sonunda).
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 dolaşım sağlar (Bölüm 11).

Dizi ile Kuyruk

Dizi ile Kuyruk

  • Öğeler bir dizide tutulur; iki indis kuyruğun uçlarını gösterir:
    • front (ön): ilk öğenin indisi.
    • rear (arka): son öğenin indisi.
  • Eklemede rear, çıkarmada front bir artar. Öğeler yerinden kaydırılmaz.
  • Basit ve hızlıdır, ancak dizinin kapasitesi sabittir.
020110215345frontrearN = 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ş: front = 0, rear = −1

Boş kuyruk. Kapasite N = 6.

10020130240345frontrear4 × enqueue → front = 0, rear = 3

10, 20, 30, 40 eklenir: her eklemede rear++.

01240345front = rear3 × dequeue → front = 3, rear = 3

Üç öğe çıkar: her çıkarmada front++. İndis 0–2 artık boş.

012403504605frontrear2 × enqueue → front = 3, rear = 5

50 ve 60 eklenir; rear dizinin son indisine (N − 1) ulaştı.

012403504605frontrearenqueue(70): rear + 1 = 6 = N → “dolu”!

Sadece 3 öğe varken “dolu” hatası: başta 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.
100201302403504567frontarka

Burada arka, bir sonraki öğenin yazılacağı boş yuvadır: arka = (front + size) % N = (0 + 5) % 8 = 5.

Dizi ile Kuyruk

İndislerin Mod ile Dolaşması

Ne?FormülÖrnek: N = 8, front = 6, size = 4
Sonraki boş yuva (ekleme yeri)(front + size) % N(6 + 4) % 8 = 2
Son öğe(front + size − 1) % N(6 + 3) % 8 = 1
Baştan k. öğe (k = 0, 1, …)(front + k) % Nk = 2 → (6 + 2) % 8 = 0
dequeue sonrası front(front + 1) % N(6 + 1) % 8 = 7
C0D12345A6B7▲ front▲ 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▲ front▲ arkafront == arkaDoluE0B1C2D3▲ front▲ arkafront == arka (!)Bir yuva boş0B1C2D3▲ front▲ arka(arka + 1) % N == front

Yalnızca front 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ı (size)Boş: size == 0 · Dolu: size == N
Dizinin N yuvasının tamamı kullanılır. Bu derste kullandığımız yöntem.
2. Bir yuvayı boş bırakmakBoş: front == arka · Dolu: (arka + 1) % N == front
Sayaç gerekmez, ama en fazla N − 1 öğe saklanır.

Dizi ile Kuyruk

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

01234indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 0 · size = 0 · arka = (0 + 0) % 5 = 0 → BOŞ

Boş kuyruk: size = 0. front ile arka aynı yuvayı gösterir.

10020130234indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 0 · size = 3 · arka = (0 + 3) % 5 = 33 × enqueue

10, 20, 30 eklenir; her eklemede size bir artar, arka hesaplanır.

0130234indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 2 · size = 1 · arka = (2 + 1) % 5 = 32 × dequeue

10 ve 20 çıkar: front = (0 + 1) % 5 = 1, sonra (1 + 1) % 5 = 2.

013024034indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 2 · size = 2 · arka = (2 + 2) % 5 = 4enqueue(40)

40, arka = 3'e yazılır.

01302403504indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 2 · size = 3 · arka = (2 + 3) % 5 = 0enqueue(50)

50 son yuvaya (4) yazılır. Yeni arka = (2 + 3) % 5 = 0 → başa sardı.

6001302403504indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 2 · size = 4 · arka = (2 + 4) % 5 = 1enqueue(60)

60, indis 0'a yazılır: basit dizide “dolu” denecek yerde boş yuva yeniden kullanıldı.

600701302403504indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 2 · size = 5 · arka = (2 + 5) % 5 = 2 → DOLUenqueue(70)

size == N → dolu. front == arka olsa da size sayesinde boş sanılmaz.

6007012403504indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 3 · size = 4 · arka = (3 + 4) % 5 = 2dequeue() → 30

30 çıkar: front = (2 + 1) % 5 = 3. Öndeki öğe hâlâ en eski öğedir (FIFO).

01234indis N−1'den sonra 0'a sarar▲ front▲ arkafront = 2 · size = 0 · arka = (2 + 0) % 5 = 2 → BOŞ4 × dequeue

40, 50, 60, 70 sırayla çıkar; front 3 → 4 → 0 → 1 → 2. size = 0 → boş.

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() → sonraki slayt
}
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.

Bağlı Liste ile Kuyruk

Bağlı Liste ile Kuyruk

  • Her kuyruk öğesi bir düğümdür (veri + sonraki referansı).
  • front listenin başını, rear 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 front, rear;
int uzunluk;
108115nullfrontrear

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] = xrear.sonraki = yeni; rear = yeni
Önü ilerletbas = (bas + 1) % Nfront = front.sonraki
Dolu kontrolüboyut == Ngerekmez (bellek yettikçe)

Bağlı Liste ile Kuyruk

Bağlı Liste Kuyruğu: enqueue

front = null, rear = nulluzunluk = 0

Boş kuyruk. enqueue(10) çağrılır.

10nullgecicifront = null, rear = nulluzunluk = 0 · veri = 10

new Dugum(10): sonraki = null olan yeni düğüm.

10nullfrontgecicirear = nulluzunluk = 0 · veri = 10

bosMu() doğru → front = gecici.

10nullfrontrearuzunluk = 1

rear = gecici, uzunluk++. Tek düğüm hem ön hem arka.

10null15nullfrontreargeciciuzunluk = 1 · veri = 15

enqueue(15): yeni düğüm; kuyruk boş değil → else dalı.

1015nullfrontreargeciciuzunluk = 1 · veri = 15

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

1015nullfrontrearuzunluk = 2

rear = gecici, uzunluk++ → 2.

101520nullfrontrearuzunluk = 3

enqueue(20) aynı adımlarla. Liste dolaşılmaz: O(1).

public void enqueue(int veri) {
    Dugum gecici = new Dugum(veri);
    if (bosMu()) {
        front = gecici;
    } else {
        rear.sonraki = gecici;
    }
    rear = gecici;
    uzunluk++;
}

Bağlı Liste ile Kuyruk

Bağlı Liste Kuyruğu: dequeue

101520nullfrontrearuzunluk = 3

Üç öğeli kuyruk. dequeue() çağrılır.

101520nullfrontrearuzunluk = 3 · sonuc = 10

Boş değil → sonuc = front.veri = 10.

101520nullfrontrearuzunluk = 3 · sonuc = 10

front = front.sonraki. Eski düğümü çöp toplayıcı (garbage collector) temizler.

1520nullfrontrearuzunluk = 2 · 10 döndürüldü

front null değil → rear aynı kalır; uzunluk--, 10 döner.

20nullfrontrearuzunluk = 1 · 15 döndürüldü

Bir dequeue() daha: 15 döner. front ve rear aynı düğümde.

20nullrearfront = null · sonuc = 20uzunluk = 1

Son öğe: front null oldu, ama rear hâlâ eski düğümü gösteriyor!

front = null, rear = nulluzunluk = 0 · 20 döndürüldü

front == null → rear = null. Bu satır olmasa sonraki ekleme bozulurdu.

public int dequeue() {
    if (bosMu())
        throw new NoSuchElementException();
    int sonuc = front.veri;
    front = front.sonraki;
    if (front == null)
        rear = 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

Öncelikli Kuyruk (Priority Queue)

  • Her öğenin bir önceliği vardır; çıkarma sırasını geliş sırası değil öncelik belirler.
  • Kullanım: işletim sistemi zamanlayıcısı, acil servis triyajı, olay simülasyonu, Dijkstra, Huffman kodlaması.
012345dizi▲ en yüksek öncelik (dizi[0])başlangıç

Boş öncelikli kuyruk. Burada büyük değer = yüksek öncelik.

0Aö=212345dizi▲ en yüksek öncelik (dizi[0])ekle(A, 2)

A tek öğe olarak dizi[0]'a yerleşir.

0Bö=51Aö=22345dizi▲ en yüksek öncelik (dizi[0])ekle(B, 5)

5 > 2 → A bir sağa kayar, B başa yerleşir.

0Bö=51Aö=22Cö=1345dizi▲ en yüksek öncelik (dizi[0])ekle(C, 1)

1 en düşük öncelik → C kaydırma olmadan sona eklenir.

0Bö=51Dö=52Aö=23Cö=145dizi▲ en yüksek öncelik (dizi[0])ekle(D, 5)

D: A ve C kayar; 5 > 5 değil → D, B'nin arkasında kalır (eşitlerde FIFO).

0Dö=51Aö=22Cö=1345dizi▲ en yüksek öncelik (dizi[0])Bçıkarıldıcikar() → B

En yüksek öncelikli B, dizi[0]'dan alınır; kalanlar bir sola kayar.

0Aö=21Cö=12345dizi▲ en yüksek öncelik (dizi[0])Dçıkarıldıcikar() → D

Sonra D çıkar. Çıkış sırası önceliğe göre: B, D, A, C.

Kuyruk Türleri

Öncelikli Kuyruk: Sıralı Dizi ile

class Eleman<T> {
    T veri;
    int oncelik;
    Eleman(T v, int p) { veri = v; oncelik = p; }
}
// OncelikliKuyruk<E> alanları
private Eleman<E>[] dizi =
    (Eleman<E>[]) new Eleman[10];
private int boyut = 0;
public void ekle(E veri, int p) {
    if (boyut == dizi.length)
        dizi = Arrays.copyOf(dizi, 2 * boyut);
    int i = boyut;
    while (i > 0 && dizi[i-1].oncelik < p) {
        dizi[i] = dizi[i - 1];  // sağa kaydır
        i--;
    }
    dizi[i] = new Eleman<>(veri, p);
    boyut++;
}
public E cikar() {
    if (bosMu()) return null;
    E sonuc = dizi[0].veri;
    for (int i = 1; i < boyut; i++)
        dizi[i - 1] = dizi[i];
    dizi[--boyut] = null;
    return sonuc;
}

Dizi, öncelikleri büyükten küçüğe sıralı tutulur: ekle O(n) (kaydırma), cikar O(n). Heap ile ikisi de O(log n) olur (Bölüm 8).

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

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 ve sonraki sonaEkle bozulur. Boş deque'de Goodrich'teki gibi null döndürülür.

Uygulamalar

Uygulama: Round Robin Zamanlayıcı

İş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 (quantum ve süreçleri kendiniz belirleyin).

Uygulamalar

Uygulama: Öncelikli Zamanlayıcı

Yüksek (0)P4Orta (1)P1P5Düşük (2)P2P3önce en yüksekdolu seviyeCPU
Kesin öncelikCPU her zaman en yüksek öncelikli, boş olmayan kuyruğun önündeki süreci seçer.
Seviye içinde FIFOAynı öncelikteki süreçler kendi kuyruklarında geliş sırasıyla (gerekirse round robin) çalışır.
Açlık ve yaşlandırmaDüşük öncelikliler hiç çalışamayabilir (starvation). Bekledikçe önceliği artırmak (aging) çözümdür.

Simülatör: Priority Scheduler (üç seviye, quantum, isteğe bağlı yaşlandırma).

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'ten sonra gelen iki sayı x + "0" ve x + "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 dolaşım).

Uygulamalar

İkilik Sayılar: ikilikSayiUret(4)

sonuc[]0123ÇıkışGiriş"1"qbaşlangıç: q.offer("1")

Kuyruğa kök olarak "1" eklenir.

sonuc[]01123ÇıkışGiriş"10""11"qi = 0

"1" alınır → sonuc[0]; "10" ve "11" arkaya eklenir.

sonuc[]0111023ÇıkışGiriş"11""100""101"qi = 1

"10" alınır; "100", "101" eklenir.

sonuc[]011102113ÇıkışGiriş"100""101""110""111"qi = 2

"11" alınır; "110", "111" eklenir.

sonuc[]011102113100ÇıkışGiriş"101""110""111""1000""1001"qi = 3

"100" alınır; "1000", "1001" eklenir.

sonuc[]011102113100ÇıkışGiriş"101""110""111""1000""1001"qreturn sonuc

Döngü biter: {"1", "10", "11", "100"}. Kuyrukta kalanlar kullanılmaz.

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;
}

n kez poll, 2n kez offer → O(n) kuyruk işlemi. Dizgiler O(log n) uzunlukta olduğundan toplam karakter işi O(n log n).

Ö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)front + rear referansı
Deque (çift yönlü liste)O(1) iki uçO(1) iki uçO(1)onceki referansı
Öncelikli (sıralı dizi)O(n)O(n)*O(1)heap ile O(log n)
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 rear = null.

* Diziyi küçükten büyüğe sıralı tutup en yüksek önceliği sondan çıkarırsak çıkarma O(1) olur; ekleme yine O(n).

Özet

Etkileşimli Simülatörler

Kuyruk (Queue)enqueue, dequeue, front, rear ve arama işlemlerini adım adım deneyin.
Çift Uçlu Kuyruk (Deque)Her iki uçtan ekleme ve çıkarmanın etkisini izleyin.
Round Robin ZamanlayıcıFIFO hazır kuyruğu, ayarlanabilir quantum ve süreç ölçümleri.
Öncelikli ZamanlayıcıÜç öncelik seviyesi, seviye içinde FIFO ve yaşlandırma (aging).

Ö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, front = 4, size = 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 (front == null) rear = 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 == front.
  4. Boş kuyrukta rear eski düğümü gösterir: rear() yanlış değer döndürür, düğüm bellekten atılamaz (tutarsız durum).
  5. Ekle: y1'e push. Çıkar: y2 boşsa y1'i y2'ye aktar, y2'den pop. Her öğe en çok iki kez taşınır → amortize O(1).
  6. Harfleri deque'ye ekle; iki uçtan birer harf çıkarıp karşılaştır. O(n).

Ö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 5: Kuyruklar
Sonraki bölüm: Liste ADT
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.

← → veya boşluk ile gezinin · F: tam ekran · Home/End: ilk/son slayt