Bölüm 8: Yığın ve Kuyruk Uygulamaları

C ile Veri Yapıları ve Algoritmalar
LabirentGeri izlemeİşlem önceliğiInfix / postfix / prefixPostfix hesaplamaParantez denetimi
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

Geri izlemeyi kodlamak

Labirentte yığınla yol arayıp çıkmazdan geri dönmeyi izlemek.

En kötü durumu kurmak

Bir algoritma için en kötü veri grubunu tasarlayıp analiz etmek.

Gösterimleri çevirmek

Infix, prefix ve postfix arasında elle ve yığınla çeviri yapmak.

Postfix hesaplamak

eval yordamını işlenen/işleç yığınıyla adım adım çalıştırmak.

Önceliği kodlamak

isp/icp tablosuyla infix → postfix çeviren yordamı yazmak.

Yığını genellemek

Parantez denetiminde, geri al/yinele gibi problemlerde ve özyinelemesiz gezinmede yığını kullanmak.

Labirent

Labirent (Maze) Problemi

0011223344556677ÇG

n×p boyutlu bir matriste 0’lar açık yolu, 1’ler duvarı simgeler. Giriş [1][1] (G), çıkış [6][6] (Ç): girişten çıkışa hangi yoldan gidilebileceği bulunmak istenir.

  • Yatay, dikey ve çapraz: 8 yönde hareket edilebilir.
  • Her adımda “matris dışına çıkıyor muyum?” denetiminden kurtulmak için matrisin çevresi 1 ile çevrilir.
  • Gidilen hücreler gezildi matrisinde işaretlenir: aynı yere iki kez girilmez.
  • Çıkmaza girilince son yol ayrımına geri dönülür: son giren ilk çıkar → yığın.

Labirent

Sekiz Yön ve hareket[] Tablosu

yönhareket[yön].dikeyhareket[yön].yatay
0−10
1−11
201
311
410
51−1
60−1
7−1−1
7[i−1][j−1]0[i−1][j]1[i−1][j+1]6[i][j−1]yön[i][j]2[i][j+1]5[i+1][j−1]4[i+1][j]3[i+1][j+1]

Yön 0 kuzey; saat yönünde 1…7. Yeni konum: [i + dikey][j + yatay].

Sekizlik arama tablosu (look-up table) sekiz ayrı if yazmaktan kurtarır: yön bir döngü değişkeni olur.

Labirent

Labirent İçin Tanımlar

#define MAX_STACK_SIZE 100
#define CIKIS_SATIR 6
#define CIKIS_SUTUN 6
typedef struct { short int dikey, yatay; } yonler;
yonler hareket[8] = {{-1, 0}, {-1, 1}, {0, 1}, {1, 1},
                     {1, 0}, {1, -1}, {0, -1}, {-1, -1}};
typedef struct { short int satir, sutun, yon; } element;
element yigit[MAX_STACK_SIZE];
int top = -1;
short int gezildi[8][8];
short int labirent[8][8] = {{1,1,1,1,1,1,1,1},
    {1,0,1,0,0,0,1,1}, {1,0,1,0,1,1,1,1},
    {1,1,0,1,1,1,1,1}, {1,0,1,1,0,1,1,1},
    {1,1,0,0,1,0,1,1}, {1,1,1,1,1,1,0,1},
    {1,1,1,1,1,1,1,1}};
  • element: yığına konan kayıt, hangi hücreden (satir, sutun) ve geri dönülürse hangi yönden devam edileceği (yon).
  • gezildi: ziyaret edilen hücreler (küresel olduğu için başta sıfırdır).
  • labirent: çevresi 1 ile çevrili 8×8 matris; iç kısım 6×6.
  • Yığının en fazla boyutu: açık hücre sayısı kadar (burada 13).

Labirent

izbul Yordamı

