SEN2211 · 8. hafta · Queues II: Circular and Priority Queues

Kuyruk II: dairesel indeks hesabı, deque, öncelik kuyruğu

count tutmayan dairesel kuyruğun boş/dolu formülleri, iki uçlu kuyruk (deque), öncelik kuyruğunun (priority queue) mantığı ve kuyrukla simülasyon. Sınavda formülle indeks bulma ve simülasyon izi sorulur.

circular queueone empty slotmodular arithmeticdequedouble-ended queueaddFirstpollLastpriority queuemin-priorityPriorityQueueComparatorround-robin schedulingsimulation

Geçen hafta kuyruğun iki temel gerçeklemesini yazdık. Bu hafta üç genişleme var. Önce dairesel dizinin count tutmayan versiyonunu ve indeks formüllerini çalışıyoruz, çünkü sınavda “front = 4, rear = 1, kaç eleman var?” gibi saf formül soruları çıkıyor. Sonra iki uçtan da işlem yapılabilen deque’yi, ardından elemanları öncelik sırasına göre çıkaran öncelik kuyruğunu (priority queue) görüyoruz. Son olarak kuyruğun asıl kullanım alanı olan simülasyonları izliyoruz.

Altın kurallar:

  1. Dairesel indekste her şey % cap. Geriye giderken önce + cap.
  2. count yoksa bir hücre boş kalır. Boş: front == rear. Dolu: (rear + 1) % cap == front.
  3. Deque iki uçlu kuyruktur. Hem yığın hem kuyruk gibi kullanılabilir.
  4. Öncelik kuyruğu FIFO değildir. Java’da en küçük önce çıkar.

Dairesel kuyruk: count olmadan

Geçen hafta front == rear durumunun hem boş hem dolu anlamına gelebildiğini gördük ve bir count alanı ekledik. Diğer yaygın çözüm: kuyruğun hiçbir zaman tamamen dolmasına izin vermemek. Bir hücre hep boş kalır, böylece:

DurumKoşul
Boşfront == rear
Dolu(rear + 1) % cap == front
Eleman sayısı(rear - front + cap) % cap
i. eleman (0’dan)data[(front + i) % cap]
En fazla elemancap - 1

rear yine bir sonraki boş yerdir. Kapasite 5 ile:

SlotQueue s = new SlotQueue(5);
for (int i = 1; i <= 4; i++) s.enqueue(i * 10);
System.out.println(s.raw() + " full=" + s.isFull());
s.dequeue(); s.dequeue(); s.enqueue(50); s.enqueue(60);
System.out.println(s.raw() + " full=" + s.isFull());
[10, 20, 30, 40, 0] front=0 rear=4 size=4 full=true
[60, 20, 30, 40, 50] front=2 rear=1 size=4 full=true

İlk satırda dizide hâlâ bir boş hücre var ama kuyruk dolu: (4 + 1) % 5 = 0 = front. İkinci satırda 50 indeks 4’e, 60 indeks 0’a yazıldı; rear 1, front 2. Boyut (1 − 2 + 5) % 5 = 4.

TUZAK · Neden + cap? rear sarıp front’un gerisine düştüğünde rear - front negatif olur. Java’da % negatif sonucu negatif bırakır: (1 − 4) % 6 = −3. Önce cap ekleyince (1 − 4 + 6) % 6 = 3 olur, doğru boyut.

Formül soruları için çözüm kalıbı

cap = 6, front = 4, rear = 1
boyut    = (1 - 4 + 6) % 6 = 3
elemanlar: indeks 4, 5, 0
dolu mu? (1 + 1) % 6 = 2 ≠ 4 → hayır
sonraki enqueue indeks 1'e yazar, rear 2 olur

Deque (double-ended queue)

İki ucundan da ekleme ve çıkarma yapılabilen kuyruk. “Deck” diye okunur.

İşlemÖn uçArka uç
EkleaddFirst(x) / offerFirst(x)addLast(x) / offerLast(x)
ÇıkarremoveFirst() / pollFirst()removeLast() / pollLast()
BakpeekFirst()peekLast()

Sadece addLast + pollFirst kullanırsan kuyruk, sadece addFirst + pollFirst kullanırsan yığın olur. Java’nın ArrayDeque sınıfı tam olarak budur: dairesel dizi üzerinde bir deque.

Deque<Integer> d = new ArrayDeque<>();
d.addFirst(1); d.addLast(2); d.addFirst(3); d.addLast(4);
System.out.println(d);
System.out.println(d.pollLast() + " " + d.pollFirst());
d.offerFirst(9);
System.out.println(d + " peekLast=" + d.peekLast());
[3, 1, 2, 4]
4 3
[9, 1, 2] peekLast=2

