SEN2211 · 6. hafta · Stacks for Algebraic Operations

Yığınla ifade işlemleri: parantez, infix → postfix, postfix hesaplama

Parantez dengesini kontrol etmek, infix ifadeyi öncelik kurallarıyla postfix'e çevirmek ve postfix ifadeyi yığınla hesaplamak. Sınavda adım adım yığın tablosu çizdirilir.

balanced parenthesesinfixpostfix (Reverse Polish Notation)prefixoperator precedenceassociativityoperandoperator stackpostfix evaluationShunting-yard

Hesap makinesine 2 + 3 * 4 yazınca 14 alırsın, 20 değil. Makine öncelik kurallarını nasıl uygular? Cevap: iki yığın algoritması. Önce ifadeyi parantezsiz bir biçime (postfix) çevirir, sonra o biçimi soldan sağa tek geçişte hesaplar. Aynı yığın fikri editörlerin parantez eşleştirmesinde de kullanılır. Sınavda üç soru tipi var: dengeli mi, postfix’e çevir (çoğu zaman yığın tablosuyla), postfix’i hesapla.

Altın kurallar:

  1. Açılan son parantez ilk kapanmalı. Yığın tam bunu kontrol eder.
  2. Postfix’te işlenenlerin (operand) sırası değişmez. Sadece operatörler yer değiştirir.
  3. Yüksek öncelikli operatör yığından önce çıkar.
  4. Postfix hesaplarken ilk çekilen sağ işlenendir.

Parantez dengesi (balanced parentheses)

Algoritma: soldan sağa oku. Açılış parantezini it. Kapanış gelince yığın boşsa ya da tepedeki açılış aynı türde değilse dengesiz. Sonda yığın boş değilse dengesiz.

