Stack

Stack Uygulamaları Applications

Parantez dengeleme, infix-postfix-prefix dönüşümleri, postfix değerlendirme ve undo mekanizması — algoritmalar, adım adım görseller ve tam C implementasyonları.

🗂️ Veri Yapıları 📅 21 February 2026 👁️ 9 görüntülenme

Stack Nerede Kullanılır?

Stack'in LIFO prensibi "en son açılan en önce kapanmalı" kalıbına uyan her problemi zarif bir şekilde çözer. Bu bölümde dört klasik uygulamayı inceleyeceğiz:

Bu Bölümün Haritası
🔗
Parantez Dengeleme
( ) [ ] { } eşleşme
🔄
Infix → Postfix/Prefix
Shunting Yard algoritması
📊
Postfix Değerlendirme
Hesap makinesi motoru
↩️
Undo Mekanizması
Geri alma / yineleme

Her uygulama aynı temel prensibi kullanır: bir şeyi geçici olarak stack'e koy, uygun koşul oluştuğunda çıkar ve işle. Fark yalnızca ne koyulduğu, ne zaman çıkarıldığı ve çıkarılanla ne yapıldığıdır.

Parantez Dengeleme Kontrolü

Bir ifadenin parantezlerinin doğru eşleşip eşleşmediğini kontrol etmek, compiler'ların, IDE'lerin ve metin editörlerinin en temel işlevlerinden biridir. Yanlış eşleşme derleme hatasına, sonsuz döngüye veya mantık hatasına yol açabilir. Stack bu problemi tek geçişte (single pass) ve O(n) zamanda çözer.

Problem Tanımı

Verilen bir string içindeki üç tür parantez çifti kontrol edilir: ( )[ ] ve { }. String "dengeli" (balanced) kabul edilir eğer:

✓ Dengeli (Balanced)

 {[()]}
 a*(b+c)-{d/[e+f]}
 ((()))
 boş string ""