Deque’yi dairesel diziyle yazmak

front ve count tutulur. Sona ekleme (front + count) % cap hücresine yazar. Başa ekleme front’u bir geri alır:

void addFirst(int x) {
    front = (front - 1 + data.length) % data.length;
    data[front] = x;
    count++;
}

void addLast(int x) {
    data[(front + count) % data.length] = x;
    count++;
}

int removeLast() {
    int idx = (front + count - 1) % data.length;
    count--;
    return data[idx];
}

Kapasite 6 ile addLast(1), addLast(2), addFirst(3), addFirst(4):

[1, 2, 0, 0, 4, 3] front=4 count=4

front 0’dan geri sarınca 5’e (3 yazıldı), sonra 4’e (4 yazıldı). Mantıksal sıra front’tan: 4 3 1 2.

Öncelik kuyruğu (priority queue)

Hastane acil servisi gibi: gelme sırasına değil, aciliyete göre çağrılırsın. Her elemanın bir önceliği vardır; remove her zaman en yüksek öncelikli elemanı verir. Java’da “en yüksek öncelik” varsayılan olarak en küçük değer demektir.

PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int x : new int[]{5, 1, 8, 3, 9, 2}) pq.add(x);
System.out.println("toString: " + pq);
System.out.println(pq.poll() + " " + pq.poll() + " " + pq.poll() + " size=" + pq.size());
toString: [1, 3, 2, 5, 9, 8]
1 2 3 size=3

toString çıktısı sıralı değil. PriorityQueue içeride bir heap tutar; garanti sadece en öndekinin en küçük olmasıdır. En büyük önce çıksın istiyorsan bir Comparator ver:

PriorityQueue<Integer> mx = new PriorityQueue<>(Comparator.reverseOrder());
PriorityQueue<String> byLen = new PriorityQueue<>((a, b) -> a.length() - b.length());

Gerçekleme seçenekleri

YapıinsertremoveMinpeekMin
Sırasız dizi/listeO(1)O(n)O(n)
Sıralı bağlı listeO(n)O(1)O(1)
Sıralı diziO(n) (kaydırma)O(1) (sondan, azalan sıralıysa)O(1)
Binary heapO(log n)O(log n)O(1)

Bu derste ilk ikisini yazabilmen yeterli. Sıralı liste versiyonu 3. haftadaki insertSorted + removeFirst’ten ibarettir. Heap bir sonraki dersin (SEN2212) konusu.

Kuyrukla simülasyon

Kuyruğun asıl gücü “sırası gelen işlenir, işi bitmeyen sona geçer” senaryolarıdır.

Round-robin zamanlama

İşletim sistemi işlemciyi işlere sırayla, her birine sabit bir süre dilimi (quantum) vererek dağıtır. Süre dilimi 2, işler A (5 birim), B (2), C (4):

while (!names.isEmpty()) {
    String p = names.remove();
    int run = Math.min(2, left.get(p));
    time += run;
    left.put(p, left.get(p) - run);
    if (left.get(p) > 0) names.add(p);   // bitmediyse sona
}
A[0-2] B[2-4] (B done at 4) C[4-6] A[6-8] C[8-10] (C done at 10) A[10-11] (A done at 11)

Hot potato (sıcak patates)

Herkes çember halinde. Patates k kez el değiştirir, elinde kalan oyundan çıkar. Kuyrukla: k kez add(remove()), sonra bir remove(). 3. haftadaki Josephus probleminin kuyruk versiyonu.

out: Deniz Cem Ece Bora | winner: Ali

Kuyruğu bozmadan gezmek

Kuyrukta indeksle erişim yok. İçeriği değiştirmeden toplamı almak için size kez döndür:

static int sumQueue(Queue<Integer> q) {
    int sum = 0, n = q.size();
    for (int i = 0; i < n; i++) { int x = q.remove(); sum += x; q.add(x); }
    return sum;
}

[3, 6, 9, 12] için 30 döner ve kuyruk aynı sırada kalır. Döngü sınırını önceden n değişkenine almak önemli; i < q.size() de bu örnekte çalışır çünkü boyut değişmiyor, ama eleman çıkaran varyasyonlarda yanıltır.

Sık yapılan hatalar