void izbul(void) {
  short int satir, sutun, y_satir, y_sutun;
  int i, yon, bulundu = FALSE;
  element konum;
  yigit[0].satir = yigit[0].sutun = 1;
  yigit[0].yon = 1;     /* ilk konum yığına */
  gezildi[1][1] = 1;  top = 0;
  while (top > -1 && !bulundu) {
    konum = yigit_al(&top);   /* yeni konum */
    satir = konum.satir;
    sutun = konum.sutun;
    yon = konum.yon;          /* hangi yöne */
    while (yon < 8 && !bulundu) {
      y_satir = satir + hareket[yon].dikey;
      y_sutun = sutun + hareket[yon].yatay;
      if (y_satir == CIKIS_SATIR &&
          y_sutun == CIKIS_SUTUN)
        bulundu = TRUE;       /* çıkış mı? */
      else if (!labirent[y_satir][y_sutun] &&
               !gezildi[y_satir][y_sutun]) {
        gezildi[y_satir][y_sutun] = 1;
        konum.satir = satir;  /* nereden */
        konum.sutun = sutun;
        konum.yon = ++yon;    /* dönüşte yön */
        yigit_ekle(&top, konum);
        satir = y_satir;  sutun = y_sutun;
        yon = 0;
      } else ++yon;           /* başka yön */
    }                         /* geri adım */
  }

Sonunda bulundu ise yığındaki konumlar (0…top), son konum ve çıkış sırayla yazdırılır; değilse “Cikis yolu bulunamadi!”. (Kodu okunur kılmak için satırlar bölündü; mantık aynen korundu.)

Labirent · Kodu adım adım çalıştır

Kod İzleme: Yığınla Geri İzleme

0011223344556677Ç

Başlangıç: yığında (1,1) yön 1. yigit: satir,sutun,yon kayıtları (alttan üste).

0011223344556677Ç

İlk pop: (1,1), yön 1'den başlanır (yön 0 zaten duvar).

0011223344556677ÇG

(1,1)'den yön 4 (G) açık → (2,1)'e geç; (1,1, yön 5) yığına itildi.

0011223344556677ÇG

(2,1)'den yön 3 (GD) açık → (3,2)'e geç; (2,1, yön 4) yığına itildi.

0011223344556677ÇG

(3,2)'den yön 1 (KD) açık → (2,3)'e geç; (3,2, yön 2) yığına itildi.

0011223344556677ÇG

(2,3)'den yön 0 (K) açık → (1,3)'e geç; (2,3, yön 1) yığına itildi.

0011223344556677ÇG

(1,3)'den yön 2 (D) açık → (1,4)'e geç; (1,3, yön 3) yığına itildi.

0011223344556677ÇG

(1,4)'den yön 2 (D) açık → (1,5)'e geç; (1,4, yön 3) yığına itildi.

00×11223344556677ÇG

Geri adım / pop: (1,4) konumuna dönülür, yön 3'ten (GD) denemeye devam edilir.

00××11223344556677ÇG

Geri adım / pop: (1,3) konumuna dönülür, yön 3'ten (GD) denemeye devam edilir.

00×××11223344556677ÇG

Geri adım / pop: (2,3) konumuna dönülür, yön 1'den (KD) denemeye devam edilir.

00×××11×223344556677ÇG

Geri adım / pop: (3,2) konumuna dönülür, yön 2'den (D) denemeye devam edilir.

00×××11×223344556677ÇG

(3,2)'den yön 5 (GB) açık → (4,1)'e geç; (3,2, yön 6) yığına itildi.

00×××11×223344556677ÇG

(4,1)'den yön 3 (GD) açık → (5,2)'e geç; (4,1, yön 4) yığına itildi.

00×××11×223344556677ÇG

(5,2)'den yön 2 (D) açık → (5,3)'e geç; (5,2, yön 3) yığına itildi.

00×××11×223344556677ÇG

(5,3)'den yön 1 (KD) açık → (4,4)'e geç; (5,3, yön 2) yığına itildi.

00×××11×223344556677ÇG

(4,4)'den yön 3 (GD) açık → (5,5)'e geç; (4,4, yön 4) yığına itildi.

00×××11×223344556677ÇG

Yön 3 (GD) çıkış! Yol: yığın + (5,5) + (6,6).

while (top > -1 && !bulundu) {
  konum = yigit_al(&top);
  satir = konum.satir;  sutun = konum.sutun;
  yon = konum.yon;
  while (yon < 8 && !bulundu) {
    ys = satir + hareket[yon].dikey;
    yy = sutun + hareket[yon].yatay;
    if (ys == CIKIS_SATIR && yy == CIKIS_SUTUN)
      bulundu = TRUE;
    else if (!labirent[ys][yy] &&
             !gezildi[ys][yy]) {
      gezildi[ys][yy] = 1;
      konum = (element){satir, sutun, ++yon};
      yigit_ekle(&top, konum);
      satir = ys;  sutun = yy;  yon = 0;
    } else ++yon;
} }

Labirent

Değerlendirme ve En Kötü Durum

  • Sekizlik look-up table kodu kısalttı ve işi kolaylaştırdı.
  • Program labirentte yığın yapısıyla kaba kuvvet (brute force) araması yapar.
  • En kötü durum analizi için en kötü veri grubu bulunmalıdır; yönlerin tarama sırası belirleyicidir.
  • Girdi 1: ilk denenen yol uzun ama doğru → yalnız ekleme, silme yok.
  • Girdi 2: ilk denenen yol uzun ve yanlış → sonuna kadar gidilir, yığın bir bir boşaltılır; doğru yol dışındaki her yer gezildikten sonra çözüme ulaşılır. En kötü veri grubu budur.
Girdi 1Girdi 2
Hocadan “Analizi bile beceremeden bir algoritmaya burun kıvrılmaz!”

Labirent

Karmaşıklık ve Kuyrukla Karşılaştırma

Zaman

Her açık hücre en çok bir kez itilir (gezildi), her hücrede en çok 8 yön denenir: m×p labirentte O(m·p).

Bellek

Yığın en fazla açık hücre sayısı kadar büyür: O(m·p); gezildi da O(m·p).

Yığın (DFS)

Bulunan yol en kısa olmak zorunda değil. Bu labirentte (2,3)→(1,3)→(1,4)→(1,5) çıkmazına girip döndü; bulduğu yol 9 hücre.

Kuyruk (BFS)

Hücreleri uzaklık sırasıyla açar: en kısa yolu garanti eder. Burada o da 9 hücrelik aynı yolu bulur; ama genelde DFS'ninki daha uzun olabilir.

Hocadan “Bir kodu ayrıntılı inceleyebilecek kişi ancak onu yazandır. Bir başkası o kodu tam olarak analiz edemez.”
Hocadan “Öğrenci sayısı 120-130 değil de 30-40 olsa ve yanımda da 4-5 tane asistanım olsa o zaman ödevleri daha ayrıntılı inceleme imkânı bulabilirdim.”

İfade Gösterimleri

İfade Değerlendirme (Evaluation of Expressions)

Mantıksal ve aritmetik ifadelerin derleyiciler tarafından değerlendirilmesinde de yığın kullanılır. Derleyici şu ifadelerin üstesinden nasıl geliyor?

Öncelik

x = a / b − c + d * e − a * c

Değer

((4 / 2) − 2) + (3 * 3) − (4 * 2) = 1

Parantez

a / (b − c) + d * (e − a) * c

  • Soldan sağa okurken bir işleç hemen uygulanamaz: sağında daha öncelikli bir işleç olabilir.
  • Çözüm iki aşamalı: önce ifade önceliğe göre postfix’e çevrilir, sonra postfix tek geçişte hesaplanır. İkisi de yığınla yapılır.
Hocadan “Problem önce araştırılmalı; o soru başkaları tarafından defalarca çözülmüştür. En hızlı yolu bulunmalı. Çözülmemişse, o zaman keşif başlar.”

İfade Gösterimleri

C'de İşlem Önceliği (Özet)

ÖncelikİşleçlerBirleşme
en yüksek() [] -> .soldan sağa
tekli − ! ++ -- * &sağdan sola
* / %soldan sağa
+ −soldan sağa
< <= > >=soldan sağa
== !=soldan sağa
&&soldan sağa
en düşük||soldan sağa
  • Önce öncelik, eşitlikte birleşme yönü karar verir: a − b − c = (a − b) − c.
  • Parantez her şeyi geçersiz kılar.
  • Örnek: a / b − c + d * e − a * c = (((a / b) − c) + (d * e)) − (a * c).
  • Bu bölümün algoritmaları + − * / % ve parantezle sınırlıdır (Horowitz–Sahni, Şekil 3.12).

İfade Gösterimleri

Infix, Prefix, Postfix

  • Infix (ara-işleç): işleç işlenenlerinin arasında, a + b. Hem parantezleri hem öncelikleri dikkate almak gerekir.
  • Prefix (ön-işleç, Polonya gösterimi): işleç önde, + a b.
  • Postfix (son-işleç, ters Polonya): işleç sonda, a b +. Prefix ve postfix’te parantez gerekmez, öncelik düşünmeden işlem yapılır.
NoPrefixInfixPostfix
1*343*434*
2+2*342+3*4234*+
3-*+abc/da(a+b)*c-d/aab+c*da/-
4**/a+-bcd-eac(a/(b-c+d))*(e-a)*cabc-d+/ea-*c*

İfade Gösterimleri

Elle Çevirme: Parantezleme, Taşıma, Silme

a / b − c + d * e − a * c

Başlangıç ifadesi.

((((a / b) − c) + (d * e)) − (a * c))

1. Öncelik ve birleşmeye göre tam parantezlenir.

((((a b /) c −) (d e *) +) (a c *) −)

2. Her işleç kendi parantezinin sağ kapanışına taşınır.

a b / c − d e * + a c * −

3. Parantezler silinir: postfix. (Prefix için işleç sol açılışa taşınır: − + − / a b c * d e * a c.)

İşlenenlerin sırası değişmez; yalnız işleçlerin yeri değişir. Bu gözlem, yığınlı algoritmanın temelidir: işlenen gelir gelmez çıktıya yazılır, işleçler uygun an gelene kadar yığında bekler.

İfade Gösterimleri

Postfix → Infix: 42/2−33*+42*−

token42/2-33*+42*-yığın[0]4[1][2][3]top = 0

4 işlenen → yığına.

token42/2-33*+42*-yığın[0]4[1]2[2][3]top = 1

2 işlenen → yığına.

token42/2-33*+42*-yığın[0](4/2)[1][2][3]top = 0

/ işleç → iki öğe alınır (4, 2), (4/2) olarak geri konur.

token42/2-33*+42*-yığın[0](4/2)[1]2[2][3]top = 1

2 işlenen → yığına.

token42/2-33*+42*-yığın[0]((4/2)-2)[1][2][3]top = 0

- işleç → iki öğe alınır ((4/2), 2), ((4/2)-2) olarak geri konur.

token42/2-33*+42*-yığın[0]((4/2)-2)[1]3[2][3]top = 1

3 işlenen → yığına.

token42/2-33*+42*-yığın[0]((4/2)-2)[1]3[2]3[3]top = 2

3 işlenen → yığına.

token42/2-33*+42*-yığın[0]((4/2)-2)[1](3*3)[2][3]top = 1

* işleç → iki öğe alınır (3, 3), (3*3) olarak geri konur.

token42/2-33*+42*-yığın[0](((4/2)-2)+(3*3))[1][2][3]top = 0

+ işleç → iki öğe alınır (((4/2)-2), (3*3)), (((4/2)-2)+(3*3)) olarak geri konur.

token42/2-33*+42*-yığın[0](((4/2)-2)+(3*3))[1]4[2][3]top = 1

4 işlenen → yığına.

token42/2-33*+42*-yığın[0](((4/2)-2)+(3*3))[1]4[2]2[3]top = 2

2 işlenen → yığına.

token42/2-33*+42*-yığın[0](((4/2)-2)+(3*3))[1](4*2)[2][3]top = 1

* işleç → iki öğe alınır (4, 2), (4*2) olarak geri konur.

token42/2-33*+42*-yığın[0]((((4/2)-2)+(3*3))-(4*2))[1][2][3]top = 0

- işleç → iki öğe alınır ((((4/2)-2)+(3*3)), (4*2)), ((((4/2)-2)+(3*3))-(4*2)) olarak geri konur. Sonuç: ((((4/2)−2)+(3*3))−(4*2)).

* işlendikten sonra [1] hücresi boş kalmaz; orada (3*3) ve (4*2) durur (top = 1).

Postfix Hesaplama

Söz Dizim Birimi: token_al

typedef enum { sol_paran, sag_paran, arti, eksi,
    carpma, bolme, mod, eos, operand } oncelik;

oncelik token_al(const char *expr, char *sembol, int *n) {
    *sembol = expr[(*n)++];
    switch (*sembol) {
        case '(':  return sol_paran;
        case ')':  return sag_paran;
        case '+':  return arti;
        case '-':  return eksi;
        case '/':  return bolme;
        case '*':  return carpma;
        case '%':  return mod;
        case '\0': return eos;
        default:   return operand;
    }
}
  • İfade karakter karakter okunur; her karakter bir token (damga, söz dizim birimi).
  • İşlenenler tek basamaklı varsayılır. Rakamı sayıya çevirmek: sembol − '0'. '2'nin ASCII kodu 50, '0'ınki 48: 50 − 48 = 2.
  • İfade '\0' ile biter → eos.
  • expr küresel bir dizi olarak da tutulabilirdi; burada parametre olarak geçirilir.
Hocadan “typedef enum { … }; ile etiketleme yapmak işimizi kolaylaştırır.”

Postfix Hesaplama

Postfix Hesaplama: eval (Küresel Sürüm)

float eval(void) {
    char sembol;  oncelik token;
    float op1, op2;
    int n = 0, top = -1;
    token = token_al(&sembol, &n);
    while (token != eos) {
        if (token == operand)
            yigit_ekle(&top, sembol - '0');
        else {
            op2 = yigit_al(&top);   /* önce sağ işlenen */
            op1 = yigit_al(&top);
            switch (token) { /* +, -, *, /, % */ }
        }
        token = token_al(&sembol, &n);
    }
    return yigit_al(&top);   /* kalan tek öğe sonuç */
}
  • İşlenen gelirse yığına konur; işleç gelirse iki işlenen alınır, işlem yapılır, sonuç yığına konur.
  • İlk alınan op2 (sağ işlenen): 6 2 − için 6 − 2, 2 − 6 değil.
  • Sonda yığında tek öğe kalmalı; kalmıyorsa ifade hatalıdır.
  • case mod: (int)op1 % (int)op2: % float ile kullanılamaz.

Postfix Hesaplama

Küresel Değişkenden Parametreye

/* kuresel surum: disa bagimli */
char expr[] = "42/2-33*+42*-";
float yigit[MAX_SIZE];
float eval(void);

İfade ve yığın küresel: aynı anda iki ifade hesaplanamaz, yordam başka bir programa taşınamaz.

/* bileşen: her şey parametre/yerel */
float eval(const char *expr);
/* yığın eval'in içinde yerel dizi */
/* main içinde: */
float a = eval("23*54*+9-");
float b = eval("42/2-33*+42*-");

Aynı yordam her yerde, her ifadeyle, yan etkisiz çalışır. Kod izlemelerinde bu sürüm kullanılır.

Hocadan “Fonksiyonlar component (bileşen) yazar gibi yazılmalı. Global değişken, define gibi kullanımlarla dışa bağımlılık oluşturulmamalı! Böyle component yazılmaz! Yarın bir yerde işe girip ekip hâlinde çalışırken proje yöneticisine şöyle mi denecek: Ben çok has programcıyım. Kendime özgü bir tarzım var. Benim değişkenleri main’in üstüne koy. Ben böyle çalışıyorum. Beni kabul edersen olduğum gibi kabul et, yoksa…”

Postfix Hesaplama · Kodu adım adım çalıştır

Kod İzleme: eval("23*54*+9-")

02132*35445*6+798-yığın[0][1][2][3]top = -1

İfade 23*54*+9-; yığın boş.

02132*35445*6+798-yığın[0]2[1][2][3]top = 0

'2' işlenen: '2' − '0' = 2 yığına.

02132*35445*6+798-yığın[0]2[1]3[2][3]top = 1

'3' işlenen: '3' − '0' = 3 yığına.

02132*35445*6+798-yığın[0]6[1][2][3]top = 0

'*' işleç: op2 = 3, op1 = 2 → 2 * 3 = 6 yığına.

02132*35445*6+798-yığın[0]6[1]5[2][3]top = 1

'5' işlenen: '5' − '0' = 5 yığına.

02132*35445*6+798-yığın[0]6[1]5[2]4[3]top = 2

'4' işlenen: '4' − '0' = 4 yığına.

02132*35445*6+798-yığın[0]6[1]20[2][3]top = 1

'*' işleç: op2 = 4, op1 = 5 → 5 * 4 = 20 yığına.

02132*35445*6+798-yığın[0]26[1][2][3]top = 0

'+' işleç: op2 = 20, op1 = 6 → 6 + 20 = 26 yığına.

02132*35445*6+798-yığın[0]26[1]9[2][3]top = 1

'9' işlenen: '9' − '0' = 9 yığına.

02132*35445*6+798-yığın[0]17[1][2][3]top = 0

'-' işleç: op2 = 9, op1 = 26 → 26 - 9 = 17 yığına.

02132*35445*6+798-yığın[0]17[1][2][3]top = 0

'\0': döngü biter; yığındaki tek öğe 17 döner. Her token bir kez işlendi → O(n).

float eval(const char *expr) {
  float y[MAX_SIZE], op1, op2;  int top = -1;
  for (int n = 0; expr[n] != '\0'; n++) {
    char c = expr[n];
    if (isdigit(c)) y[++top] = c - '0';
    else {
      op2 = y[top--];  op1 = y[top--];
      switch (c) {
        case '+': y[++top] = op1 + op2; break;
        case '-': y[++top] = op1 - op2; break;
        case '*': y[++top] = op1 * op2; break;
        case '/': y[++top] = op1 / op2; break;
      }
    }
  }
  return y[top];
}

Postfix Hesaplama · Kodu adım adım çalıştır

Kod İzleme: Örnek İfade

04122/324-53637*8+9410211*12-yığın[0][1][2][3]top = -1

İfade 42/2-33*+42*-; yığın boş.

04122/324-53637*8+9410211*12-yığın[0]4[1][2][3]top = 0

'4' işlenen: '4' − '0' = 4 yığına.

04122/324-53637*8+9410211*12-yığın[0]4[1]2[2][3]top = 1

'2' işlenen: '2' − '0' = 2 yığına.

04122/324-53637*8+9410211*12-yığın[0]2[1][2][3]top = 0

'/' işleç: op2 = 2, op1 = 4 → 4 / 2 = 2 yığına.

04122/324-53637*8+9410211*12-yığın[0]2[1]2[2][3]top = 1

'2' işlenen: '2' − '0' = 2 yığına.

04122/324-53637*8+9410211*12-yığın[0]0[1][2][3]top = 0

'-' işleç: op2 = 2, op1 = 2 → 2 - 2 = 0 yığına.

04122/324-53637*8+9410211*12-yığın[0]0[1]3[2][3]top = 1

'3' işlenen: '3' − '0' = 3 yığına.

04122/324-53637*8+9410211*12-yığın[0]0[1]3[2]3[3]top = 2

'3' işlenen: '3' − '0' = 3 yığına.

04122/324-53637*8+9410211*12-yığın[0]0[1]9[2][3]top = 1

'*' işleç: op2 = 3, op1 = 3 → 3 * 3 = 9 yığına.

04122/324-53637*8+9410211*12-yığın[0]9[1][2][3]top = 0

'+' işleç: op2 = 9, op1 = 0 → 0 + 9 = 9 yığına.

04122/324-53637*8+9410211*12-yığın[0]9[1]4[2][3]top = 1

'4' işlenen: '4' − '0' = 4 yığına.

04122/324-53637*8+9410211*12-yığın[0]9[1]4[2]2[3]top = 2

'2' işlenen: '2' − '0' = 2 yığına.

04122/324-53637*8+9410211*12-yığın[0]9[1]8[2][3]top = 1

'*' işleç: op2 = 2, op1 = 4 → 4 * 2 = 8 yığına.

04122/324-53637*8+9410211*12-yığın[0]1[1][2][3]top = 0

'-' işleç: op2 = 8, op1 = 9 → 9 - 8 = 1 yığına.

04122/324-53637*8+9410211*12-yığın[0]1[1][2][3]top = 0

'\0': döngü biter; yığındaki tek öğe 1 döner. Her token bir kez işlendi → O(n).

float eval(const char *expr) {
  float y[MAX_SIZE], op1, op2;  int top = -1;
  for (int n = 0; expr[n] != '\0'; n++) {
    char c = expr[n];
    if (isdigit(c)) y[++top] = c - '0';
    else {
      op2 = y[top--];  op1 = y[top--];
      switch (c) {
        case '+': y[++top] = op1 + op2; break;
        case '-': y[++top] = op1 - op2; break;
        case '*': y[++top] = op1 * op2; break;
        case '/': y[++top] = op1 / op2; break;
      }
    }
  }
  return y[top];
}

Infix → Postfix

Infix → Postfix: Algoritma

  • İşlenen: doğrudan çıktıya yazılır.
  • İşleç: yığındaki işlecin önceliği gelenin önceliğinden büyük ya da eşit oldukça o işleç çıktıya alınır; sonra gelen yığına konur (eşitlikte almak = soldan birleşme).
  • ( : her zaman yığına girer; içeride en düşük öncelikli davranır.
  • ): ( görülene kadar yığından çıktıya alınır; ( atılır.
  • Sonda: yığında kalanların hepsi çıktıya alınır.
Tokenisp (yığında)icp (gelen)
(020
* / %1313
+ −1212
# (dip işareti)−1—
( iki farklı öncelik taşır: gelirken en yüksek (hiçbir şeyi yığından çıkarmadan girer), yığındayken en düşük (üstüne her işleç binebilir). Yığının dibindeki # döngüyü durdurur.

Infix → Postfix · Kodu adım adım çalıştır

Kod İzleme: a*(b+c)-d → Postfix

0a1*2(3b4+5c6)7-8dyığın[0][1][2][3]top = -1

İfade a*(b+c)-d. Yığının dibinde # (isp = −1) var; şekilde gösterilmiyor.

0a1*2(3b4+5c6)7-8dyığın[0][1][2][3]top = -1

a işlenen → çıktıya.

0a1*2(3b4+5c6)7-8dyığın[0]*[1][2][3]top = 0

*: isp(#) = -1 < icp(*) = 13 → * yığına.

0a1*2(3b4+5c6)7-8dyığın[0]*[1]([2][3]top = 1

(: isp(*) = 13 < icp(() = 20 → ( yığına.

0a1*2(3b4+5c6)7-8dyığın[0]*[1]([2][3]top = 1

b işlenen → çıktıya.

0a1*2(3b4+5c6)7-8dyığın[0]*[1]([2]+[3]top = 2

+: isp(() = 0 < icp(+) = 12 → + yığına.

0a1*2(3b4+5c6)7-8dyığın[0]*[1]([2]+[3]top = 2

c işlenen → çıktıya.

0a1*2(3b4+5c6)7-8dyığın[0]*[1][2][3]top = 0

): ( görülene kadar al → + çıktıya; ( atıldı.

0a1*2(3b4+5c6)7-8dyığın[0]-[1][2][3]top = 0

-: isp(*) = 13 ≥ icp(-) = 12 → * çıktıya; - yığına.

0a1*2(3b4+5c6)7-8dyığın[0]-[1][2][3]top = 0

d işlenen → çıktıya.

0a1*2(3b4+5c6)7-8dyığın[0][1][2][3]top = -1

Girdi bitti: kalan - çıktıya. Sonuç abc+*d-. Her token bir kez yığına girip bir kez çıkar → O(n).

void postfix(const char *e, char *cikti) {
  char y[MAX] = {'#'};  int top = 0, k = 0;
  for (; *e; e++) {
    if (isalnum(*e)) cikti[k++] = *e;
    else if (*e == ')') {
      while (y[top] != '(')
        cikti[k++] = y[top--];
      top--;                /* '(' at */
    } else {
      while (isp(y[top]) >= icp(*e))
        cikti[k++] = y[top--];
      y[++top] = *e;
    }
  }
  while (top > 0) cikti[k++] = y[top--];
  cikti[k] = '\0';
}

Infix → Postfix · Kodu adım adım çalıştır

Kod İzleme: a*(b+c)*d Örneği

0a1*2(3b4+5c6)7*8dyığın[0][1][2][3]top = -1

İfade a*(b+c)*d. Yığının dibinde # (isp = −1) var; şekilde gösterilmiyor.

0a1*2(3b4+5c6)7*8dyığın[0][1][2][3]top = -1

a işlenen → çıktıya.

0a1*2(3b4+5c6)7*8dyığın[0]*[1][2][3]top = 0

*: isp(#) = -1 < icp(*) = 13 → * yığına.

0a1*2(3b4+5c6)7*8dyığın[0]*[1]([2][3]top = 1

(: isp(*) = 13 < icp(() = 20 → ( yığına.

0a1*2(3b4+5c6)7*8dyığın[0]*[1]([2][3]top = 1

b işlenen → çıktıya.

0a1*2(3b4+5c6)7*8dyığın[0]*[1]([2]+[3]top = 2

+: isp(() = 0 < icp(+) = 12 → + yığına.

0a1*2(3b4+5c6)7*8dyığın[0]*[1]([2]+[3]top = 2

c işlenen → çıktıya.

0a1*2(3b4+5c6)7*8dyığın[0]*[1][2][3]top = 0

): ( görülene kadar al → + çıktıya; ( atıldı.

0a1*2(3b4+5c6)7*8dyığın[0]*[1][2][3]top = 0

*: isp(*) = 13 ≥ icp(*) = 13 → * çıktıya; * yığına.

0a1*2(3b4+5c6)7*8dyığın[0]*[1][2][3]top = 0

d işlenen → çıktıya.

0a1*2(3b4+5c6)7*8dyığın[0][1][2][3]top = -1

Girdi bitti: kalan * çıktıya. Sonuç abc+*d*. Her token bir kez yığına girip bir kez çıkar → O(n).

void postfix(const char *e, char *cikti) {
  char y[MAX] = {'#'};  int top = 0, k = 0;
  for (; *e; e++) {
    if (isalnum(*e)) cikti[k++] = *e;
    else if (*e == ')') {
      while (y[top] != '(')
        cikti[k++] = y[top--];
      top--;                /* '(' at */
    } else {
      while (isp(y[top]) >= icp(*e))
        cikti[k++] = y[top--];
      y[++top] = *e;
    }
  }
  while (top > 0) cikti[k++] = y[top--];
  cikti[k] = '\0';
}

Infix → Postfix

İzleme Tablosu: a+b*c → abc*+

token[0][1]topÇıktı
a−1a
++0a
b+0ab
*+*1ab
c+*1abc
eos−1abc*+
  • * gelince üstte + var: isp(+) = 12 < icp(*) = 13 → * üstüne biner.
  • Sonda önce *, sonra + çıkar: daha öncelikli olan önce uygulanır.
Sık sorulan soru: “Son işlenen basılmıyor mu?” postfix yordamı işlenenleri anında çıktıya yazdığı ve sonda yığını boşalttığı için son işlenen de basılır.

Infix → Postfix

Infix → Prefix: Ters Çevirme, Postfix, Ters Çevirme

(a+b)*c-d/a

Infix → prefix için bir yol: ifade ters çevrilir.

a/d-c*)b+a(

Ters çevrildi. Şimdi ( ile ) yer değiştirir.

a/d-c*(b+a)

Parantezler düzeltildi. Bu ifade postfix’e çevrilir; ancak eşit öncelikte yığından alınmaz (> kullanılır), çünkü ters çevrilmiş ifadede birleşme yönü de ters döner.

ad/cba+*-

Postfix sonucu.

-*+abc/da

Tekrar ters çevrilir: prefix. Tablodaki 3. satırla aynı.

Doğrudan yol: ifade sağdan sola taranır; işlenenler çıktının başına eklenir, işleçler aynı yığın kuralıyla yönetilir. İki yol da O(n).

Infix → Postfix · Kodu adım adım çalıştır

Kod İzleme: Prefix Hesaplama (-+*23*549)

0-1+2*32435*657489yığın[0][1][2][3]top = -1

Prefix -+*23*549 (infix: 2*3+5*4−9). Sağdan sola taranır.

0-1+2*32435*657489yığın[0]9[1][2][3]top = 0

'9' işlenen → yığına.

0-1+2*32435*657489yığın[0]9[1]4[2][3]top = 1

'4' işlenen → yığına.

0-1+2*32435*657489yığın[0]9[1]4[2]5[3]top = 2

'5' işlenen → yığına.

0-1+2*32435*657489yığın[0]9[1]20[2][3]top = 1

'*': ilk alınan op1 = 5 (sol işlenen!), op2 = 4 → 5 * 4 = 20.

0-1+2*32435*657489yığın[0]9[1]20[2]3[3]top = 2

'3' işlenen → yığına.

0-1+2*32435*657489yığın[0]9[1]20[2]3[3]2top = 3

'2' işlenen → yığına.

0-1+2*32435*657489yığın[0]9[1]20[2]6[3]top = 2

'*': ilk alınan op1 = 2 (sol işlenen!), op2 = 3 → 2 * 3 = 6.

0-1+2*32435*657489yığın[0]9[1]26[2][3]top = 1

'+': ilk alınan op1 = 6 (sol işlenen!), op2 = 20 → 6 + 20 = 26.

0-1+2*32435*657489yığın[0]17[1][2][3]top = 0

'-': ilk alınan op1 = 26 (sol işlenen!), op2 = 9 → 26 - 9 = 17.

0-1+2*32435*657489yığın[0]17[1][2][3]top = 0

Sonuç 17: postfix örneğiyle (23*54*+9−) aynı ifade. Postfix’te ilk alınan sağ işlenendi; prefix’te sol işlenen.

float prefix_eval(const char *e) {
  float y[MAX], op1, op2;  int top = -1;
  for (int i = strlen(e) - 1; i >= 0; i--) {
    if (isdigit(e[i])) y[++top] = e[i] - '0';
    else {
      op1 = y[top--];  op2 = y[top--];
      y[++top] = uygula(e[i], op1, op2);
    }
  }
  return y[top];
}

Infix → Postfix

Çalışma: Diğer Çeviriler

in-fixpost-fixpre-fixeval✓?????✓?

✓: bu bölümde yapıldı · ?: tasarlanacak çeviriler.

ÇALIŞMA: İfade çözümleme ile ilgili diğer çevirme ve hesaplama algoritmalarını tasarlayınız.

  • postfix → infix (bu bölümde elle yapıldı; C ile yazın)
  • postfix → prefix ve prefix → postfix
  • infix → prefix (doğrudan, sağdan sola tarayarak)
  • prefix → infix
  • infix'i doğrudan hesaplama (iki yığın: işlenen ve işleç)

Diğer Uygulamalar · Kodu adım adım çalıştır

Kod İzleme: Dengeli Parantez {a*(b+c)-[d]}

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0][1][2][3]top = -1

Girdi {a*(b+c)-[d]}.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1][2][3]top = 0

{ açan → yığına.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1][2][3]top = 0

'a' parantez değil, atlanır.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1][2][3]top = 0

'*' parantez değil, atlanır.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1]([2][3]top = 1

( açan → yığına.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1]([2][3]top = 1

'b' parantez değil, atlanır.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1]([2][3]top = 1

'+' parantez değil, atlanır.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1]([2][3]top = 1

'c' parantez değil, atlanır.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1][2][3]top = 0

) kapanan; üstteki ( ile eşleşti → pop.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1][2][3]top = 0

'-' parantez değil, atlanır.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1][[2][3]top = 1

[ açan → yığına.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1][[2][3]top = 1

'd' parantez değil, atlanır.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0]{[1][2][3]top = 0

] kapanan; üstteki [ ile eşleşti → pop.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0][1][2][3]top = -1

} kapanan; üstteki { ile eşleşti → pop.

0{1a2*3(4b5+6c7)8-9[10d11]12}yığın[0][1][2][3]top = -1

Girdi bitti ve yığın boş → true: dengeli.

bool dengeli(const char *e) {
  char y[MAX];  int top = -1;
  for (; *e; e++) {
    if (*e=='(' || *e=='[' || *e=='{')
      y[++top] = *e;
    else if (*e==')' || *e==']' || *e=='}') {
      if (top < 0 || !eslesir(y[top], *e))
        return false;  /* fazla/yanlış */
      top--;
    }
  }
  return top == -1;       /* açık kalmadı mı */
}

Diğer Uygulamalar

Parantez Denetimi: Üç Hata Türü

GirdiNe olur?Hata
[(a+b]*c)] gelince üstte ( varYanlış eşleşme
(a+b))İkinci ) geldiğinde yığın boşFazla kapanan
((a+b)Girdi bitince yığında ( kaldıKapanmamış açan
  • Yalnız sayaç tutmak tek tür parantez için yeterlidir; birden çok türde “en son açılan” bilgisi gerekir → yığın.
  • Derleyiciler, HTML/XML denetleyicileri ve metin düzenleyicilerin eşleşen parantez vurgusu bu algoritmayı kullanır.
  • Zaman O(n), bellek en kötü O(n) (hepsi açan).

Diğer Uygulamalar

Yığın ve Kuyruğun Başka Uygulamaları

Geri al / yinele

İki yığın: yapılan işlem “geri al” yığınına, geri alınan “yinele” yığınına gider; tarayıcının geri/ileri düğmeleri de böyle çalışır.

Özyinelemesiz gezinme

Kök ortada gezinme, düğüm adreslerini tutan açık bir yığınla döngüye çevrilir: O(n) zaman, O(h) yığın.

Sonraki büyük öğe

Tek geçişte her öğenin sağındaki ilk büyük öğe: azalan yığın, O(n).

Kayan pencere maksimumu

Deque ile her pencerenin en büyüğü O(n)'de bulunur.

BFS ile en kısa yol

Labirentte kuyruk kullanılırsa ilk bulunan yol en kısa yoldur.

Hanoi, N-vezir, sudoku

Özyinelemeli geri izleme; açık yığınla da yazılabilir.

Özet

Karmaşıklık Özeti

AlgoritmaYapıZamanEk bellek
Labirent (izbul)yığınO(m·p)O(m·p)
Labirent en kısa yolkuyrukO(m·p)O(m·p)
Postfix / prefix hesaplamayığınO(n)O(n)
Infix → postfix / prefixyığınO(n)O(n)
Parantez denetimiyığınO(n)O(n)
Özyinelemesiz kök ortadayığınO(n)O(h)
Seviye sırası gezinmekuyrukO(n)O(n)

n: ifade uzunluğu ya da düğüm sayısı · m×p: labirent boyutu · h: ağaç yüksekliği.

Özet

Sık Yapılan Hatalar

1Postfix'te op1 ile op2'yi ters almak: 6 2 − −4 olur.
2Prefix'te postfix sırasını kullanmak: orada ilk alınan sol işlenendir.
3Infix → postfix'te eşit öncelikte yığından almamak: a−b−c yanlış çevrilir.
4) gelince ('ı yığında unutmak ya da çıktıya yazmak.
5Labirentte gezildi işaretini unutup sonsuz döngüye girmek.
6Çevreyi 1 ile çevirmeden dizi dışına taşmak.

Özet

Alıştırmalar

  1. a/(b-c)+d*(e-a)*c ifadesini postfix ve prefix'e çevir.
  2. 6 2 3 + − 3 8 2 / + * postfix ifadesini hesapla.
  3. Postfix → infix çeviren C yordamını yaz.
  4. izbul bulunan yolu labirentte işaretlesin.
  5. Labirentte en kısa yolu kuyrukla bul.
  6. Özyinelemesiz kök başta ve kök sonda gezinmeyi yaz.
İpuçları / cevaplar
  1. Postfix: abc-/dea-*c*+; prefix: +/a-bc**d-eac.
  2. 2+3=5, 6−5=1, 8/2=4, 3+4=7, 1*7 = 7.
  3. İşlenen: tek karakterli dizge it; işleç: b = pop, a = pop, "(" a op b ")" it. malloc/free'ye dikkat.
  4. Bulunca yığındaki (satir, sutun) çiftlerini labirent[s][t] = 2 yap.
  5. Kuyruğa (satir, sutun) koy; her hücre için geldiği hücreyi sakla; çıkışa ulaşınca geriye izle.
  6. Kök başta: kökü it; döngü: al, bas, önce sağı sonra solu it. Kök sonda: iki yığın ya da ziyaret bayrağı.

Özet

Bölüm Özeti

Geri izleme

Labirentte her yol ayrımı yığına; çıkmazda pop ile son ayrıma dönülür.

En kötü durum

Uzun ve yanlış ilk yol: tüm yanlış bölge gezilir.

Gösterimler

Infix öncelik ister; postfix/prefix parantezsiz ve önceliksiz hesaplanır.

Postfix hesaplama

İşlenen itilir; işleçte op2, op1 alınır; sonuç itilir. O(n).

Infix → postfix

isp ≥ icp oldukça alınır; ( gelirken yüksek, yığında düşük öncelik.

Genel ilke

“En son açılan ilk kapanır” görülen her yerde yığın; “sırayla” görülen yerde kuyruk.

Hocadan “Ödev kâğıdını şöyle benim kâğıtla üst üste getirip ışığa tuttuğum zaman… (fark görmemem lazım)”

Özet

Kaynaklar ve Devam

  • E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, 2. bs., Bölüm 3.5 (labirent), 3.6 (ifade değerlendirme).
  • R. Sedgewick, Algorithms in C, Kısım 1–4, Bölüm 4.3 (postfix), 5 (özyineleme ve yığın).

Son

Bölüm 8: Yığın ve Kuyruk Uygulamaları
Sonraki bölüm: Bölüm 9: Liste ADT ve Genelleştirilmiş Listeler · Ders sayfası
1 / 1 C ile Veri Yapıları