✗ Dengesiz (Unbalanced)

 {[()} — ] eksik
 )( — sıra yanlış
 (()] — tip uyuşmuyor
 (() — kapanmamış

Algoritma

Algoritma tek bir kurala dayanır: "en son açılan parantez, en önce kapanmalıdır" — bu tam olarak LIFO'dur.

Algoritma Adımları:
1. String'i soldan sağa tara.
2. Karakter bir açma parantezi ise → stack'e push et.
3. Karakter bir kapama parantezi ise → stack'ten pop et.
   • Stack boşsa → dengesiz (fazla kapama).
   • Pop edilen eşleşmiyorsa → dengesiz (tip uyuşmazlığı).
4. Karakter parantez değilse → atla (skip).
5. String bitti ve stack boşsa → dengeli ✓
   Stack doluysa → dengesiz (kapanmamış parantezler).

Adım Adım Görsel — Dengeli: {[()]}

"{[()]}" — 6 Adımlık Takip
{
[
(
)
]
}
Adım
Karakter
İşlem
Stack
Durum
1
{
Açma → push
{
2
[
Açma → push
{ [
3
(
Açma → push
{ [ (
4
)
Kapama → pop → ( eşleşti ✓
{ [
( ↔ ) ✓
5
]
Kapama → pop → [ eşleşti ✓
{
[ ↔ ] ✓
6
}
Kapama → pop → { eşleşti ✓
boş
{ ↔ } ✓
String bitti, stack boş → Dengeli ✓

Adım Adım Görsel — Dengesiz: {[(])}

"{[(])}" — Tip Uyuşmazlığı Tespiti
{
[
(
]
)
}
Adım
Karakter
İşlem
Stack
Durum
1
{
Açma → push
{
2
[
Açma → push
{ [
3
(
Açma → push
{ [ (
4
]
Kapama → pop → ( ≠ ] ✗
( ↔ ] ✗ HATA
⛔ Adım 4'te duruldu: pop edilen "(" ile gelen "]" eşleşmiyor → Dengesiz

Adım Adım Görsel — Dengesiz: (()

"(()" — Kapanmamış Parantez
(
(
)
Adım
Karakter
İşlem
Stack
Durum
1
(
Açma → push
(
2
(
Açma → push
( (
3
)
Kapama → pop → ( eşleşti ✓
(
( ↔ ) ✓
⛔ String bitti ama stack'te hâlâ "(" kaldı → Dengesiz (kapanmamış parantez)

Eşleştirme Mantığı

Bir kapama parantezi geldiğinde, stack'ten pop edilen açma parantezinin aynı türden olup olmadığını kontrol etmemiz gerekir. Bu eşleştirme basit bir yardımcı fonksiyon ile yapılır:

Eşleşme Tablosu
( ↔ )
[ ↔ ]
{ ↔ }
Açma push edilir, kapama gelince pop edilen ile karşılaştırılır

Tam C Implementasyonu

parantez_kontrol.c
#include <stdio.h>
#include <string.h>

#define MAX 256

typedef struct {
    char items[MAX];
    int  top;
} CharStack;

void olustur(CharStack* s)      { s->top = -1; }
int  isEmpty(CharStack* s)      { return s->top == -1; }
void push(CharStack* s, char c) { s->items[++s->top] = c; }
char pop(CharStack* s)           { return s->items[s->top--]; }

/* ── Açma parantezi mi? ── */
int acmaMi(char c) {
    return c == '(' || c == '[' || c == '{';
}

/* ── Kapama parantezi mi? ── */
int kapamaMi(char c) {
    return c == ')' || c == ']' || c == '}';
}

/* ── Eşleşme kontrolü ── */
int eslesiyorMu(char acma, char kapama) {
    return (acma == '(' && kapama == ')') ||
           (acma == '[' && kapama == ']') ||
           (acma == '{' && kapama == '}');
}

/* ════════════════════════════════
   ANA FONKSİYON: Parantez Dengeli mi?
   ════════════════════════════════ */
int dengeliMi(const char* ifade) {
    CharStack s;
    olustur(&s);

    for (int i = 0; ifade[i] != '\0'; i++) {
        char c = ifade[i];

        if (acmaMi(c)) {
            push(&s, c);           // Kural 2: açma → push
        }
        else if (kapamaMi(c)) {
            // Kural 3a: stack boşsa → fazla kapama
            if (isEmpty(&s)) {
                printf("  Hata: pozisyon %d — '%c' için eşleşme yok\n",
                       i, c);
                return 0;
            }

            char acma = pop(&s);
            // Kural 3b: tip uyuşmazlığı
            if (!eslesiyorMu(acma, c)) {
                printf("  Hata: pozisyon %d — '%c' ile '%c' eşleşmiyor\n",
                       i, acma, c);
                return 0;
            }
        }
        // Kural 4: parantez değilse → atla
    }

    // Kural 5: string bitti, stack boş mu?
    if (!isEmpty(&s)) {
        printf("  Hata: %d adet kapanmamış parantez\n", s.top + 1);
        return 0;
    }

    return 1;  // Dengeli ✓
}

Demo Program

demo — main()
int main() {
    const char* testler[] = {
        "{[()]}",                  // dengeli
        "a*(b+c)-{d/[e+f]}",       // dengeli (parantez dışı karakterler)
        "((()))",                   // dengeli
        "",                         // dengeli (boş string)
        "{[(])}",                   // dengesiz: tip uyuşmazlığı
        ")(",                       // dengesiz: fazla kapama
        "(()",                      // dengesiz: kapanmamış
        "([{((()))}])",             // dengeli (derin iç içe)
    };
    int n = sizeof(testler) / sizeof(testler[0]);

    for (int i = 0; i < n; i++) {
        printf("Test: \"%s\"\n", testler[i]);
        if (dengeliMi(testler[i]))
            printf("  ✓ Dengeli\n\n");
        else
            printf("  ✗ Dengesiz\n\n");
    }
    return 0;
}
program çıktısı
Test: "{[()]}"
  ✓ Dengeli

Test: "a*(b+c)-{d/[e+f]}"
  ✓ Dengeli

Test: "((()))"
  ✓ Dengeli

Test: ""
  ✓ Dengeli

Test: "{[(])}"
  Hata: pozisyon 3 — '(' ile ']' eşleşmiyor
  ✗ Dengesiz

Test: ")("
  Hata: pozisyon 0 — ')' için eşleşme yok
  ✗ Dengesiz

Test: "(()"
  Hata: 1 adet kapanmamış parantez
  ✗ Dengesiz

Test: "([{((()))}])"
  ✓ Dengeli

Karmaşıklık Analizi

ÖlçütDeğerNeden?
ZamanO(n)Her karakter tam bir kez ziyaret edilir
Push/PopEn fazla n/2Sadece parantez karakterleri stack'e girer
BellekO(n)En kötü: tamamı açma parantezi → "((((("
Pratik bellekGenellikle O(d)d = maksimum iç içe derinlik (depth)

Edge Case'ler ve Dikkat Noktaları

Üç Hata Türü
① Fazla Kapama
)(
Kapama geldi ama stack boş → eşleşecek açma yok
② Tip Uyuşmazlığı
(]
Pop edilen açma ile kapama farklı tür → ( ≠ ]
③ Kapanmamış
(()
String bitti ama stack'te açma parantezi kaldı
String'deki diğer karakterler: Parantez olmayan karakterler (harfler, rakamlar, operatörler) algoritmayı etkilemez — sadece atlanır. Bu sayede a*(b+c)-{d/[e+f]} gibi matematiksel ifadeler de doğru kontrol edilir. Bazı uygulamalar (JSON parser, HTML tag kontrolü) farklı "açma-kapama" çiftleri kullanır ama temel mantık aynıdır.

Gerçek Dünya Kullanım Alanları

Parantez dengeleme algoritması sadece akademik bir alıştırma değildir. Endüstride yaygın olarak kullanılan uygulamaları şunlardır:

Compiler / Interpreter

Kaynak koddaki parantez, süslü parantez ve köşeli parantez hatalarını derleme öncesi tespit eder. GCC, Clang gibi derleyicilerin lexer/parser aşamasının temel parçasıdır.

IDE ve Editörler

VS Code, IntelliJ gibi editörler imleç bir paranteze geldiğinde eşini vurgular. Bu "bracket matching" özelliği tam olarak bu algoritmanın gerçek zamanlı versiyonudur.

JSON / XML Doğrulama

JSON'daki { } ve [ ], XML/HTML'deki <tag> </tag> çiftleri aynı dengeleme prensibiyle kontrol edilir. Hatalı iç içe yapı → geçersiz dosya.

Matematik Motoru

Hesap makineleri ve CAS sistemleri kullanıcının girdiği ifadeyi değerlendirmeden önce parantez kontrolü yapar. Dengesizse hata mesajı verir.
Bir sonraki uygulama: Parantez dengeleme stack'in en basit uygulamasıdır. Bir sonraki bölümde daha karmaşık bir probleme geçeceğiz: matematiksel ifadeleri infix notasyonundan postfix'e (ters Polonya notasyonu) dönüştürmek — Dijkstra'nın ünlü Shunting Yard algoritması ile.

Infix → Postfix Dönüşümü

Matematiksel ifadeleri günlük hayatta infix notasyonuyla yazarız: operatör iki operandın arasındadır (a + b). Ancak bilgisayarlar ve hesap makineleri ifadeleri değerlendirmek için genellikle postfix (Ters Polonya Notasyonu — Reverse Polish Notation, RPN) kullanır çünkü postfix'te parantez gerekmez ve operatör önceliği belirsizliği yoktur.

Üç Notasyon Biçimi

Aynı İfade — Üç Farklı Notasyon
Infix:
a + b * c
Postfix:
a b c * +
Prefix:
+ a * b c
Infix: operatör arada | Postfix: operatör sonra | Prefix: operatör önce

Infix

(a + b) * c
a + b * c
((a + b) * (c - d)) / e
İnsan dostu ama parantez ve öncelik kuralları gerektirir

Postfix (RPN)

a b + c *
a b c * +
a b + c d - * e /
Parantez yok, belirsizlik yok, soldan sağa taranır

Prefix (PN)

* + a b c
+ a * b c
/ * + a b - c d e
Lisp/Scheme'de doğal gösterim
Neden postfix? Postfix ifadeyi değerlendirmek son derece basittir: soldan sağa oku, operand görürsen stack'e koy, operatör görürsen iki operand pop et, sonucu push et. Paranteze, önceliğe veya ilişkilendirme yönüne (associativity) bakmanıza gerek yoktur. Bu yüzden HP hesap makineleri, Forth dili ve JVM bytecode hep postfix kullanır.

Operatör Öncelik Tablosu

Shunting Yard algoritması, infix ifadeyi postfix'e dönüştürürken hangi operatörün önce çıkacağına karar vermek için bir öncelik tablosu kullanır. Yüksek öncelikli operatör, düşük öncelikliden önce çıktıya yazılır.

OperatörÖncelikİlişkilendirmeAçıklama
^3 (en yüksek)Sağdan sola →←Üs alma: 2^3^2 = 2^(3^2) = 512
* /2Soldan sağa ←→Çarpma, bölme
+ -1 (en düşük)Soldan sağa ←→Toplama, çıkarma
(0Stack'te engel görevi görür
İlişkilendirme (associativity) neden önemli? Aynı öncelikli iki operatör yan yana geldiğinde hangi yöne gruplanacağını belirler. a - b - c soldan sağa → (a-b)-c. Ama 2^3^2 sağdan sola → 2^(3^2) = 512, yoksa (2^3)^2 = 64 olurdu. Algoritma: sol ilişkilendirmede ≥ ile pop, sağ ilişkilendirmede sadece > ile pop.

Shunting Yard Algoritması

Edsger Dijkstra'nın 1961'de geliştirdiği bu algoritma, adını tren istasyonlarındaki manevra alanından alır: trenler (operatörler) yan hatlarda (stack) bekletilir, uygun sıra gelince ana hatta (output) yönlendirilir.

Shunting Yard — Kurallar:
1. Operand (sayı/değişken) → doğrudan çıktıya yaz.
2. Açma parantezi ( → stack'e push et.
3. Kapama parantezi ) → stack'ten ( görene kadar pop et, hepsini çıktıya yaz. ('i at.
4. Operatör → stack'in tepesindeki operatör daha yüksek öncelikli (veya aynı öncelikli + sol ilişkilendirmeli) olduğu sürece pop edip çıktıya yaz. Sonra gelen operatörü push et.
5. String bitince stack'te kalan tüm operatörleri çıktıya yaz.

Adım Adım — a + b * c

"a + b * c" → Postfix: "a b c * +"
#
Token
İşlem
Stack
Çıktı
Neden?
1
a
→ çıktı
boş
a
Operand → doğrudan çıktı
2
+
push
+
a
Stack boş → push
3
b
→ çıktı
+
a b
Operand → doğrudan çıktı
4
*
push
+ *
a b
* (önc=2) > + (önc=1) → push
5
c
→ çıktı
+ *
a b c
Operand → doğrudan çıktı
6
SON
pop all
boş
a b c * +
Kalan operatörleri çıktıya
Sonuç: a b c * + → önce b*c hesaplanır, sonra a ile toplanır ✓

Adım Adım — (a + b) * c - d / e

"(a + b) * c - d / e" → Postfix: "a b + c * d e / -"
#
Token
İşlem
Stack
Çıktı
Neden?
1
(
push
(
Kural 2: ( → push
2
a
→ çıktı
(
a
Operand
3
+
push
( +
a
Top = ( → engel, push
4
b
→ çıktı
( +
a b
Operand
5
)
pop→(
boş
a b +
Kural 3: ( bulana kadar pop
6
*
push
*
a b +
Stack boş → push
7
c
→ çıktı
*
a b + c
Operand
8
-
pop *push -
-
a b + c *
- (1) ≤ * (2) → pop *, push -
9
d
→ çıktı
-
a b + c * d
Operand
10
/
push
- /
a b + c * d
/ (2) > - (1) → push
11
e
→ çıktı
- /
a b + c * d e
Operand
12
SON
pop all
boş
a b + c * d e / -
Kalan: / sonra -
Sonuç: a b + c * d e / - → ((a+b)*c) - (d/e) ✓

Adım Adım — a ^ b ^ c (Sağ ilişkilendirme)

"a ^ b ^ c" → Postfix: "a b c ^ ^" (sağdan sola!)
#
Token
İşlem
Stack
Çıktı
Neden?
1
a
→ çıktı
boş
a
Operand
2
^
push
^
a
Stack boş → push
3
b
→ çıktı
^
a b
Operand
4
^
push
^ ^
a b
^ sağ ilişk. → aynı önc. pop etmez
5
c
→ çıktı
^ ^
a b c
Operand
6
SON
pop all
boş
a b c ^ ^
İlk ^ sonra ikinci ^
Sonuç: a b c ^ ^ → a ^ (b ^ c) — sağdan sola doğru ✓
Eğer sol ilişkilendirmeli olsaydı: a b ^ c ^ → (a ^ b) ^ c olurdu

Tam C Implementasyonu

infix_to_postfix.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX 256

/* ── Stack ── */
typedef struct { char items[MAX]; int top; } CharStack;
void olustur(CharStack* s)       { s->top = -1; }
int  isEmpty(CharStack* s)       { return s->top == -1; }
void push(CharStack* s, char c)  { s->items[++s->top] = c; }
char pop(CharStack* s)            { return s->items[s->top--]; }
char peek(CharStack* s)           { return s->items[s->top]; }

/* ── Öncelik ── */
int oncelik(char op) {
    switch (op) {
        case '+': case '-': return 1;
        case '*': case '/': return 2;
        case '^':           return 3;
        default:            return 0;
    }
}

/* ── Sağ ilişkilendirmeli mi? ── */
int sagIliskilendirme(char op) {
    return op == '^';
}

/* ════════════════════════════════
   INFIX → POSTFIX  (Shunting Yard)
   ════════════════════════════════ */
void infixToPostfix(const char* infix, char* postfix) {
    CharStack s;
    olustur(&s);
    int j = 0;  // postfix indeksi

    for (int i = 0; infix[i] != '\0'; i++) {
        char c = infix[i];

        // Boşlukları atla
        if (c == ' ') continue;

        // Kural 1: Operand → çıktıya
        if (isalnum(c)) {
            postfix[j++] = c;
        }
        // Kural 2: ( → push
        else if (c == '(') {
            push(&s, c);
        }
        // Kural 3: ) → ( bulana kadar pop
        else if (c == ')') {
            while (!isEmpty(&s) && peek(&s) != '(')
                postfix[j++] = pop(&s);
            if (!isEmpty(&s)) pop(&s);  // ( 'yi at
        }
        // Kural 4: Operatör
        else {
            while (!isEmpty(&s) && peek(&s) != '(' &&
                   (oncelik(peek(&s)) > oncelik(c) ||
                    (oncelik(peek(&s)) == oncelik(c) &&
                     !sagIliskilendirme(c))))
            {
                postfix[j++] = pop(&s);
            }
            push(&s, c);
        }
    }

    // Kural 5: Kalan operatörleri boşalt
    while (!isEmpty(&s))
        postfix[j++] = pop(&s);

    postfix[j] = '\0';
}

Demo Program

demo — main()
int main() {
    const char* testler[] = {
        "a+b*c",
        "(a+b)*c",
        "(a+b)*c-d/e",
        "a^b^c",
        "a+b*(c^d-e)^(f+g*h)-i",
    };
    int n = sizeof(testler) / sizeof(testler[0]);

    for (int i = 0; i < n; i++) {
        char sonuc[MAX];
        infixToPostfix(testler[i], sonuc);
        printf("Infix:   %s\n", testler[i]);
        printf("Postfix: %s\n\n", sonuc);
    }
    return 0;
}
program çıktısı
Infix:   a+b*c
Postfix: abc*+

Infix:   (a+b)*c
Postfix: ab+c*

Infix:   (a+b)*c-d/e
Postfix: ab+c*de/-

Infix:   a^b^c
Postfix: abc^^

Infix:   a+b*(c^d-e)^(f+g*h)-i
Postfix: abcd^e-fgh*+^*+i-

Karmaşıklık Analizi — Shunting Yard

ÖlçütDeğerNeden?
ZamanO(n)Her token tam bir kez okunur, her operatör en fazla bir kez push + bir kez pop
BellekO(n)En kötü: tüm tokenler operatör → hepsi stack'te
Çıktı boyutuO(n)Parantezler hariç tüm tokenler çıktıya geçer

Infix → Prefix Dönüşümü

Prefix notasyonu (Polonya Notasyonu), Lisp ve Scheme gibi dillerin doğal gösterim biçimidir. Infix'ten prefix'e dönüşüm, postfix dönüşümünün "ayna" versiyonudur: ifadeyi sağdan sola tarayarak aynı algoritmayı uygularsınız, ardından sonucu ters çevirirsiniz.

Algoritma

Infix → Prefix — 4 Adım:
1. Infix ifadeyi ters çevir. Çevirirken ( ile )'yi, [ ile ]'yi takas et.
2. Ters çevrilmiş ifadeye Shunting Yard uygula (postfix'e dönüştür).
   • Tek fark: ilişkilendirme koşulunu ters çevir (sol ilişkilendirmede > kullan, ≥ değil).
3. Elde edilen postfix sonucu ters çevir.
4. Sonuç = Prefix ifade.

Adım Adım — (a + b) * c

"(a + b) * c" → Prefix: "* + a b c"
① Orijinal infix:
(a + b) * c
② Ters çevir + parantez takas:
c * (b + a)
( → ), ) → ( takası yapıldı
③ Shunting Yard → Postfix:
c b a + *
④ Ters çevir → Prefix:
* + a b c

Tam C Implementasyonu

infix_to_prefix.c
/* ── String'i ters çevir ── */
void tersCevir(char* str) {
    int n = strlen(str);
    for (int i = 0; i < n / 2; i++) {
        char tmp = str[i];
        str[i] = str[n - 1 - i];
        str[n - 1 - i] = tmp;
    }
}

/* ── Parantezleri takas et ── */
void parantezTakas(char* str) {
    for (int i = 0; str[i]; i++) {
        if      (str[i] == '(') str[i] = ')';
        else if (str[i] == ')') str[i] = '(';
    }
}

/* ════════════════════════════════
   INFIX → PREFIX
   ════════════════════════════════ */
void infixToPrefix(const char* infix, char* prefix) {
    char temp[MAX];
    strcpy(temp, infix);

    // 1. Ters çevir
    tersCevir(temp);

    // 2. Parantezleri takas et
    parantezTakas(temp);

    // 3. Shunting Yard uygula (postfix'e dönüştür)
    char postfix[MAX];
    infixToPostfix(temp, postfix);

    // 4. Sonucu ters çevir → prefix
    tersCevir(postfix);
    strcpy(prefix, postfix);
}

// Kullanım:
char sonuc[MAX];
infixToPrefix("(a+b)*c", sonuc);
printf("%s\n", sonuc);   // *+abc

Üç Notasyon — Hızlı Referans

InfixPostfixPrefix
a + ba b ++ a b
a + b * ca b c * ++ a * b c
(a + b) * ca b + c ** + a b c
a + b * c - da b c * + d -- + a * b c d
a * (b + c) / da b c + * d // * a + b c d
a ^ b ^ ca b c ^ ^^ a ^ b c
Bir sonraki adım: Artık infix'i postfix'e dönüştürebiliyoruz. Sonraki bölümde postfix ifadeyi stack ile değerlendireceğiz (evaluate) — yani gerçek bir hesap makinesi motoru inşa edeceğiz. Ardından stack'in günlük hayattaki en görünür uygulamasını inceleyeceğiz: Undo/Redo mekanizması.

Postfix İfade Değerlendirme

Önceki bölümde infix ifadeyi postfix'e dönüştürdük. Şimdi sıra postfix ifadeyi gerçekten hesaplamakta. Postfix değerlendirme, stack'in en zarif uygulamalarından biridir: parantez yok, öncelik kuralı yok, sadece soldan sağa okuyup stack'e koy veya hesapla.

Algoritma

Postfix Değerlendirme — Kurallar:
1. Postfix ifadeyi soldan sağa tara.
2. Token bir operand (sayı) ise → stack'e push et.
3. Token bir operatör ise →
   • İki operand pop et: önce sağ (b), sonra sol (a).
   • Hesapla: sonuç = a operatör b
   • Sonucu stack'e push et.
4. İfade bittiğinde stack'te kalan tek eleman = sonuç.
Pop sırası kritik! Çıkarma ve bölmede sıra önemlidir: a b - demek a - b demektir. Önce pop edilen b (sağ operand), sonra pop edilen a (sol operand). Yani sonuç = ikinci_pop OP ilk_pop. Sırayı karıştırırsanız b - a hesaplarsınız.

Adım Adım — 3 4 + 2 * 7 -

Bu ifade infix'te (3 + 4) * 2 - 7 ifadesine karşılık gelir. Beklenen sonuç: (3+4)×2−7 = 7.

"3 4 + 2 * 7 -" → Sonuç: 7
#
Token
İşlem
Stack
Hesaplama
1
3
push(3)
3
2
4
push(4)
3 4
3
+
pop 4,3 → hesapla
7
3 + 4 = 7 → push
4
2
push(2)
7 2
5
*
pop 2,7 → hesapla
14
7 * 2 = 14 → push
6
7
push(7)
14 7
7
-
pop 7,14 → hesapla
7
14 - 7 = 7 → push
Stack'te tek eleman kaldı: 7 = Sonuç ✓

Adım Adım — 5 1 2 + 4 * + 3 -

Infix karşılığı: 5 + (1 + 2) * 4 - 3. Beklenen sonuç: 5 + 3×4 − 3 = 14.

"5 1 2 + 4 * + 3 -" → Sonuç: 14
#
Token
İşlem
Stack
Hesaplama
1
5
push(5)
5
2
1
push(1)
5 1
3
2
push(2)
5 1 2
4
+
pop 2,1
5 3
1 + 2 = 3
5
4
push(4)
5 3 4
6
*
pop 4,3
5 12
3 * 4 = 12
7
+
pop 12,5
17
5 + 12 = 17
8
3
push(3)
17 3
9
-
pop 3,17
14
17 - 3 = 14
Sonuç: 14 ✓

Tam C Implementasyonu

postfix_eval.c
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>

#define MAX 256

/* ── Stack (double) ── */
typedef struct { double items[MAX]; int top; } NumStack;
void   nOlustur(NumStack* s)          { s->top = -1; }
int    nIsEmpty(NumStack* s)          { return s->top == -1; }
void   nPush(NumStack* s, double v)  { s->items[++s->top] = v; }
double nPop(NumStack* s)             { return s->items[s->top--]; }

/* ── Operatör mü? ── */
int operatorMu(char c) {
    return c == '+' || c == '-' || c == '*' || c == '/' || c == '^';
}

/* ── İşlemi uygula ── */
double hesapla(double a, double b, char op) {
    switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/':
            if (b == 0) { fprintf(stderr, "Sıfıra bölme!\n"); exit(1); }
            return a / b;
        case '^': return pow(a, b);
        default:  return 0;
    }
}

/* ════════════════════════════════
   POSTFİX DEĞERLENDİRME
   ════════════════════════════════ */
double postfixDegerlendir(const char* ifade) {
    NumStack s;
    nOlustur(&s);

    for (int i = 0; ifade[i] != '\0'; i++) {
        char c = ifade[i];

        // Boşlukları atla
        if (c == ' ') continue;

        // Sayı → stack'e push
        if (isdigit(c)) {
            double sayi = 0;
            while (isdigit(ifade[i])) {
                sayi = sayi * 10 + (ifade[i] - '0');
                i++;
            }
            i--;  // for döngüsü de artıracak
            nPush(&s, sayi);
        }
        // Operatör → iki operand pop et, hesapla, push et
        else if (operatorMu(c)) {
            double b = nPop(&s);   // ← önce sağ operand
            double a = nPop(&s);   // ← sonra sol operand
            double sonuc = hesapla(a, b, c);
            nPush(&s, sonuc);
        }
    }

    return nPop(&s);  // son eleman = sonuç
}

Demo Program

demo — main()
int main() {
    const char* testler[] = {
        "3 4 + 2 * 7 -",       // (3+4)*2-7 = 7
        "5 1 2 + 4 * + 3 -",   // 5+(1+2)*4-3 = 14
        "2 3 ^ 4 +",            // 2^3+4 = 12
        "15 7 1 1 + - / 3 * 2 1 1 + + -",  // karmaşık = 5
    };
    int n = sizeof(testler) / sizeof(testler[0]);

    for (int i = 0; i < n; i++) {
        double sonuc = postfixDegerlendir(testler[i]);
        printf("Postfix: %-35s → %.2f\n", testler[i], sonuc);
    }
    return 0;
}
program çıktısı
Postfix: 3 4 + 2 * 7 -                     → 7.00
Postfix: 5 1 2 + 4 * + 3 -                 → 14.00
Postfix: 2 3 ^ 4 +                         → 12.00
Postfix: 15 7 1 1 + - / 3 * 2 1 1 + + -    → 5.00

Tam Boru Hattı: Infix → Postfix → Sonuç

Önceki iki bölümü birleştirerek tam bir hesap makinesi motoru oluşturabiliriz. İnsan tarafından girilen infix ifade, önce postfix'e dönüştürülür, ardından değerlendirilir:

Hesap Makinesi Pipeline'ı
(3+4)*2-7
Shunting Yard
infixToPostfix()
3 4 + 2 * 7 -
Stack Eval
postfixDegerlendir()
7

Karmaşıklık Analizi

ÖlçütDeğerNeden?
ZamanO(n)Her token bir kez okunur, her sayı en fazla bir kez push + pop
BellekO(n)En kötü: tüm tokenler operand → hepsi stack'te
Pratik bellekGenellikle O(n/2)Operandlar yaklaşık tokenların yarısı

Undo / Redo Mekanizması

Stack'in günlük hayatta en çok karşılaştığımız uygulaması: geri alma (undo) ve yineleme (redo). Metin editörlerinden grafik programlarına, tarayıcılardan dosya yöneticilerine kadar neredeyse her interaktif uygulama bu mekanizmayı kullanır. Temel prensip: iki stack, biri geçmişi, diğeri "geleceği" tutar.

Çift Stack Modeli

Undo / Redo — İki Stack Mimarisi
Undo Stack (geçmiş)
Yaz "M"
Yaz "e"
Yaz "r"
Yaz "h"
← en son yapılan işlem tepede
Undo →
← Redo
Redo Stack (gelecek)
boş
boş
boş
Undo yapılınca buraya taşınır
Kurallar:
1. Yeni işlem → Undo stack'e push et. Redo stack'i temizle (yeni dal açıldı).
2. Undo → Undo stack'ten pop et, işlemi ters çevir, Redo stack'e push et.
3. Redo → Redo stack'ten pop et, işlemi tekrar uygula, Undo stack'e push et.
4. Undo stack boşsa → geri alınacak bir şey yok.
5. Redo stack boşsa → yinelenecek bir şey yok.

Adım Adım Senaryo

Bir metin editöründe "Mer" yazıp, undo ile geri alıp, redo ile tekrar yaptığımızı düşünelim:

Yazma → Undo → Undo → Redo Senaryosu
① Yaz "M" — yeni işlem
Undo: [M]
Redo: [boş]
Ekran: M
② Yaz "e" — yeni işlem
Undo: [M, e]
Redo: [boş]
Ekran: Me
③ Yaz "r" — yeni işlem
Undo: [M, e, r]
Redo: [boş]
Ekran: Mer
④ UNDO — "r" geri alındı
Undo: [M, e]
Redo: [r]
Ekran: Me
⑤ UNDO — "e" geri alındı
Undo: [M]
Redo: [r, e]
Ekran: M
⑥ REDO — "e" tekrar uygulandı
Undo: [M, e]
Redo: [r]
Ekran: Me
⑦ Yaz "s" — yeni işlem (redo stack temizlenir!)
Undo: [M, e, s]
Redo: [boş] ← temizlendi!
Ekran: Mes
Yeni işlem yapıldığında redo geçmişi silinir — "r" artık geri getirilemez

Tam C Implementasyonu

undo_redo.c
#include <stdio.h>
#include <string.h>

#define MAX 100

/* ── Karakter Stack ── */
typedef struct { char items[MAX]; int top; } CStack;
void csOlustur(CStack* s)      { s->top = -1; }
int  csIsEmpty(CStack* s)      { return s->top == -1; }
void csPush(CStack* s, char c) { s->items[++s->top] = c; }
char csPop(CStack* s)           { return s->items[s->top--]; }
void csTemizle(CStack* s)      { s->top = -1; }

/* ── Editör Durumu ── */
typedef struct {
    char   metin[MAX];   // mevcut metin
    int    uzunluk;      // metin uzunluğu
    CStack undo;         // geri alma yığını
    CStack redo;         // yineleme yığını
} Editor;

void editorOlustur(Editor* e) {
    e->metin[0] = '\0';
    e->uzunluk = 0;
    csOlustur(&e->undo);
    csOlustur(&e->redo);
}

/* ── Karakter yaz ── */
void yaz(Editor* e, char c) {
    e->metin[e->uzunluk++] = c;
    e->metin[e->uzunluk] = '\0';
    csPush(&e->undo, c);
    csTemizle(&e->redo);   // yeni dal → redo sıfırla
    printf("  Yaz '%c' → \"%s\"\n", c, e->metin);
}

/* ── Geri al ── */
void geriAl(Editor* e) {
    if (csIsEmpty(&e->undo)) {
        printf("  Undo: geri alınacak işlem yok\n");
        return;
    }
    char c = csPop(&e->undo);
    e->metin[--e->uzunluk] = '\0';
    csPush(&e->redo, c);
    printf("  Undo '%c' → \"%s\"\n", c, e->metin);
}

/* ── Yinele ── */
void yinele(Editor* e) {
    if (csIsEmpty(&e->redo)) {
        printf("  Redo: yinelenecek işlem yok\n");
        return;
    }
    char c = csPop(&e->redo);
    e->metin[e->uzunluk++] = c;
    e->metin[e->uzunluk] = '\0';
    csPush(&e->undo, c);
    printf("  Redo '%c' → \"%s\"\n", c, e->metin);
}

Demo Program

demo — main()
int main() {
    Editor e;
    editorOlustur(&e);

    printf("=== Yazma ===\n");
    yaz(&e, 'M');
    yaz(&e, 'e');
    yaz(&e, 'r');

    printf("\n=== Undo ===\n");
    geriAl(&e);
    geriAl(&e);

    printf("\n=== Redo ===\n");
    yinele(&e);

    printf("\n=== Yeni yazma (redo silinir) ===\n");
    yaz(&e, 's');
    yinele(&e);   // redo boş

    return 0;
}
program çıktısı
=== Yazma ===
  Yaz 'M' → "M"
  Yaz 'e' → "Me"
  Yaz 'r' → "Mer"

=== Undo ===
  Undo 'r' → "Me"
  Undo 'e' → "M"

=== Redo ===
  Redo 'e' → "Me"

=== Yeni yazma (redo silinir) ===
  Yaz 's' → "Mes"
  Redo: yinelenecek işlem yok
Gerçek uygulamalarda: Yukarıdaki örnek basitleştirilmiş bir versiyondur — her işlem tek bir karakter ekleme/silme. Gerçek editörlerde (VS Code, Photoshop vb.) her "işlem" bir komut nesnesidir (Command Pattern) ve sadece metni değil silme, biçimlendirme, taşıma gibi karmaşık işlemleri de kapsar. Ama temel yapı aynıdır: iki stack, birinden çıkan diğerine girer.

Dört Uygulama — Özet Tablosu

UygulamaStack'te ne var?Push ne zaman?Pop ne zaman?Karmaşıklık
Parantez DengelemeAçma parantezleri( [ { geldiğinde) ] } geldiğindeO(n) zaman, O(n) bellek
Infix → PostfixOperatörler ve (Operatör veya ( geldiğindeDaha yüksek/eşit öncelikli operatör geldiğindeO(n) zaman, O(n) bellek
Postfix DeğerlendirmeOperandlar (sayılar)Sayı okunduğundaOperatör okunduğunda (2 pop)O(n) zaman, O(n) bellek
Undo / Redoİşlemler (komutlar)Yeni işlem yapıldığındaCtrl+Z (undo) veya Ctrl+Y (redo)O(1) per işlem
Ortak kalıp: Dört uygulamanın hepsinde aynı yapı tekrar eder — bir şeyi geçici olarak stack'e koy, uygun koşul oluştuğunda çıkar ve işle. Fark sadece ne koyulduğu (parantez, operatör, sayı, komut), ne zaman çıkarıldığı (eşleşme, öncelik, operatör, kullanıcı isteği) ve çıkarılanla ne yapıldığıdır (karşılaştırma, çıktıya yazma, hesaplama, geri alma).

Sonuç

Stack basit bir veri yapısıdır — sadece push ve pop. Ama bu iki operasyon, LIFO prensibi ile birleştiğinde şaşırtıcı derecede güçlü çözümler üretir. Parantez dengeleme derleyicilerin temelini oluşturur. Shunting Yard algoritması ifade ayrıştırma motorlarının kalbindedir. Postfix değerlendirme hesap makinelerini ve JVM'i çalıştırır. Undo mekanizması her interaktif uygulamanın vazgeçilmezidir.

Bu dört uygulamanın ötesinde, stack call stack (fonksiyon çağrı yığını), DFS (derinlik öncelikli arama), string ters çevirme, tarayıcı geri butonu ve recursion'ın iteratif hale dönüştürülmesinde de kritik rol oynar. Stack'i anlamak, bilgisayar biliminin temel yapı taşlarından birini kavramak demektir.

Bir sonraki bölümde stack'in kardeş veri yapısına geçeceğiz: Queue (Kuyruk) — FIFO prensibi, circular queue, priority queue ve BFS uygulamaları.

Bölüm 4.3 tamamlandı. Stack uygulamalarını öğrendik: parantez dengeleme (compiler), infix↔postfix↔prefix dönüşümleri (Shunting Yard), postfix değerlendirme (hesap makinesi motoru) ve undo/redo (çift stack). Tüm uygulamaların ortak kalıbı: stack'e koy → koşul oluşunca çıkar → işle.
← Veri Yapıları