Bölüm 7: Kuyruklar

C ile Veri Yapıları ve Algoritmalar
FIFOKuyruk ADTDoğrusal kuyrukDairesel kuyrukBağlı kuyrukDequeZamanlamaBFS
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

FIFO'yu tanımlamak

Kuyruğu Q = {a0, …, an−1} olarak tanımlayıp ön ve arka uçları göstermek.

Doğrusal kuyruğun sorununu görmek

Önde boşalan hücrelerin neden kullanılamadığını ve kaydırmanın maliyetini açıklamak.

Dairesel kuyruk yazmak

% MAX ile sarma yapıp dolu/boş ayrımını üç yöntemle çözmek.

Bağlı kuyruk kurmak

on ve arka işaretçileriyle O(1) ekleme/çıkarma yapmak.

Deque'yi tanımak

İki uçtan ekleme ve çıkarma yapan çift uçlu kuyruğu dairesel dizide gerçeklemek.

Uygulamak

Zaman dilimli iş zamanlama benzetiminde kuyruğu kullanmak; iki yığınla kuyruk kurmak.

Kuyruk ADT

Kuyruk (Queue): İlk Giren İlk Çıkar

Q = {a0, a1, …, an−1} kuyruğunda, yığından farklı olarak işlenecek ilk öğe a0'dır (ön, front); an−1 kuyruğun arkasında (rear) yer alır. Ekleme arkadan, silme önden yapılır: FIFO (first-in-first-out).

ön →← arkaboş kuyruk

Boş kuyruk: ön ve arka aynı yerde.

Aön →← arkaA eklendi

A arkaya eklendi.

ABön →← arkaB eklendi

B arkaya eklendi.

ABCön →← arkaC eklendi

C arkaya eklendi.

ABCDön →← arkaD eklendi

D arkaya eklendi.

BCDön →← arkaA alındı

İlk alınan, ilk eklenen A olur: FIFO. Öndeki hücre boşaldı.

Kuyruk ADT

Soyut Veri Tipi: Kuyruk

İşlemAnlamıHata durumu
kuyruk_olustur(max)En çok max öğelik boş kuyruk—
kuyruk_dolu(Q)Yer kalmadıysa doğru—
kuyruk_ekle(Q, x) · enqueuex'i arkaya eklerDolu ise taşma
kuyruk_bos(Q)Öğe yoksa doğru—
kuyruk_al(Q) · dequeueÖndeki öğeyi çıkarıp döndürürBoş ise alt taşma
kuyruk_on(Q) · peekÖndekini çıkarmadan okurBoş ise hata
Yığın

Tek uç: ekle ve al üstte. LIFO.

Kuyruk

İki uç: ekle arkada, al önde. FIFO.

Kuyruk ADT

Kuyruğun Kullanıldığı Yerler

İşletim sistemi

Çalışmaya hazır süreçler, yazıcı işleri, disk istekleri sırayla kuyrukta bekler.

Tampon (buffer)

Klavye girdisi, ağ paketleri, ses akışı: üretici ile tüketici arasında dairesel tampon.

Genişlik öncelikli arama

BFS ve ağacın seviye sırası gezinmesi: işlenen düğümün çocukları arkaya eklenir, sıradaki düğüm önden alınır.

Benzetim (simülasyon)

Banka gişesi, trafik ışığı, iş zamanlama: gelen müşteri sıraya girer.

Mesaj kuyrukları

Sunucular arasında işler sırayla ve kaybolmadan aktarılır.

Öncelikli kuyruk

“Kuyruk” adını taşır ama FIFO değildir: en öncelikli öğe önce çıkar.

Kuyruk ADT

Arkadan Ekle, Önden Al

Çıkan sıra, giren sırayla aynıdır. Yığında tam tersiydi.

Doğrusal Kuyruk

Dizi ile Doğrusal Kuyruk

#define MAX_QUEUE_SIZE 100
element kuyruk[MAX_QUEUE_SIZE];
int on = -1;        /* ilk öğenin bir öncesi */
int arka = -1;      /* son öğe */

void kuyruk_ekle(int *arka, element item) {
    if (*arka >= MAX_QUEUE_SIZE - 1)   /* yer var mı? */
        kuyruk_dolu();         /* uyar ve çık */
    kuyruk[++*arka] = item;    /* önce artır, sonra yaz */
}
element kuyruk_al(int *on, int arka) {
    if (*on == arka)
        kuyruk_bos();          /* boşsa uyar ve çık */
    return kuyruk[++*on];      /* artır ve öndekini döndür */
}
  • on ilk öğeyi değil, bir öncesini gösterir; arka son öğeyi gösterir.
  • Boş: on == arka (başta ikisi de −1).
  • Dolu: arka == MAX − 1, önde boş yer olsa bile.
  • kuyruk_al arka'yı değiştirmez; bu yüzden onu değerle alır.
  • kuyruk_dolu/kuyruk_bos: yığındaki gibi mesaj + exit.