static boolean isBalanced(String s) {
    Deque<Character> st = new ArrayDeque<>();
    for (char c : s.toCharArray()) {
        if (c == '(' || c == '[' || c == '{') st.push(c);
        else if (c == ')' || c == ']' || c == '}') {
            if (st.isEmpty()) return false;
            char open = st.pop();
            if ((c == ')' && open != '(') || (c == ']' && open != '[') || (c == '}' && open != '{'))
                return false;
        }
    }
    return st.isEmpty();
}
{[()()]} -> true
([)] -> false
(() -> false
())( -> false
{[]}() -> true
a*(b+c] -> false

Her karakterden sonra yığının hali (soldan sağa: dip → tepe):

input: ( [ ] { } )
(  stack(bottom->top): (
[  stack(bottom->top): ([
]  stack(bottom->top): (
{  stack(bottom->top): ({
}  stack(bottom->top): (
)  stack(bottom->top): (empty)
end: empty -> balanced

TUZAK · Sayı tutmak yetmez. “Açılış sayısı = kapanış sayısı” kontrolü ([)] için evet der ama ifade yanlış. )( için de sayı eşit ama sıra yanlış. Tür ve sıra için yığın şart.

Üç gösterim: infix, postfix, prefix

GösterimÖrnekAçıklama
infixA + B * Coperatör ortada, insan dili; parantez ve öncelik gerekir
postfixA B C * +operatör işlenenlerden sonra; parantez gerekmez
prefix+ A * B Coperatör işlenenlerden önce

Postfix’e Reverse Polish Notation (RPN) da denir. Avantajı: öncelik bilgisi sıranın içine gömülüdür, makine soldan sağa okuyup tek yığınla hesaplar.

Elle çevirmenin hızlı yolu: ifadeyi tamamen parantezle, sonra her operatörü kendi kapanış parantezinin yerine taşı. A + B * C → (A + (B * C)) → A B C * +.

İnfix → postfix algoritması

Bir çıktı dizgisi ve bir operatör yığını kullanılır.

Her token için:
  işlenen (harf/sayı)  → çıktıya yaz
  (                    → yığına it
  )                    → ( gelene kadar pop edip çıktıya yaz; ( 'ı at
  operatör op          → yığın boş değil, tepe ( değil VE
                         [ prec(tepe) > prec(op)  ya da  prec(tepe) == prec(op) ve op sola bağlı ]
                         olduğu sürece pop edip çıktıya yaz; sonra op'u it
Sonda: yığında kalanları pop edip çıktıya yaz
OperatörÖncelikBağlanma (associativity)
^3sağdan sola
* / %2soldan sağa
+ -1soldan sağa

Java kodu (tek harfli işlenenler için):

static int prec(char op) {
    switch (op) {
        case '+': case '-': return 1;
        case '*': case '/': case '%': return 2;
        case '^': return 3;
    }
    return 0;
}

static String toPostfix(String infix) {
    StringBuilder out = new StringBuilder();
    Deque<Character> st = new ArrayDeque<>();
    for (char c : infix.toCharArray()) {
        if (c == ' ') continue;
        if (Character.isLetterOrDigit(c)) out.append(c);
        else if (c == '(') st.push(c);
        else if (c == ')') {
            while (st.peek() != '(') out.append(st.pop());
            st.pop();                        // ( at
        } else {
            while (!st.isEmpty() && st.peek() != '(' &&
                   (prec(st.peek()) > prec(c) || (prec(st.peek()) == prec(c) && c != '^')))
                out.append(st.pop());
            st.push(c);
        }
    }
    while (!st.isEmpty()) out.append(st.pop());
    return out.toString();
}

Çözümlü iz: A + B * C - D / E

token  stack      output
A      (empty)    A
+      +          A
B      +          AB
*      +*         AB
C      +*         ABC
-      -          ABC*+
D      -          ABC*+D
/      -/         ABC*+D
E      -/         ABC*+DE
end    (empty)    ABC*+DE/-

Kritik an - token’ı: tepede * (öncelik 2 > 1) var, çıkar. Sonra tepede + (eşit öncelik, soldan sağa) var, o da çıkar. Ancak ondan sonra - itilir. / geldiğinde tepedeki - daha düşük öncelikli olduğu için kalır.

Program bir dizi ifadeyi çevirdi:

A+B*C -> ABC*+  maxStack=2
(A+B)*C-D -> AB+C*D-  maxStack=2
A*(B+C)/D-E -> ABC+*D/E-  maxStack=3
A-B-C -> AB-C-  maxStack=1
A^B^C -> ABC^^  maxStack=2
(A+B)*(C-D) -> AB+CD-*  maxStack=3
A+B*(C^D-E)^(F+G*H)-I -> ABCD^E-FGH*+^*+I-  maxStack=6

maxStack, çevirme sırasında yığının ulaştığı en büyük boyuttur; sınavda bazen sorulur.

TUZAK · Sağa bağlı ^. ABCA^{B^C} matematikte A(BC)A^{(B^C)} demektir. Eşit öncelikte ^ geldiğinde tepedeki ^’ı çıkarırsan AB^C^ yazarsın, bu (AB)C(A^B)^C olur. Doğrusu ABC^^.

Postfix ifadeyi hesaplama

Bu sefer yığında sayılar var.

Her token için:
  sayı      → it
  operatör  → right = pop(), left = pop(), it(left op right)
Sonda yığında tek sayı kalır: sonuç
static int evalPostfix(String expr) {
    Deque<Integer> st = new ArrayDeque<>();
    for (String tok : expr.trim().split("\\s+")) {
        if (tok.matches("-?\\d+")) st.push(Integer.parseInt(tok));
        else {
            int right = st.pop();
            int left = st.pop();
            switch (tok) {
                case "+": st.push(left + right); break;
                case "-": st.push(left - right); break;
                case "*": st.push(left * right); break;
                case "/": st.push(left / right); break;
            }
        }
    }
    return st.pop();
}

Çözümlü iz: 4 7 2 - * 3 +

Her token sonrası yığın (dip → tepe):

4    4
7    4 7
2    4 7 2
-    4 5
*    20
3    20 3
+    23
23

Kontrol: postfix’i infix’e geri çevir. 7 2 - = (7 − 2), 4 (7−2) * = 4 · 5, 20 3 + = 23. Doğru.

İPUCU · Operand sırası. 9 5 - 2 - için doğru sonuç (9 − 5) − 2 = 2. pop sırasını ters alırsan önce 5 − 9 = −4, sonra 2 − (−4) = 6 bulursun. “İlk çıkan sağa gider” cümlesini ezberle.

Hatalı ifade tespiti: bir operatör geldiğinde yığında iki sayı yoksa ya da sonda birden fazla sayı kalıyorsa ifade bozuktur (3 + gibi).

Sık yapılan hatalar

YanlışDoğrusu
Parantezde sadece sayı kontrolüTür ve sıra için yığın; sonda boş olmalı
Postfix’te işlenen sırasını değiştirmekA B C sırası korunur, sadece operatörler taşınır
) gelince (’ı çıktıya yazmakParantezler çıktıya hiç girmez
Eşit öncelikli operatörü yığında bırakmak (A-B-C → ABC--)Sola bağlıysa çıkar: AB-C-
^ için de eşitte çıkarmakSağa bağlı: ABC^^
Sonda yığını boşaltmayı unutmakKalan operatörler tepeden sırayla çıktıya
Hesaplarken pop() - pop()right = pop(); left = pop(); left - right
Operatörden önce ( kontrolü yapmamakTepe ( ise dur, ( sadece ) ile çıkar

Kendini test et

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

Soru 1kolay

Using the stack algorithm, which of these strings are balanced? (a) {[()()]} (b) ([)] (c) (() (d) {[]}()

Cevabı göster
(a) and (d)

(a) her kapanış tepedeki açılışla eşleşir, sonda yığın boş. (b) ) geldiğinde tepede [ var: uyumsuz. (c) Sonda yığında bir ( kalır. (d) iki ayrı dengeli grup, sonda boş.

Soru 2orta

The string { [ ( ) ] ( } is checked with a stack. What does the stack contain (bottom → top) just before the last character is read, and why does the check fail?

Cevabı göster
{( ; '}' pops '(' which does not match

{ → {, [ → {[, ( → {[(, ) → {[, ] → {, ( → {(. Son karakter } tepedeki ( ile eşleşmez, dengesiz.

Soru 3kolay

Convert to postfix: A + B * C

Cevabı göster
A B C * +

* önceliği +'dan yüksek. + yığına girer, B'den sonra gelen * onu çıkarmaz, üstüne girer. Sonda yığın tepeden boşaltılır: önce *, sonra +. Çıktı ABC*+.

Soru 4orta

Convert to postfix: (A + B) * C - D

Cevabı göster
A B + C * D -

Parantez içi önce: AB+. Sonra * girer, C yazılır. - geldiğinde tepedeki * daha yüksek öncelikli, çıkar. Sonra D, en sonda -. Çıktı AB+C*D-.

Soru 5orta

Convert to postfix: A * (B + C) / D - E

Cevabı göster
A B C + * D / E -

* girer. ( girer, B, +, C. ) gelince + çıkar: ABC+. / gelince tepedeki * aynı öncelikte ve soldan sağa olduğundan çıkar: ABC+*. D yazılır. - gelince / çıkar. E, sonda -. Yığının en fazla 3 eleman tuttuğu an: * ( +.

Soru 6zor

Convert to postfix: (a) A - B - C (b) A ^ B ^ C, where ^ is right-associative.

Cevabı göster
(a) A B - C - (b) A B C ^ ^

(a) - soldan sağa: (A − B) − C. İkinci - geldiğinde tepedeki eşit öncelikli - çıkar: AB-C-. (b) ^ sağdan sola: A ^ (B ^ C). Eşit öncelikteki ^ çıkarılmaz, üstüne itilir; sonda ikisi birden çıkar: ABC^^. Kural: eşit öncelikte sola bağlıysa çıkar, sağa bağlıysa çıkarma.

Soru 7kolay

Evaluate the postfix expression: 5 3 + 2 *

Cevabı göster
16

5, 3 itilir. +: 3 ve 5 çekilir, 8 itilir. 2 itilir. *: 8 · 2 = 16.

Soru 8orta

Evaluate the postfix expression and show the stack after each token: 6 2 3 + - 3 8 2 / + *

Cevabı göster
7

6    6
2    6 2
3    6 2 3
+    6 5
-    1        (6 - 5)
3    1 3
8    1 3 8
2    1 3 8 2
/    1 3 4    (8 / 2)
+    1 7
*    7
İnfix karşılığı (6 − (2 + 3)) · (3 + 8 / 2) = 1 · 7 = 7.

Soru 9orta

Evaluate: 8 2 / 3 - 4 2 * +. Careful with the operand order for - and /.

Cevabı göster
9

8 2 / → 8 / 2 = 4. 4 3 - → 4 − 3 = 1. 4 2 * → 8. 1 8 + → 9. Operatörde ilk çekilen sağ işlenendir: right = pop(); left = pop(); left - right. Ters yaparsan 2 / 8 = 0 ve 3 − 4 = −1 gibi yanlış sonuçlar çıkar.

Soru 10orta

Write static boolean isBalanced(String s) that checks (), [] and {} using a stack. Other characters are ignored.

Cevabı göster
Push openers; on a closer, fail if the stack is empty or the popped opener doesn't match; at the end the stack must be empty.

static boolean isBalanced(String s) {
    Deque<Character> st = new ArrayDeque<>();
    for (char c : s.toCharArray()) {
        if (c == '(' || c == '[' || c == '{') {
            st.push(c);
        } else if (c == ')' || c == ']' || c == '}') {
            if (st.isEmpty()) return false;          // fazladan kapanış
            char open = st.pop();
            if ((c == ')' && open != '(') ||
                (c == ']' && open != '[') ||
                (c == '}' && open != '{')) return false;   // tür uyumsuz
        }
    }
    return st.isEmpty();                             // açık kalan var mı
}
Üç hata türü: ())( (boşken kapanış), ([)] (tür uyumsuz), (() (sonda dolu). a*(b+c] → false.

Soru 11zor

Write static int evalPostfix(String expr) for space-separated integer tokens and the operators + - * /.

Cevabı göster
Push numbers; on an operator pop right then left, push left op right; return the single remaining value.

static int evalPostfix(String expr) {
    Deque<Integer> st = new ArrayDeque<>();
    for (String tok : expr.trim().split("\\s+")) {
        if (tok.matches("-?\\d+")) {
            st.push(Integer.parseInt(tok));
        } else {
            int right = st.pop();    // önce SAĞ
            int left = st.pop();
            switch (tok) {
                case "+": st.push(left + right); break;
                case "-": st.push(left - right); break;
                case "*": st.push(left * right); break;
                case "/": st.push(left / right); break;
            }
        }
    }
    return st.pop();
}
4 7 2 - * 3 + → 4 · (7 − 2) + 3 = 23. Hatalı ifadede (3 +) ikinci pop boş yığında NoSuchElementException fırlatır.

Soru 12zor

Convert A * (B - C) + D to postfix and give the operator stack (bottom → top) right after the token ) is processed and right after + is processed.

Cevabı göster
A B C - * D +; after ')': * ; after '+': +

token  stack      output
A      (empty)    A
*      *          A
(      *(         A
B      *(         AB
-      *(-        AB
C      *(-        ABC
)      *          ABC-
+      +          ABC-*
D      +          ABC-*D
end    (empty)    ABC-*D+
) gelince ('a kadar her şey çıkar (-), ( atılır. + gelince tepedeki * daha yüksek öncelikli, çıkar; sonra + itilir.

Doğrulama: Parantez kontrolü, infix → postfix çevirici ve postfix hesaplayıcı verify/SEN2211/W06.java içinde yazıldı ve JDK ile çalıştırıldı (java W06.java). Sayfadaki iz tabloları programın her token sonrası yazdırdığı yığın ve çıktı satırlarından birebir alındı; postfix sonucu aynı infix ifadesinin Java'da doğrudan hesabıyla (7) karşılaştırıldı.

Deftere not kartı

PARANTEZ: açılışı it; kapanışta yığın boşsa veya tepe eşleşmiyorsa FALSE; sonda yığın boş olmalı INFIX A+B POSTFIX AB+ PREFIX +AB postfix'te parantez yok, işlenenlerin sırası değişmez Öncelik: ^ (3) > * / % (2) > + - (1) INFIX → POSTFIX: işlenen → doğrudan çıktıya ( → it ) → ( gelene kadar pop → çıktı, ( atılır operatör → tepede ( olmadığı sürece, tepe DAHA YÜKSEK ya da EŞİT (sola bağlı) ise pop → çıktı; sonra it son: yığında kalanı pop → çıktı ^ sağa bağlı: eşit öncelikte POP ETME (A^B^C → ABC^^) A-B-C → AB-C- POSTFIX HESAP: sayı → it; operatör → right = pop, left = pop, it(left op right) Sonda yığında TEK değer kalmalı Kontrol: postfix'i infix'e geri çevir, sonucu karşılaştır