YanlışDoğrusu
Boyut rear - front(rear - front + cap) % cap
Count’suz kuyrukta cap kadar elemanEn fazla cap − 1
front = (front - 1) % cap(front - 1 + cap) % cap
Dolu koşulu rear == frontBoşluk koşulu o; dolu: (rear + 1) % cap == front
PriorityQueue FIFO’durÖnceliğe göre; Java’da en küçük önce
System.out.println(pq) sıralı yazarHeap sırası; poll ile çek
En büyük önce için ayrı sınıf gerekirComparator.reverseOrder()
Simülasyonda biten işi tekrar kuyruğa koymakSadece kalanı > 0 olanlar sona

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

A circular queue (no count field, one slot always left empty) has capacity 6, front = 4, rear = 1 (rear = next free slot). How many elements are in the queue?

Cevabı göster
3

Boyut formülü (rear−front+cap)%cap=(1−4+6)%6=3(rear - front + cap) \% cap = (1 - 4 + 6) \% 6 = 3. Elemanlar 4, 5, 0 numaralı hücrelerde. + cap negatif sonucu önler.

Soru 2orta

Same design, capacity 5, front = 2, rear = 1. Is the queue full? How many elements can this queue hold at most?

Cevabı göster
Yes, full; at most 4

Dolu koşulu (rear+1)%cap==front(rear + 1) \% cap == front: (1+1)%5=2(1 + 1) \% 5 = 2, front da 2: dolu. Boş koşulu front == rear olduğu için bir hücre hep boş bırakılır; 5 hücrede en fazla 4 eleman. Bu, count tutmadan boşu ve doluyu ayırt etmenin bedeli.

Soru 3kolay

In a circular queue with capacity 6 and front = 4, at which array index is the third element from the front (i = 2)?

Cevabı göster
0

i. eleman (front+i)%cap(front + i) \% cap hücresindedir: (4+2)%6=0(4 + 2) \% 6 = 0. Önden: indeks 4, 5, 0.

Soru 4orta

A circular-array deque does front = (front - 1) % cap in addFirst. With cap = 8 and front = 0, what goes wrong, and what is the correct formula?

Cevabı göster
Java gives -1 (index out of bounds); use (front - 1 + cap) % cap = 7

Java'da % sonucunun işareti soldaki sayıdan gelir: (0 − 1) % 8 = −1. data[-1] ArrayIndexOutOfBoundsException fırlatır. Çıkarmadan önce cap ekle: (0 − 1 + 8) % 8 = 7. Geri sarma her zaman + cap ister.

Soru 5orta

What is printed?

Deque<Integer> d = new ArrayDeque<>();
d.addFirst(1); d.addLast(2); d.addFirst(3); d.addLast(4);
System.out.println(d);
System.out.println(d.pollLast() + " " + d.pollFirst());
d.offerFirst(9);
System.out.println(d + " peekLast=" + d.peekLast());
Cevabı göster
[3, 1, 2, 4] / 4 3 / [9, 1, 2] peekLast=2

[1] → [1,2] → [3,1,2] → [3,1,2,4]. pollLast 4, pollFirst 3: [1,2]. offerFirst(9): [9,1,2]. Son eleman 2. Deque iki uçtan da ekleyip çıkarır; toString baştan sona yazar.

Soru 6zor

Write static boolean isPalindrome(String s) using a Deque<Character>: compare characters from both ends.

Cevabı göster
Add all chars with addLast; while size > 1, compare pollFirst and pollLast.

static boolean isPalindrome(String s) {
    Deque<Character> d = new ArrayDeque<>();
    for (char c : s.toCharArray()) d.addLast(c);
    while (d.size() > 1) {
        if (d.pollFirst() != d.pollLast()) return false;
    }
    return true;
}
"racecar" → true, "radix" → false (r = x değil), "abba" → true, tek harfli "a" → true (döngü hiç çalışmaz). Not: Character nesneleri != ile karşılaştırılırken Java küçük karakterleri önbellekten verdiği için burada çalışır; güvenli yol !d.pollFirst().equals(d.pollLast()).

Soru 7kolay

What is printed?

PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int x : new int[]{5, 1, 8, 3, 9, 2}) pq.add(x);
System.out.println(pq.poll() + " " + pq.poll() + " " + pq.poll() + " size=" + pq.size());
Cevabı göster
1 2 3 size=3

Java'nın PriorityQueue'su varsayılan olarak en küçük önceliği önce verir (min-priority). Ekleme sırası önemli değil: poll sırasıyla 1, 2, 3 çıkar, 3 eleman kalır (5, 8, 9).

Soru 8zor

Same queue right after the six add calls: what does System.out.println(pq) print, and why is it not sorted?