Doğrusal Kuyruk · Kodu adım adım çalıştır

Kod İzleme: Doğrusal Kuyruk ve “Sahte Doluluk”

0123-1▲ on = -1▲ arka = -1

Başta on = arka = −1: kuyruk boş. MAX_QUEUE_SIZE = 4. Sırayla ekle A, B, C; iki kez al; ekle D, E.

0123-1▲ on = -1▲ arka = -1

-1 ≥ 3? Hayır.

0A123-1▲ on = -1▲ arka = 0

++*arka → 0; kuyruk[0] = A.

0A123-1▲ on = -1▲ arka = 0

0 ≥ 3? Hayır.

0A1B23-1▲ on = -1▲ arka = 1

++*arka → 1; kuyruk[1] = B.

0A1B23-1▲ on = -1▲ arka = 1

1 ≥ 3? Hayır.

0A1B2C3-1▲ on = -1▲ arka = 2

++*arka → 2; kuyruk[2] = C.

0A1B2C3-1▲ on = -1▲ arka = 2

on == arka (-1 == 2)? Hayır, boş değil.

0A1B2C3-1▲ on = 0▲ arka = 2

++*on → 0; kuyruk[0] = A döner. Hücre artık kullanılmıyor.

0A1B2C3-1▲ on = 0▲ arka = 2

on == arka (0 == 2)? Hayır, boş değil.

0A1B2C3-1▲ on = 1▲ arka = 2

++*on → 1; kuyruk[1] = B döner. Hücre artık kullanılmıyor.

0A1B2C3-1▲ on = 1▲ arka = 2

2 ≥ 3? Hayır.

0A1B2C3D-1▲ on = 1▲ arka = 3

++*arka → 3; kuyruk[3] = D.

0A1B2C3D-1▲ on = 1▲ arka = 3

3 ≥ 3 → dolu sayılır! Oysa 0 ve 1 numaralı hücreler boş.

0A1B2C3D-1▲ on = 1▲ arka = 3

kuyruk_dolu(): program sonlanır. Doğrusal kuyruğun sorunu: arka sona dayanınca öndeki boş yerler kullanılamaz.

void kuyruk_ekle(int *arka, element item) {
  if (*arka >= MAX_QUEUE_SIZE - 1)
    kuyruk_dolu();   /* "Kuyruk Dolu!!!", exit */
  kuyruk[++*arka] = item;
}
element kuyruk_al(int *on, int arka) {
  if (*on == arka)
    kuyruk_bos();    /* "Kuyruk Bos!!!", exit */
  return kuyruk[++*on];
}

Doğrusal Kuyruk

Önde Boşalan Yer: Kaydırmak mı?

012C3Don = 1, arka = 3: “dolu”

Önde iki boş hücre var ama arka sona dayandı.

012C3Dher öğeyi başa kaydır

Kaydırma: n öğenin her biri bir kez taşınır → O(n).

0C1D23on = −1, arka = 1

Yer açıldı; ama her “dolu”da bunu yapmak ekleme işlemini O(n) yapar.

Hocadan “Kaydırma! Bilgisayar bilimlerinde en kötü kelime! Yüksek maliyet demek!”

Kaydırmalar aşırı zaman alır; çözüm olarak döngüsel (dairesel) kuyruk geliştirilmiştir: kuyruğun başı ile sonu birleştirilir, önde boşalan yerler arkadaymış gibi kullanılır.

Dairesel Kuyruk

Dairesel (Döngüsel) Kuyruk

0önA1B2C3arka45
  • Dizinin sonu başına bağlanmış gibi düşünülür: 5'ten sonra 0 gelir.
  • İlerleme: arka = (arka + 1) % MAX, on = (on + 1) % MAX.
  • on yine ilk öğenin bir öncesini gösterir; başta on = arka = 0.
  • Boş: on == arka. Dolu: (arka + 1) % MAX == on.
  • Bu yüzden MAX hücrenin biri boş kalır: en çok MAX − 1 öğe.

Dairesel Kuyruk

Sarma (wrap-around): % MAX

arkaarka + 1(arka + 1) % 6Anlamı
344normal ilerleme
455son hücre
560sarma: başa dön
011yeniden normal
Geri gitmek

Bir geri adım: (i − 1 + MAX) % MAX. C'de -1 % 6 −1 verir; + MAX eklemek negatif sonucu önler (deque bunu kullanır).

Öğe sayısı

(arka − on + MAX) % MAX. Örnek: on = 4, arka = 1, MAX = 6 → (1 − 4 + 6) % 6 = 3.

Dairesel Kuyruk

Dairesel Kuyruk Şekli: A…F ile Sarma

0önarka12345012345on = 0 arka = 0başlangıç: on = arka = 0

Boş kuyruk: on == arka = 0.

0önA1arka234501A2345on = 0 arka = 1A eklendi

A eklendi: arka = 1.

0önA1B2arka34501A2B345on = 0 arka = 2B eklendi

B eklendi: arka = 2.

0önA1B2C3arka4501A2B3C45on = 0 arka = 3C eklendi

C eklendi: arka = 3.

0önA1B2C3D4arka501A2B3C4D5on = 0 arka = 4D eklendi

D eklendi: arka = 4.

0önA1B2C3D4E5arka01A2B3C4D5Eon = 0 arka = 5E eklendi

E eklendi: arka = 5.

01önB2C3D4E5arka012B3C4D5Eon = 1 arka = 5A alındı

A alındı: on = 1. Artık 0 ve 1 numaralı hücrelerden biri boş.

F0arka1önB2C3D4E50F12B3C4D5Eon = 1 arka = 0F eklendi: arka 5 → 0 (sarma)

F, arka = (5 + 1) % 6 = 0'a yazıldı: önde boşalan yer arkadaymış gibi kullanıldı. Kuyruk şimdi dolu: (0 + 1) % 6 == on. 6 hücrede 5 öğe.

Dairesel Kuyruk

Dairesel Kuyruk: C Kodu