Cevabı göster
[1, 3, 2, 5, 9, 8]

PriorityQueue içeride bir heap (yığın ağacı, SEN2212 konusu) tutar. toString diziyi iç sırasıyla yazar; sadece ilk elemanın en küçük olduğu garanti. Sıralı görmek istiyorsan poll ile tek tek çek.

Soru 9orta

What is printed?

PriorityQueue<Integer> mx = new PriorityQueue<>(Comparator.reverseOrder());
mx.add(4); mx.add(10); mx.add(7);
System.out.println(mx.peek());
mx.poll(); mx.add(6);
while (!mx.isEmpty()) System.out.print(mx.poll() + " ");
Cevabı göster
10 / 7 6 4

reverseOrder ile en büyük önce gelir (max-priority). peek 10. poll 10'u çıkarır, 6 eklenir: {4, 7, 6}. Büyükten küçüğe: 7 6 4.

Soru 10kolay

A priority queue (smallest first) is implemented with (a) an unsorted linked list, (b) a sorted linked list. Give the time complexity of insert and removeMin for each.

Cevabı göster
(a) insert O(1), removeMin O(n); (b) insert O(n), removeMin O(1)

Sırasız listede başa eklemek O(1), ama en küçüğü bulmak için hepsini gezmek gerekir: O(n). Sıralı listede eklerken doğru yeri aramak O(n) (3. haftadaki insertSorted), en küçük her zaman head'de: O(1). Heap ile ikisi de O(log n) olur.

Soru 11zor

Round-robin CPU scheduling with time quantum 2. Jobs enter the queue in order A (needs 5 units), B (2), C (4). A job runs for min(2, remaining) units; if unfinished it goes to the back. At what time does each job finish?

Cevabı göster
B at 4, C at 10, A at 11

zaman   çalışan   kalan sonrası   kuyruk
0-2     A         A:3             B C A
2-4     B         B:0 bitti (4)   C A
4-6     C         C:2             A C
6-8     A         A:1             C A
8-10    C         C:0 bitti (10)  A
10-11   A         A:0 bitti (11)
Program çıktısı: A[0-2] B[2-4] (B done at 4) C[4-6] A[6-8] C[8-10] (C done at 10) A[10-11] (A done at 11).

Soru 12zor

Hot potato: the queue holds Ali Bora Cem Deniz Ece (front first). Each round, move the front person to the back 3 times, then remove the front person. Who is removed in order, and who wins?

Cevabı göster
Deniz, Cem, Ece, Bora; winner Ali

Tur 1: Ali, Bora, Cem arkaya → Deniz Ece Ali Bora Cem, Deniz çıkar. Tur 2: Ece, Ali, Bora arkaya → Cem Ece Ali Bora, Cem çıkar. Tur 3: üç kişi, üç kaydırma aynı sıraya döner: Ece Ali Bora, Ece çıkar. Tur 4: Ali Bora; Ali, Bora, Ali arkaya → Bora Ali, Bora çıkar. Kalan: Ali.

Doğrulama: Formüller, dairesel deque, ArrayDeque ve PriorityQueue davranışları ve iki simülasyon verify/SEN2211/W08.java içinde JDK ile çalıştırıldı (java W08.java). Java'nın negatif mod sonucu (-1) ve PriorityQueue'nun toString çıktısı da programdan alındı.

Deftere not kartı

COUNT'SUZ DAİRESEL KUYRUK (bir hücre boş): boş: front == rear dolu: (rear + 1) % cap == front kapasite = cap - 1 boyut = (rear - front + cap) % cap i. eleman: data[(front + i) % cap] Geri sarma: (i - 1 + cap) % cap (Java'da (0-1)%8 = -1 !) DEQUE (iki uçlu): addFirst/addLast, pollFirst/pollLast, peekFirst/peekLast hepsi O(1) deque hem yığın hem kuyruk olabilir; palindrom: iki uçtan karşılaştır ÖNCELİK KUYRUĞU: sıraya göre değil, ÖNCELİĞE göre çıkar Java PriorityQueue: en KÜÇÜK önce; en büyük için new PriorityQueue<>(Comparator.reverseOrder()) toString sıralı DEĞİL (heap sırası); sıralı görmek için poll sırasız liste: insert O(1), removeMin O(n) | sıralı liste: insert O(n), removeMin O(1) | heap: ikisi O(log n) SİMÜLASYON: kuyruktan al, işle, bitmediyse arkaya geri koy (round-robin, hot potato) İz tablosu: zaman | çalışan | kalan | kuyruk