void kuyruk_ekle(int on, int *arka, element item) {
    *arka = (*arka + 1) % MAX_QUEUE_SIZE; /* ilerlet */
    if (on == *arka) kuyruk_dolu();       /* dolu mu? */
    kuyruk[*arka] = item;                 /* yerleştir */
}
element kuyruk_al(int *on, int arka) {
    if (*on == arka) kuyruk_bos();        /* boş mu? */
    *on = (*on + 1) % MAX_QUEUE_SIZE;     /* ilerlet */
    return kuyruk[*on];                   /* döndür */
}
İnce bir nokta: kuyruk_ekle önce *arka'yı ilerletip sonra doluluğa bakar. kuyruk_dolu programı bitirdiği için sorun olmaz; ama hata döndüren bir sürümde arka bozulur (on'a eşit kalır ve kuyruk boş görünür). Güvenli sürümde yeni konum önce geçici bir değişkende denenir (Horowitz–Sahni de rear'ı geri alır).
Hocadan “Dünyadaki bilgisayar mühendisliği alanında en iyi üniversite hangisi? Harvard mı? Öyleyse “Harvard Üniversitesi’nde okuyorum da, eğer başaramazsam atılacağım. Daha kötü bir okula gitmek zorunda kalacağım.” diye okumak lazım.”

Dairesel Kuyruk · Kodu adım adım çalıştır

Kod İzleme: Güvenli Dairesel Kuyruk

0önarka123401234on = 0 arka = 0öğe sayısı = (0 − 0 + 5) % 5 = 0

MAX_QUEUE_SIZE = 5, on = arka = 0. Ekle A, B, C, D, E; al, al; ekle E, F, G.

0önarka123401234on = 0 arka = 0öğe sayısı = (0 − 0 + 5) % 5 = 0yeni = 1

yeni = (0 + 1) % 5 = 1; 1 ≠ on, yer var.

0önA1arka23401A234on = 0 arka = 1öğe sayısı = (1 − 0 + 5) % 5 = 1

arka = 1, kuyruk[1] = A.

0önA1arka23401A234on = 0 arka = 1öğe sayısı = (1 − 0 + 5) % 5 = 1yeni = 2

yeni = (1 + 1) % 5 = 2; 2 ≠ on, yer var.

0önA1B2arka3401A2B34on = 0 arka = 2öğe sayısı = (2 − 0 + 5) % 5 = 2

arka = 2, kuyruk[2] = B.

0önA1B2arka3401A2B34on = 0 arka = 2öğe sayısı = (2 − 0 + 5) % 5 = 2yeni = 3

yeni = (2 + 1) % 5 = 3; 3 ≠ on, yer var.

0önA1B2C3arka401A2B3C4on = 0 arka = 3öğe sayısı = (3 − 0 + 5) % 5 = 3

arka = 3, kuyruk[3] = C.

0önA1B2C3arka401A2B3C4on = 0 arka = 3öğe sayısı = (3 − 0 + 5) % 5 = 3yeni = 4

yeni = (3 + 1) % 5 = 4; 4 ≠ on, yer var.

0önA1B2C3D4arka01A2B3C4Don = 0 arka = 4öğe sayısı = (4 − 0 + 5) % 5 = 4

arka = 4, kuyruk[4] = D.

0önA1B2C3D4arka01A2B3C4Don = 0 arka = 4öğe sayısı = (4 − 0 + 5) % 5 = 4yeni = 0

yeni = (4 + 1) % 5 = 0 == on → dolu, false döner. arka 4 olarak korunur. 5 hücrede 4 öğe.

0önA1B2C3D4arka01A2B3C4Don = 0 arka = 4öğe sayısı = (4 − 0 + 5) % 5 = 4

on == arka (0 == 4)? Hayır.

01önB2C3D4arka012B3C4Don = 1 arka = 4öğe sayısı = (4 − 1 + 5) % 5 = 3

on = 1, *x = kuyruk[1] = A.

01önB2C3D4arka012B3C4Don = 1 arka = 4öğe sayısı = (4 − 1 + 5) % 5 = 3

on == arka (1 == 4)? Hayır.

012önC3D4arka0123C4Don = 2 arka = 4öğe sayısı = (4 − 2 + 5) % 5 = 2

on = 2, *x = kuyruk[2] = B.

012önC3D4arka0123C4Don = 2 arka = 4öğe sayısı = (4 − 2 + 5) % 5 = 2yeni = 0

yeni = (4 + 1) % 5 = 0 (sarma!); 0 ≠ on, yer var.

E0arka12önC3D40E123C4Don = 2 arka = 0öğe sayısı = (0 − 2 + 5) % 5 = 3

arka = 0, kuyruk[0] = E.

E0arka12önC3D40E123C4Don = 2 arka = 0öğe sayısı = (0 − 2 + 5) % 5 = 3yeni = 1

yeni = (0 + 1) % 5 = 1; 1 ≠ on, yer var.

E0F1arka2önC3D40E1F23C4Don = 2 arka = 1öğe sayısı = (1 − 2 + 5) % 5 = 4

arka = 1, kuyruk[1] = F.

E0F1arka2önC3D40E1F23C4Don = 2 arka = 1öğe sayısı = (1 − 2 + 5) % 5 = 4yeni = 2

yeni = (1 + 1) % 5 = 2 == on → dolu, false döner. arka 1 olarak korunur. 5 hücrede 4 öğe.

bool kuyruk_ekle(int on, int *arka, element x) {
  int yeni = (*arka + 1) % MAX_QUEUE_SIZE;
  if (yeni == on) return false;       /* dolu */
  *arka = yeni;  kuyruk[yeni] = x;
  return true;
}
bool kuyruk_al(int *on, int arka, element *x) {
  if (*on == arka) return false;      /* boş */
  *on = (*on + 1) % MAX_QUEUE_SIZE;
  *x = kuyruk[*on];  return true;
}

Dairesel Kuyruk

Dolu/Boş Ayrımı: Üç Yöntem

YöntemBoşDoluKapasiteBedeli
1. Bir hücre boş bırakmakon == arka(arka+1)%MAX == onMAX − 1Bir hücre israf
2. Sayaç tutmaksayac == 0sayac == MAXMAXHer işlemde sayaç güncellemesi
3. Bayrak tutmakon == arka ve son işlem alon == arka ve son işlem ekleMAXHer işlemde bayrak

Sorunun kökü: yalnız on ve arka ile MAX + 1 farklı doluluk durumu (0, 1, …, MAX öğe) ayırt edilmek istenir, ama iki indisin farkı yalnız MAX farklı değer alabilir. Ya bir durumdan vazgeçilir (yöntem 1) ya da ek bilgi tutulur (yöntem 2, 3).

Sayaçlı sürüm ayrıca öğe sayısını O(1)'de verir ve arka'yı hiç tutmaz: arka = (on + sayac − 1) % KAP.

Dairesel Kuyruk · Kodu adım adım çalıştır

Kod İzleme: Sayaçlı Dairesel Kuyruk

012▲ onsayac = 0KAP = 3

KAP = 3, on = 0, sayac = 0. Ekle 1, 2, 3, 4; al; ekle 4; al.

0112▲ onsayac = 1KAP = 3

Yer var; konum = (on + sayac) % 3 = 0, veri[0] = 1; sayac = 1.

01122▲ onsayac = 2KAP = 3

Yer var; konum = (on + sayac) % 3 = 1, veri[1] = 2; sayac = 2.

011223▲ onsayac = 3KAP = 3

Yer var; konum = (on + sayac) % 3 = 2, veri[2] = 3; sayac = 3.

011223▲ onsayac = 3KAP = 3

sayac == KAP → dolu. Üç hücrenin üçü de dolu: hiç hücre israf edilmedi.

011223▲ onsayac = 3KAP = 3

Boş değil; *x = veri[0] = 1.

01223▲ onsayac = 2KAP = 3

on = 1, sayac = 2.

041223▲ onsayac = 3KAP = 3

Yer var; konum = (on + sayac) % 3 = 0 (sarma), veri[0] = 4; sayac = 3.

041223▲ onsayac = 3KAP = 3

Boş değil; *x = veri[1] = 2.

04123▲ onsayac = 2KAP = 3

on = 2, sayac = 2.

bool ekle(Kuyruk *k, int x) {
  if (k->sayac == KAP) return false;       /* dolu */
  k->veri[(k->on + k->sayac) % KAP] = x;
  k->sayac++;
  return true;
}
bool al(Kuyruk *k, int *x) {
  if (k->sayac == 0) return false;         /* boş */
  *x = k->veri[k->on];
  k->on = (k->on + 1) % KAP;
  k->sayac--;
  return true;
}

Bağlı Kuyruk

Bağlı Liste ile Kuyruk

*on*arka102030NULL

on ilk düğümü, arka son düğümü gösterir; son düğümün bağı NULL.

  • Ekleme arkaya: arka->bag = yeni; arka = yeni; → O(1).
  • Silme önden: on = on->bag; → O(1).
  • Tersi (önden ekle, arkadan sil) olmaz: tek yönlü bağlıda sondan silmek için sondan bir öncekini bulmak O(n).
Hocadan “– Ben iki derstir ne anlatıyorum? Dersi bıraktım, C anlattım, pointerları anlattım. Çok iyi bilinen pointerları bir de ben anlattım. – Ben pointerları çok iyi biliyorum, ama hoca bir şey diyor, yine de anlamadım. – Olabilir.”

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

Kod İzleme: Bağlı kuyruk_ekle

*onNULL*arkaNULL

Boş kuyruk: on = arka = NULL. Sırayla ekle 10, 20, 30.

*onNULL*arkaNULL?NULLyeni

malloc: yeni düğüm ayrıldı.

*onNULL*arkaNULL10NULLyeni

deger = 10, bag = NULL (yeni son düğüm olacak).

*on*arkaNULL10NULLyeni

*arka == NULL: kuyruk boştu, yeni düğüm aynı zamanda ön olur: *on = yeni.

*on*arka10NULLyeni

*arka = yeni: arka 10'u gösterir.

*on*arka10?NULLyeni

malloc: yeni düğüm ayrıldı.

*on*arka1020NULLyeni

deger = 20, bag = NULL (yeni son düğüm olacak).

*on*arka1020NULLyeni

Kuyruk boş değil: (*arka)->bag = yeni, 10 artık 20'u gösterir.

*on*arka1020NULLyeni

*arka = yeni: arka 20'u gösterir.

*on*arka1020?NULLyeni

malloc: yeni düğüm ayrıldı.

*on*arka102030NULLyeni

deger = 30, bag = NULL (yeni son düğüm olacak).

*on*arka102030NULLyeni

Kuyruk boş değil: (*arka)->bag = yeni, 20 artık 30'u gösterir.

*on*arka102030NULLyeni

*arka = yeni: arka 30'u gösterir.

*on*arka102030NULL

Kuyruk: 10 → 20 → 30. Her ekleme sabit iş: O(1); arka olmasaydı sonu bulmak O(n) olurdu.

bool kuyruk_ekle(dugum **on, dugum **arka, int x) {
  dugum *yeni = malloc(sizeof *yeni);
  if (yeni == NULL) return false;
  yeni->deger = x;  yeni->bag = NULL;
  if (*arka == NULL) *on = yeni;   /* boş kuyruk */
  else (*arka)->bag = yeni;        /* sona bağla */
  *arka = yeni;
  return true;
}

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

Kod İzleme: Bağlı kuyruk_al

*on*arka1020NULL

Kuyruk: 10 → 20. Üç kez kuyruk_al.

*on*arka1020NULLeski

eski = *on.

*on*arka1020NULLeski

*x = 10; *on = eski->bag → düğüm(20).

*on*arka1020NULLeski

*on ≠ NULL: arka değişmez.

*on*arka1020NULL

free(eski); main 10 yazar.

*on*arka1020NULLeski

eski = *on.

*onNULL*arka1020NULLeski

*x = 20; *on = eski->bag → NULL.

*onNULL*arkaNULL1020NULLeski

*on == NULL: son öğe de çıktı; *arka = NULL yapılmazsa arka serbest bırakılacak düğümü göstermeye devam eder (sarkan işaretçi)!

*onNULL*arkaNULL1020NULL

free(eski); main 20 yazar.

*onNULL*arkaNULL1020NULL

eski == NULL → boş, false döner.

bool kuyruk_al(dugum **on, dugum **arka, int *x) {
  dugum *eski = *on;
  if (eski == NULL) return false;      /* boş */
  *x = eski->deger;
  *on = eski->bag;
  if (*on == NULL) *arka = NULL;  /* son öğe gitti */
  free(eski);
  return true;
}

Bağlı Kuyruk

Kuyruk Gerçeklemelerinin Karşılaştırması

ÖlçütDoğrusal diziDairesel diziBağlı liste
ekle / alO(1), ama “sahte dolu”O(1)O(1) (malloc/free)
Kaydırma gerekir mi?Evet: O(n)HayırHayır
KapasiteMAXMAX − 1 (ya da sayaçla MAX)Bellek kadar
Ek bellekYokYokÖğe başına bir işaretçi
Özel durumon/arka sona dayanırSarma ve dolu/boş ayrımıBoşalınca arka = NULL

Pratikte en çok dairesel dizi (sabit kapasiteli tampon) ve büyüyen dairesel dizi kullanılır; bağlı kuyruk, öğeler zaten düğüm olarak yaşıyorsa ya da üst sınır bilinmiyorsa tercih edilir.

Deque ve Uygulamalar

Çift Uçlu Kuyruk (Deque)

03152103204başa eklebaştan alsona eklesondan al
  • Deque (double-ended queue): ekleme ve silme her iki uçta, hepsi O(1).
  • Yalnız bir uç kullanılırsa yığın, bir uçtan ekleyip öbüründen alırsa kuyruk olur.
  • Kısıtlı türler: girdisi kısıtlı (ekleme tek uçta), çıktısı kısıtlı (silme tek uçta).
  • Kullanım: kayan pencere maksimumu, iş çalma (work stealing), geri al + yinele geçmişi.

Deque ve Uygulamalar · Kodu adım adım çalıştır

Kod İzleme: Dairesel Dizide Deque

100on201234010120234on = 0 sayac = 2 son = (on + sayac − 1) % 5 = 1

KAP = 5; sona_ekle ile 10 ve 20 kondu. Şimdi başa 5, başa 3; sonra iki kez sondan al.

100on201234010120234on = 0 sayac = 2 son = (on + sayac − 1) % 5 = 1

sayac = 2 < 5, yer var.

100201234on010120234on = 4 sayac = 2 son = (on + sayac − 1) % 5 = 0

on = (0 − 1 + 5) % 5 = 4: 0'ın bir gerisi dizinin sonu.

1002012354on0101202345on = 4 sayac = 3 son = (on + sayac − 1) % 5 = 1

veri[4] = 5; sayac = 3.

1002012354on0101202345on = 4 sayac = 3 son = (on + sayac − 1) % 5 = 1

sayac = 3 < 5, yer var.

10020123on540101202345on = 3 sayac = 3 son = (on + sayac − 1) % 5 = 0

on = (4 − 1 + 5) % 5 = 3.

100201233on5401012023345on = 3 sayac = 4 son = (on + sayac − 1) % 5 = 1

veri[3] = 3; sayac = 4.

100201233on5401012023345on = 3 sayac = 4 son = (on + sayac − 1) % 5 = 1

Son konum = (3 + 4 − 1) % 5 = 1; *x = 20.

1001233on54010123345on = 3 sayac = 3 son = (on + sayac − 1) % 5 = 0

sayac = 3. (on değişmez.)

1001233on54010123345on = 3 sayac = 3 son = (on + sayac − 1) % 5 = 0

Son konum = (3 + 3 − 1) % 5 = 0; *x = 10.

01233on540123345on = 3 sayac = 2 son = (on + sayac − 1) % 5 = 4

sayac = 2. (on değişmez.)

bool basa_ekle(Deque *d, int x) {
  if (d->sayac == KAP) return false;
  d->on = (d->on - 1 + KAP) % KAP;    /* bir geri */
  d->veri[d->on] = x;  d->sayac++;
  return true;
}
bool sondan_al(Deque *d, int *x) {
  if (d->sayac == 0) return false;
  *x = d->veri[(d->on + d->sayac - 1) % KAP];
  d->sayac--;  return true;
}

Deque ve Uygulamalar

Tek Dizide Birden Çok Kuyruk

Kesimlere bölme

Çoklu yığında olduğu gibi tek boyutlu bir memory[] dizisi kesimlere bölünür; her kuyruğun on[i], arka[i] ve sinir[i] değerleri tutulur.

İki kuyruk

Yığındaki “uçlardan büyütme” hilesi kuyrukta işe yaramaz: kuyruk iki ucunda da hareket eder, kesim içinde kayar.

Kesim içinde dairesel

Her kesim kendi başına dairesel kuyruk gibi yönetilirse kaydırma gerekmez; ama bir kesim dolunca yine yer açmak (O(m)) gerekir.

Günümüzde

Genellikle her kuyruk için ayrı büyüyen dizi ya da bağlı liste tercih edilir.

Deque ve Uygulamalar · Kodu adım adım çalıştır

Kod İzleme: Round Robin Zamanlama

kuyruk:P0P1P2kalan: P0=5 P1=2 P2=4zaman çizelgesi:01234567891011

Üç süreç: P0 = 5, P1 = 2, P2 = 4 birim iş. Zaman dilimi Q = 2 (round robin).

kuyruk:P1P2kalan: P0=3 P1=2 P2=4zaman çizelgesi:P001234567891011

Öndeki P0 alınır, 2 birim çalışır; t = 2.

kuyruk:P1P2P0kalan: P0=3 P1=2 P2=4zaman çizelgesi:P001234567891011

P0'nin 3 birimi kaldı → kuyruğun sonuna geri döner.

kuyruk:P2P0kalan: P0=3 P1=0 P2=4zaman çizelgesi:P0P101234567891011

Öndeki P1 alınır, 2 birim çalışır; t = 4.

kuyruk:P2P0kalan: P0=3 P1=0 P2=4zaman çizelgesi:P0P101234567891011

P1 bitti; kuyruğa dönmez.

kuyruk:P0kalan: P0=3 P1=0 P2=2zaman çizelgesi:P0P1P201234567891011

Öndeki P2 alınır, 2 birim çalışır; t = 6.

kuyruk:P0P2kalan: P0=3 P1=0 P2=2zaman çizelgesi:P0P1P201234567891011

P2'nin 2 birimi kaldı → kuyruğun sonuna geri döner.

kuyruk:P2kalan: P0=1 P1=0 P2=2zaman çizelgesi:P0P1P2P001234567891011

Öndeki P0 alınır, 2 birim çalışır; t = 8.

kuyruk:P2P0kalan: P0=1 P1=0 P2=2zaman çizelgesi:P0P1P2P001234567891011

P0'nin 1 birimi kaldı → kuyruğun sonuna geri döner.

kuyruk:P0kalan: P0=1 P1=0 P2=0zaman çizelgesi:P0P1P2P0P201234567891011

Öndeki P2 alınır, 2 birim çalışır; t = 10.

kuyruk:P0kalan: P0=1 P1=0 P2=0zaman çizelgesi:P0P1P2P0P201234567891011

P2 bitti; kuyruğa dönmez.

kuyruk:kalan: P0=0 P1=0 P2=0zaman çizelgesi:P0P1P2P0P2P001234567891011

Öndeki P0 alınır, 1 birim çalışır; t = 11.

kuyruk:kalan: P0=0 P1=0 P2=0zaman çizelgesi:P0P1P2P0P2P001234567891011

P0 bitti; kuyruğa dönmez.

/* k: süreç numaraları kuyruğu; Q: zaman dilimi */
while (kuyruk_al(&k, &p)) {
  int dilim = kalan[p] < Q ? kalan[p] : Q;
  zaman += dilim;
  kalan[p] -= dilim;
  if (kalan[p] > 0)
    kuyruk_ekle(&k, p);           /* sona geri */
  else
    printf("P%d biter: t=%d\n", p, zaman);
}

Deque ve Uygulamalar · Kodu adım adım çalıştır

Kod İzleme: İki Yığınla Kuyruk

giriscikisaktar (ters çevirir)

Kuyruk iki yığından oluşur. Ekle 1, 2, 3; al; ekle 4; üç kez al.

1giriscikisaktar (ters çevirir)

ekle(1): giriş yığınına push, O(1).

12giriscikisaktar (ters çevirir)

ekle(2): giriş yığınına push, O(1).

123giriscikisaktar (ters çevirir)

ekle(3): giriş yığınına push, O(1).

123giriscikisaktar (ters çevirir)

Çıkış yığını boş → girişteki her şey aktarılacak.

12giris3cikisaktar (ters çevirir)

3 giriş'ten pop, çıkış'a push.

1giris32cikisaktar (ters çevirir)

2 giriş'ten pop, çıkış'a push.

giris321cikisaktar (ters çevirir)

1 giriş'ten pop, çıkış'a push.

giris32cikisaktar (ters çevirir)

Çıkış yığınının üstü 1: en eski öğe. FIFO sağlandı.

4giris32cikisaktar (ters çevirir)

ekle(4): giriş yığınına push, O(1).

4giris3cikisaktar (ters çevirir)

Çıkış yığınının üstü 2: en eski öğe. FIFO sağlandı.

4giriscikisaktar (ters çevirir)

Çıkış yığınının üstü 3: en eski öğe. FIFO sağlandı.

4giriscikisaktar (ters çevirir)

Çıkış yığını boş → girişteki her şey aktarılacak.

giris4cikisaktar (ters çevirir)

4 giriş'ten pop, çıkış'a push.

giriscikisaktar (ters çevirir)

Çıkış yığınının üstü 4: en eski öğe. FIFO sağlandı. Her öğe en çok bir kez aktarıldı → amortize O(1).

void ekle(Kuyruk2 *q, int x) {
  push(&q->giris, x);
}
bool al(Kuyruk2 *q, int *x) {
  int t;
  if (yigit_bos(&q->cikis))       /* çıkış boşsa */
    while (pop(&q->giris, &t))    /* hepsini aktar */
      push(&q->cikis, t);
  return pop(&q->cikis, x);
}

Deque ve Uygulamalar

Hangisi Uygun?

ProblemYapı
Yazıcıya gönderilen belgeler
gösterKuyruk: gönderiliş sırasıyla basılır.
Metin düzenleyicide Ctrl+Z
gösterYığın: en son işlem ilk geri alınır.
Ağacı seviye seviye yazdırmak
gösterKuyruk (BFS).
İç içe parantezlerin denetimi
gösterYığın: kapanan, en son açılanla eşleşir.
Fonksiyon çağrıları
gösterYığın (çağrı yığını).
Acil serviste en ağır hasta önce
gösterÖncelikli kuyruk: FIFO değil!
Hocadan “Kuyruk diye bir şey biliniyor, bunda da kuyruk kelimesi geçiyor. Ama bildiğimiz kuyrukta sondan eklenir, baştan alınır. Bu ise ondan farklı…”

Özet

Karmaşıklık Özeti

İşlemDoğrusal diziDairesel diziBağlıDeque (dairesel)
ekleO(1)*O(1)O(1)O(1) iki uçta
alO(1)O(1)O(1)O(1) iki uçta
ön / boş / doluO(1)O(1)O(1)O(1)
öğe sayısıarka − on(arka−on+MAX)%MAXO(n) ya da sayaçsayac
bellekMAXMAXn · (öğe + işaretçi)KAP

* Doğrusal kuyrukta ekleme O(1)’dir ama “sahte dolu” durumunda kaydırma yapılırsa O(n) olur. Round robin benzetimi ve seviye sırası gezinme n öğe için O(n) kuyruk işlemi yapar.

Özet

Sık Yapılan Hatalar

1Doğrusal kuyrukta arka == MAX−1'i gerçek doluluk sanmak.
2Dairesel kuyrukta % MAX'ı unutup dizi dışına yazmak.
3Önce arka'yı ilerletip doluysa geri almamak: kuyruk boş görünür.
4Geri adımda (i − 1) % MAX yazmak: C'de negatif sonuç çıkar.
5Bağlı kuyruktan son öğe alınınca arka = NULL yapmamak.
6Boş kuyruğa ilk eklemede on'u güncellemeyi unutmak.

Özet

Alıştırmalar

  1. MAX = 6, on = 4, arka = 1: kaç öğe var, hangi indislerde?
  2. Bayrak yöntemiyle dairesel kuyruğun ekle ve al yordamlarını yaz.
  3. İki yığınla bir kuyruk yaz; al işleminin amortize maliyeti nedir?
  4. Kuyruğu yalnız arka işaretçili dairesel bağlı listeyle yaz.
  5. Round robin örneğini Q = 3 ile çalıştır: süreçler ne zaman biter?
İpuçları / cevaplar
  1. (1 − 4 + 6) % 6 = 3 öğe: indis 5, 0, 1 (on'un bir sonrasından arka'ya kadar).
  2. Boş: on == arka && !son_ekleme; dolu: on == arka && son_ekleme. ekle'de bayrak = 1, al'da bayrak = 0.
  3. Giriş yığınına push; al'da çıkış yığını boşsa girişi tümüyle aktar, sonra pop. Her öğe en çok iki kez taşınır → amortize O(1).
  4. arka->bag ön düğümdür. Ekleme: yeni->bag = arka->bag; arka->bag = yeni; arka = yeni. Alma: on = arka->bag.
  5. P0: 0–3, P1: 3–5 (biter t=5), P2: 5–8, P0: 8–10 (biter t=10), P2: 10–11 (biter t=11).

Özet

Bölüm Özeti

FIFO

Ekleme arkada, alma önde; ilk giren ilk çıkar.

Doğrusal kuyruk

Basit ama arka sona dayanınca öndeki boş yer kullanılamaz; kaydırma pahalı.

Dairesel kuyruk

% MAX ile sarma; boş on == arka, dolu (arka+1)%MAX == on.

Dolu/boş ayrımı

Bir hücre boş bırakılır ya da sayaç veya bayrak tutulur.

Bağlı kuyruk

on ve arka işaretçisi; son öğe çıkınca arka = NULL.

Uygulamalar

Deque, iş zamanlama, tampon, iki yığınla kuyruk, BFS.

Özet

Kaynaklar ve Devam

  • E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, 2. bs., Bölüm 3.3 ve 4.3.
  • T. H. Cormen vd., Introduction to Algorithms, 3. bs., Bölüm 10.1 (kuyruklar, deque alıştırmaları).
  • R. Sedgewick, Algorithms in C, Kısım 1–4, Bölüm 4.

Son

Bölüm 7: Kuyruklar
Sonraki bölüm: Bölüm 8: Yığın ve Kuyruk Uygulamaları · Ders sayfası
1 / 1 C ile Veri Yapıları