Bölüm 4: Yığınlar

Veri Yapıları
LIFO ve temel işlemlerDizi ile yığınBağlı liste ile yığın Parantez kontrolüSonraki büyük elemanHanoi kuleleri Stock spanInfix → postfix / prefixPostfix değerlendirme
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

LIFO modeli

Yığının son giren ilk çıkar davranışını açıklamak.

Gerçekleme

Yığını dizi ve bağlı liste kullanarak gerçekleştirmek.

Uygulamalar

Yığınları ifade çözümleme ve çağrı yönetiminde kullanmak.

Maliyet analizi

Temel yığın işlemlerinin zaman ve alan maliyetlerini değerlendirmek.

Yığın ADT

Yığın (Stack)

  • Ekleme ve çıkarma yalnızca bir uçtan, yığının üstünden (top) yapılır.
  • Üst üste konmuş tabaklar ya da kutular gibi düşünülebilir.
  • En alttaki öğeye ulaşmak için üstündeki öğeler tek tek çıkarılmalıdır.
  • LIFO (Last-In, First-Out · son giren ilk çıkar) ya da eşdeğeri FILO (ilk giren son çıkar) mantığıyla çalışır.
Farklı renklerde üst üste konmuş tabaklardan oluşan bir yığın.

Yeni tabak en üste konur, alınan tabak da en üstten alınır.

Geri al (undo)Son yapılan işlem ilk geri alınır.
Tarayıcı “geri”En son ziyaret edilen sayfaya dönülür.
Fonksiyon çağrılarıEn son çağrılan fonksiyon ilk biter.

Yığın ADT

Temel İşlemler

İşlemAçıklama
push(x)Üste yeni öğe x ekler.
pop()Üstteki öğeyi çıkarır ve döndürür.
top()Üstteki öğeyi çıkarmadan döndürür (Java: peek()).
isEmpty()Yığın boş mu?
size()Öğe sayısını döndürür.
Kullanıcı yalnızca üstteki öğeyi görür; ortadaki öğelere doğrudan erişim yoktur.

Yığın ADT

push ve pop Adım Adım

yığınboşİşlemDönen değersize

Başlangıçta yığın boş: size = 0, üstte öğe yok.

10yığın← topİşlemDönen değersizepush(10)—1

push(10): 10 boş yığına eklenir; üst (top) artık 10.

1015yığın← topİşlemDönen değersizepush(10)—1push(15)—2

push(15): 15, 10'un üstüne konur.

101520yığın← topİşlemDönen değersizepush(10)—1push(15)—2push(20)—3

push(20): 20 en üste konur; size = 3.

101520yığın← topİşlemDönen değersizepush(10)—1push(15)—2push(20)—3top()203

top(): üstteki 20 okunur ama çıkarılmaz.

1015yığın← top20← çıkanİşlemDönen değersizepush(10)—1push(15)—2push(20)—3top()203pop()202

pop(): son giren 20 ilk çıkar (LIFO).

10yığın← top15← çıkanİşlemDönen değersizepush(10)—1push(15)—2push(20)—3top()203pop()202pop()151

pop(): 15 çıkar; üst artık 10.

yığınboş10← çıkanİşlemDönen değersizepush(10)—1push(15)—2push(20)—3top()203pop()202pop()151pop()100

pop(): 10 çıkar; yığın yeniden boş.

yığınboşalt taşma!İşlemDönen değersizepush(10)—1push(15)—2push(20)—3top()203pop()202pop()151pop()100pop()hata!0

Boş yığında pop() → alt taşma (underflow) hatası.

Yığın ADT

İşlemlerin Algoritmaları (Dizi ile)

Yığın bir dizide tutulur; top üstteki öğenin indisidir ve boş yığında top = −1.

push(x)
başla
  eğer yığın doluysa   // top = kap−1
    hata ver: üst taşma (overflow)
  değilse
    top değerini 1 artır
    yığın[top] ← x
son
pop()
başla
  eğer yığın boşsa     // top = −1
    hata ver: alt taşma (underflow)
  değilse
    x ← yığın[top]; top 1 azalt
    x değerini döndür
son
top()
başla
  eğer yığın boşsa hata ver
  değilse yığın[top] döndür
son
isEmpty() · size()
isEmpty: top = −1 ise true, değilse false
size:    top + 1 döndür

Yığın ADT

Zaman ve Alan Karmaşıklığı

İşlemZamanNeden?
pushO(1)yalnızca üst değişir
popO(1)yalnızca üst değişir
topO(1)üst doğrudan bilinir
isEmpty, sizeO(1)sayaç / indis tutulur
bir öğeyi aramaO(n)öğeler tek tek çıkarılmalı
  • Temel işlemlerin süresi yığındaki öğe sayısından bağımsızdır.
  • n öğeli yığın O(n) bellek kullanır; işlemler ek bellek gerektirmez (O(1)).
  • Dinamik dizide kapasite dolunca dizi büyütülür: push amortize O(1).
Yığın, erişimi kısıtlayarak hız kazanır: “yalnızca üst” kuralı sayesinde hiçbir temel işlem öğeleri kaydırmaz ya da taramaz.

Gerçekleme

Yığın Türleri

Sabit Boyutlu Yığın (Fixed Size Stack)
  • Kapasitesi baştan belirlenir; büyüyemez, küçülemez.
  • Dolu yığına ekleme → üst taşma (overflow).
  • Boş yığından çıkarma → alt taşma (underflow).
  • Arka planda sabit boyutlu bir dizi kullanır.
Dinamik Boyutlu Yığın (Dynamic Size Stack)
  • Çalışma sırasında büyüyüp küçülebilir.
  • Dolunca kapasitesini otomatik artırır (ör. iki katına çıkarır).
  • Öğe sayısı kapasiteye göre çok azalınca küçülebilir.
  • Dinamik dizi ya da bağlı liste ile gerçekleştirilir.

Boş yığından çıkarma (alt taşma) her iki türde de hatadır; üst taşma yalnızca sabit boyutlu yığında görülür.

Gerçekleme

Yığın Gerçekleme Yöntemleri

Yığın ADT'si bir dizi (array) ya da bağlı liste (linked list) ile gerçekleştirilebilir.

10015120234top = 2dizi: push → top++ , pop → top−−201510nulltopbağlı liste: push/pop listenin başında
Dizi Tabanlı push: top indisi artırılır, öğe dizi[top]'a yazılır.
pop: dizi[top] alınır, sonra top azaltılır.
Bağlı Liste Tabanlı push: yeni düğüm listenin başına eklenir.
pop: baştaki düğüm çıkarılır, değeri döndürülür.

Gerçekleme

Dizi Tabanlı Yığın

Bu yaklaşıma sınırlı yığın (bounded stack) da denir: kapasite oluşturulurken belirlenir ve değişmez.

Avantajları

  • Basit ve kolay uygulanır.
  • Düğüm başına referans tutulmaz → bellek tasarrufu.
  • Bellek kullanımı sıkı biçimde kontrol edilir; belleği doldurmaya yönelik saldırılara karşı daha güvenlidir.
  • Öğeler ardışık → önbellek (cache) dostu.

Dikkat edilecekler

  • Eklemeden önce yığının dolu olmadığı kontrol edilmelidir.
  • Kapasite az seçilirse taşma, çok seçilirse boşa bellek.
  • Nesne tutan dizide çıkarılan hücreye null atanmalıdır; yoksa nesne bellekte gereksiz yere kalır.

Gerçekleme

Dizi Tabanlı Yığın: Java

public class DiziYigin<E> {
    private E[] veri;
    private int top = -1;                    // -1 → boş yığın
    public DiziYigin(int kapasite) { veri = (E[]) new Object[kapasite]; }
    public boolean isEmpty() { return top == -1; }
    public int size()        { return top + 1; }
    public E top()           { return isEmpty() ? null : veri[top]; }
    public void push(E x) {
        if (top == veri.length - 1) throw new IllegalStateException("Taşma");
        veri[++top] = x;
    }
    public E pop() {
        if (isEmpty()) throw new IllegalStateException("Alt taşma");
        E x = veri[top];
        veri[top--] = null;                  // referansı bırak
        return x;
    }
}

Genel (generic) dizi doğrudan oluşturulamadığı için Object[] dönüştürülür (derleyici “unchecked” uyarısı verir). Tüm işlemler O(1).

Gerçekleme

Bağlı Liste Tabanlı Yığın

  • Öğeler birbirine bağlı düğümlerden oluşan bir zincirde tutulur → esnek boyut.
  • Boyut, çalışma zamanında ihtiyaca göre değişir; üst taşma olmaz (bellek bitene kadar).
  • Her düğümde bir referans (sonraki) tutulduğu için ek bellek kullanır.
  • Rastgele erişim yoktur, ama üstteki öğeye (listenin başı) erişim hızlı ve basittir.
  • Ekleme ve çıkarma listenin başında yapıldığı için O(1)'dir.
class Dugum {
    int veri;
    Dugum sonraki;
    Dugum(int veri) { this.veri = veri; }   // sonraki = null
}

Neden başa ekliyoruz? Tek yönlü listede sondan silmek için sondan bir önceki düğümü bulmak gerekir → O(n).

Gerçekleme

Bağlı Liste ile push

topnulluzunluk = 0
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

Başlangıç: top = null, uzunluk = 0 (boş yığın).

10nulltopnullgeciciuzunluk = 0push(10)
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

Yeni düğüm oluşturulur; gecici ona işaret eder (sonraki = null).

10nulltopnullgeciciuzunluk = 0push(10)
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

gecici.sonraki = top: top null olduğundan sonraki yine null.

10nulltopgeciciuzunluk = 0push(10)
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

top = gecici: top artık yeni düğümü gösterir.

10nulltopgeciciuzunluk = 1push(10)
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

uzunluk++ → 1. Metot biter, gecici yerel değişkeni kaybolur.

10null15nulltopgeciciuzunluk = 1push(15)
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

push(15): yeni düğüm 15 oluşturulur, gecici onu gösterir.

10null15topgeciciuzunluk = 1push(15)
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

gecici.sonraki = top: 15, eski üst olan 10'a bağlanır.

10null15topgeciciuzunluk = 1push(15)
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

top = gecici: yeni üst 15. Listenin başına ekleme → O(1).

10null15topuzunluk = 2push(15)
// Örnek değişkenleri
Dugum top;         // yığının üstü
int uzunluk;

public void push(int veri) {
    Dugum gecici = new Dugum(veri);
    gecici.sonraki = top;
    top = gecici;
    uzunluk++;
}

uzunluk++ → 2. Yığın (üstten alta): 15, 10.

Gerçekleme

Bağlı Liste ile pop

10null15topuzunluk = 2pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

pop(): önce boşluk kontrolü; top null değil, devam edilir.

10null15topuzunluk = 2sonuc = 15pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

sonuc = top.veri → 15 saklanır.

10null15topuzunluk = 2sonuc = 15pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

top = top.sonraki: top bir sonraki düğüme (10) geçer.

10null15topuzunluk = 1sonuc = 15pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

uzunluk-- → 1.

10nulltopuzunluk = 1sonuc = 15pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

15 döndürülür. Referansı kalmayan düğümü çöp toplayıcı temizler.

10nulltopuzunluk = 1sonuc = 10pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

İkinci pop(): sonuc = top.veri → 10.

10nulltopnulluzunluk = 1sonuc = 10pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

top = top.sonraki → top = null.

topnulluzunluk = 0sonuc = 10pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

uzunluk-- → 0 ve 10 döndürülür. Yığın boş.

topnulluzunluk = 0pop()
public int pop() {
    if (top == null)
        throw new RuntimeException("boş");
    int sonuc = top.veri;
    top = top.sonraki;
    uzunluk--;
    return sonuc;
}

Boş yığında pop(): top == null → istisna (alt taşma), NullPointerException değil.

Gerçekleme

Dizi mi, Bağlı Liste mi?

ÖlçütDizi tabanlıBağlı liste tabanlı
Boyutsabit kapasite (dinamik dizide büyütülür)esnek, çalışma zamanında değişir
push / popO(1) (büyütmede amortize O(1))O(1)
Taşmaüst taşma mümkünyalnızca bellek biterse
Öğe başına bellekyalnızca veriveri + sonraki referansı
Boş yerkullanılmayan kapasiteyok
JavaArrayDeque, Stack (Vector tabanlı)LinkedList (Deque olarak)
Java'da yeni kodda Deque<Integer> y = new ArrayDeque<>(); ile push/pop/peek önerilir; eski Stack sınıfı senkronize olduğu için daha yavaştır.

Yığın Uygulamaları

Yığınların Kullanıldığı Yerler

İfadelerInfix, prefix, postfix dönüşümü ve değerlendirmesi; parantez kontrolü.
Geri al / yineleMetin düzenleyicilerde undo ve redo işlemleri.
Tarayıcı geçmişi“Geri” ve “ileri” düğmeleri iki yığınla yönetilir.
Çağrı yığınıFonksiyon çağrıları, yerel değişkenler ve dönüş adresleri (call stack).
Geri izlemeLabirentte yol bulma, N-Vezir, Şövalye Turu (backtracking).
AlgoritmalarHanoi kuleleri, ağaç gezinme, stock span, histogram problemleri.
Çizge algoritmalarıDerinlik öncelikli arama, topolojik sıralama, güçlü bağlantılı bileşenler.
Dize ters çevirmeKarakterler sırayla eklenir, ters sırayla çıkar.
Sonraki büyük elemanHer eleman için sağındaki ilk büyük değer.

Yığın Uygulamaları

Karakter Dizisini (String) Tersine Çevirme

str = "ABCD"A0B1C2D3karakterleryigit

Başlangıç: str = "ABCD", karakter dizisi oluşturulur, yığın boş.

str = "ABCD"A0B1C2D3karakterlerc = 'A'Ayigit← top

1. döngü: yigit.push('A')

str = "ABCD"A0B1C2D3karakterlerc = 'B'AByigit← top

1. döngü: yigit.push('B')

str = "ABCD"A0B1C2D3karakterlerc = 'C'ABCyigit← top

1. döngü: yigit.push('C')

str = "ABCD"A0B1C2D3karakterlerc = 'D'ABCDyigit← top

1. döngü: yigit.push('D')

str = "ABCD"D0B1C2D3karakterleri = 0DçıkanABCyigit← top

2. döngü, i = 0: karakterler[0] = yigit.pop() → 'D'

str = "ABCD"D0C1C2D3karakterleri = 1CçıkanAByigit← top

2. döngü, i = 1: karakterler[1] = yigit.pop() → 'C'

str = "ABCD"D0C1B2D3karakterleri = 2BçıkanAyigit← top

2. döngü, i = 2: karakterler[2] = yigit.pop() → 'B'

str = "ABCD"D0C1B2A3karakterleri = 3Açıkanyigit

i = 3: son karakter 'A' yerleşir → sonuç "DCBA".

String tersCevir(String str) {
    Stack<Character> yigit = new Stack<>();
    char[] karakterler = str.toCharArray();
    for (char c : karakterler)
        yigit.push(c);
    for (int i = 0; i < str.length(); i++)
        karakterler[i] = yigit.pop();
    return new String(karakterler);
}
Tüm karakterler önce yığına girer; sonra çıkarken ters sırayla gelir. Zaman ve ek bellek O(n).

Yığın Uygulamaları

Parantez Kontrolü

S dizisi yalnızca ( ) { } [ ] karakterlerinden oluşsun. S geçerlidir ancak ve ancak:

  1. her açık parantez aynı tip bir parantezle kapatılmışsa ve
  2. parantezler doğru sırada kapatılmışsa (en son açılan ilk kapanır → LIFO!).
Algoritma: eşleşmemiş açık parantezler yığında tutulur. S baştan sona gezilir:
  • Açık parantez → yığına push.
  • Kapalı parantez → yığın boşsa geçersiz; değilse pop, çıkan aynı tipte değilse geçersiz.
  • S bitince yığın boşsa geçerli, değilse geçersiz (kapanmamış parantez var).
{[()]}geçerli
([)]sıra yanlış
(()kapanmamış
())fazla kapanış

Yığın Uygulamaları

Parantez Kontrolü: S = {{[]}()}[]

#GirdiİşlemYığın (alt → üst)
1{push{
2{push{ {
3[push{ { [
4]pop [{ {
5}pop {{
6(push{ (
7)pop ({
8}pop {(boş)
9[push[
10]pop [(boş)
sonyığın boş → geçerli
Geçersiz: ([)]( push, [ push, ) gelince pop → [ çıkar: tip uyuşmaz → false.
Geçersiz: (()Dize bitti ama yığında ( kaldı → false.
Geçersiz: ())Son ) geldiğinde yığın boş → false.

Yığın Uygulamaları

Parantez Kontrolü: Java

boolean gecerliMi(String s) {
    Deque<Character> yigin = new ArrayDeque<>();
    for (char c : s.toCharArray()) {
        if (c == '(' || c == '[' || c == '{') {
            yigin.push(c);
        } else {
            if (yigin.isEmpty()) return false;
            char acik = yigin.pop();
            if ((c == ')' && acik != '(') ||
                (c == ']' && acik != '[') ||
                (c == '}' && acik != '{'))
                return false;
        }
    }
    return yigin.isEmpty();
}
Boş yığın kontrolüOrijinal kodda yoktu: "())" ya da "]" girdisinde stack.pop() boş yığında çağrılır ve EmptyStackException fırlatılır.
KarmaşıklıkHer karakter bir kez işlenir: zaman O(n), en kötü durumda yığın O(n) bellek.

Yığın Uygulamaları

Sonraki Büyük Eleman (Next Greater Element)

Dizideki her eleman için sağında kalan, kendisinden büyük ilk elemanı bul; yoksa −1.

dizi473481
sonuc7848−1−1
  • Kaba kuvvet: her eleman için sağını tara → O(n²).
  • Yığınla: dizi sağdan sola gezilir; yığında yalnızca “aday” büyük değerler kalır → O(n).
  • Yığın, alttan üste doğru azalan sırada kalır (monoton yığın).
int[] sonrakiBuyuk(int[] dizi) {
    int[] sonuc = new int[dizi.length];
    Stack<Integer> yigit = new Stack<>();
    for (int i = dizi.length-1; i >= 0; i--) {
        while (!yigit.isEmpty() &&
               yigit.peek() <= dizi[i])
            yigit.pop();
        sonuc[i] = yigit.isEmpty()
                 ? -1 : yigit.peek();
        yigit.push(dizi[i]);
    }
    return sonuc;
}

Yığın Uygulamaları

Sonraki Büyük Eleman: Adım Adım

407132438415dizi[]012345sonuc[]yigit

Dizi sağdan sola taranır; yığında sağdaki olası “sonraki büyük” adaylar tutulur.

407132438415dizi[]i01234-15sonuc[]yığın boş → sonuc[5] = -1push 11yigit← top

i = 5: dizi[5] = 1 için cevap yığının üstünde.

407132438415dizi[]i0123-14-15sonuc[]pop 1 (1 ≤ 8)yığın boş → sonuc[4] = -1push 88yigit← top

i = 4: yığındaki küçük/eşit değerler atılır; kalan üst değer cevaptır.

407132438415dizi[]i01283-14-15sonuc[]peek = 8 → sonuc[3] = 8push 484yigit← top

i = 3: dizi[3] = 4 için cevap yığının üstünde.

407132438415dizi[]i014283-14-15sonuc[]peek = 4 → sonuc[2] = 4push 3843yigit← top

i = 2: dizi[2] = 3 için cevap yığının üstünde.

407132438415dizi[]i0814283-14-15sonuc[]pop 3 (3 ≤ 7)pop 4 (4 ≤ 7)peek = 8 → sonuc[1] = 8push 787yigit← top

i = 1: yığındaki küçük/eşit değerler atılır; kalan üst değer cevaptır.

407132438415dizi[]i70814283-14-15sonuc[]peek = 7 → sonuc[0] = 7push 4874yigit← top

Sonuç: {7, 8, 4, 8, −1, −1}. Her eleman en fazla bir kez eklenip çıkarılır → O(n).

Hanoi Kuleleri

Hanoi Kuleleri (Towers of Hanoi)

Bilgisayar bilimi ve matematikte klasik bir problem: bir disk yığını, kurallara uyularak kaynak çubuktan hedef çubuğa taşınır.

  • Bir hamlede yalnızca bir disk taşınır (bir çubuğun en üstündeki).
  • Bir disk yalnızca daha büyük bir diskin üstüne ya da boş çubuğa konabilir.
  • Amaç: yardımcı çubuğu kullanarak tüm diskleri kaynaktan (A) hedefe (C) taşımak.
ABC321
Özyinelemeli fikir: n−1 diski A'dan B'ye taşı → en büyük diski A'dan C'ye taşı → n−1 diski B'den C'ye taşı. Hamle sayısı T(n) = 2T(n−1) + 1 = 2n − 1.

Hanoi Kuleleri

Hanoi Kuleleri: 3 Diskle 7 Hamle

ABC321

Başlangıç: 3 disk A çubuğunda (büyük altta). Hedef: hepsini C'ye taşımak.

ABC321

Hamle 1: disk 1, A → C.

ABC321

Hamle 2: disk 2, A → B.

ABC321

Hamle 3: disk 1, C → B.

ABC213

Hamle 4: disk 3, A → C.

ABC123

Hamle 5: disk 1, B → A.

ABC132

Hamle 6: disk 2, B → C.

ABC321

Hamle 7: disk 1, A → C. Bitti: 2³ − 1 = 7 hamle.

Hanoi Kuleleri

Hanoi Kuleleri: Yığın ile Özyinelemesiz Çözüm

record Gorev(int n, char kaynak, char yard, char hedef) {}

void hanoi(int diskSayisi) {
    Stack<Gorev> yigin = new Stack<>();
    yigin.push(new Gorev(diskSayisi, 'A', 'B', 'C'));      // başlangıç görevi
    while (!yigin.isEmpty()) {
        Gorev g = yigin.pop();
        if (g.n() == 1) {
            System.out.println("Diski " + g.kaynak() + " → " + g.hedef() + " taşı");
        } else {                   // LIFO: son eklenen ilk çalışır
            yigin.push(new Gorev(g.n() - 1, g.yard(), g.kaynak(), g.hedef()));  // 3.
            yigin.push(new Gorev(1, g.kaynak(), g.yard(), g.hedef()));        // 2.
            yigin.push(new Gorev(g.n() - 1, g.kaynak(), g.hedef(), g.yard()));  // 1.
        }
    }
}
Bir görev (n, kaynak, yardımcı, hedef) “n diski kaynaktan hedefe taşı” demektir. n > 1 ise üç alt görev ters sırayla eklenir: 1. n−1 diski kaynaktan yardımcıya (hedef yardımcı olur), 2. 1 diski kaynaktan hedefe, 3. n−1 diski yardımcıdan hedefe (kaynak yardımcı olur).

Hanoi Kuleleri

Hanoi Kuleleri: Yığın İşlemleri

Yığına ekle 3 A B CABC3213 A B Cyığın (n kaynak yard. hedef)← top

Başlangıç görevi: 3 diski A'dan C'ye, B yardımcı ile taşı.

Yığından al 3 A B CYığına ekle 2 B A CYığına ekle 1 A B CYığına ekle 2 A C BABC3212 B A C1 A B C2 A C Byığın (n kaynak yard. hedef)← top

n = 3: üç alt göreve bölünür; ilk yapılacak iş en son eklenir (LIFO).

Yığından al 2 A C BYığına ekle 1 C A BYığına ekle 1 A C BYığına ekle 1 A B CABC3212 B A C1 A B C1 C A B1 A C B1 A B Cyığın (n kaynak yard. hedef)← top

n = 2: üç alt göreve bölünür; ilk yapılacak iş en son eklenir (LIFO).

Yığından al 1 A B CDiski A → C taşıABC3212 B A C1 A B C1 C A B1 A C Byığın (n kaynak yard. hedef)← top

n = 1: doğrudan taşınır (disk 1).

Yığından al 1 A C BDiski A → B taşıABC3212 B A C1 A B C1 C A Byığın (n kaynak yard. hedef)← top

n = 1: doğrudan taşınır (disk 2).

Yığından al 1 C A BDiski C → B taşıABC3212 B A C1 A B Cyığın (n kaynak yard. hedef)← top

n = 1: doğrudan taşınır (disk 1).

Yığından al 1 A B CDiski A → C taşıABC2132 B A Cyığın (n kaynak yard. hedef)← top

n = 1: doğrudan taşınır (disk 3).

Yığından al 2 B A CYığına ekle 1 A B CYığına ekle 1 B A CYığına ekle 1 B C AABC2131 A B C1 B A C1 B C Ayığın (n kaynak yard. hedef)← top

n = 2: üç alt göreve bölünür; ilk yapılacak iş en son eklenir (LIFO).

Yığından al 1 B C ADiski B → A taşıABC1231 A B C1 B A Cyığın (n kaynak yard. hedef)← top

n = 1: doğrudan taşınır (disk 1).

Yığından al 1 B A CDiski B → C taşıABC1321 A B Cyığın (n kaynak yard. hedef)← top

n = 1: doğrudan taşınır (disk 2).

Yığından al 1 A B CDiski A → C taşıABC321yığın (n kaynak yard. hedef)

Yığın boş: 7 hamle tamamlandı, tüm diskler C'de.

Stock Span

Stock Span (Hisse Senedi Sıçrama) Problemi

  • Günlük kapanış fiyatları bir dizide verilir.
  • Bir günün span değeri: o gün dahil, geriye doğru fiyatı bugünkü fiyattan küçük ya da eşit olan ardışık günlerin sayısı.
  • Finansal veri analizinde bir fiyatın “kaç gündür aşılmadığını” gösterir.
GünFiyatSpanNeden?
11001ilk gün
470260 ≤ 70, 80 > 70
675460, 70, 60 ≤ 75; 80 > 75
785675, 60, 70, 60, 80 ≤ 85
100g1180g2160g3170g4260g5175g6485g76

Fiyatlar [100, 80, 60, 70, 60, 75, 85] → span [1, 1, 1, 2, 1, 4, 6]. Çubukların altındaki kalın sayılar span değerleridir.

Stock Span

Stock Span: Yığın ile O(n) Çözüm

int[] span(int[] fiyatlar) {
    int[] sicrama = new int[fiyatlar.length];
    Stack<Integer> yigin = new Stack<>();
    yigin.push(0);
    sicrama[0] = 1;
    for (int i = 1; i < fiyatlar.length; i++) {
        while (!yigin.isEmpty() &&
            fiyatlar[i] >= fiyatlar[yigin.peek()])
            yigin.pop();
        sicrama[i] = yigin.isEmpty() ? i + 1
                              : i - yigin.peek();
        yigin.push(i);
    }
    return sicrama;
}
Yığında ne var?Henüz aşılmamış günlerin indisleri (fiyatlar alttan üste azalır).
SpanÜst = önceki ilk daha yüksek gün: span = i − üst; yığın boşsa i + 1.
KarmaşıklıkHer indis bir kez eklenir, en çok bir kez çıkar → O(n).

Stock Span

Stock Span: Adım Adım

1000801602703604755856fiyati10123456spanboş → span = 0+1 = 1push 00 : 100yığın (indis : fiyat)← top

İlk gün: yığın boş → span[0] = 1; indis 0 yığına eklenir.

1000801602703604755856fiyati101123456spanspan = 1 − 0 = 1push 10 : 1001 : 80yığın (indis : fiyat)← top

fiyat[1] = 80, üstteki 100'den düşük → span = 1.

1000801602703604755856fiyati1011123456spanspan = 2 − 1 = 1push 20 : 1001 : 802 : 60yığın (indis : fiyat)← top

fiyat[2] = 60, üstteki 80'den düşük → span = 1.

1000801602703604755856fiyati10111223456spanpop 2 (70 ≥ 60)span = 3 − 1 = 2push 30 : 1001 : 803 : 70yığın (indis : fiyat)← top

Bugünkü fiyattan küçük/eşit günler atılır; üstteki indis ilk daha yüksek gün.

1000801602703604755856fiyati101112231456spanspan = 4 − 3 = 1push 40 : 1001 : 803 : 704 : 60yığın (indis : fiyat)← top

fiyat[4] = 60, üstteki 70'den düşük → span = 1.

1000801602703604755856fiyati1011122314456spanpop 4 (75 ≥ 60)pop 3 (75 ≥ 70)span = 5 − 1 = 4push 50 : 1001 : 805 : 75yığın (indis : fiyat)← top

Bugünkü fiyattan küçük/eşit günler atılır; üstteki indis ilk daha yüksek gün.

1000801602703604755856fiyati10111223144566spanpop 5 (85 ≥ 75)pop 1 (85 ≥ 80)span = 6 − 0 = 6push 60 : 1006 : 85yığın (indis : fiyat)← top

85 ≥ 75 ve 80 → çıkarılır; üstte 100 kalır: span[6] = 6 − 0 = 6.

İfade Gösterimleri

Infix, Prefix ve Postfix Gösterimleri

GösterimOperatörün yeriA * (B + C) / D
İçtakı (infix)işlenenlerin arasındaA * ( B + C ) / D
Öntakı (prefix) · Polonya gösterimiişlenenlerden önce/ * A + B C D
Arttakı (postfix) · ters Polonya (RPN)işlenenlerden sonraA B C + * D /
  • Prefix ve postfixte parantez ve öncelik kuralı gerekmez: ifade soldan sağa (ya da sağdan sola) tek geçişte, yığınla değerlendirilir.
  • Derleyiciler ve yorumlayıcılar ifadeleri çoğunlukla postfix biçimine (ör. yığın tabanlı bayt koduna) çevirir; hesap makineleri de RPN kullanabilir.
  • Sembolik cebir ve ifade ağaçları üzerinde çalışırken de kullanılır.
Dikkat: orijinal sunumdaki prefix * A / + B C D, A * ((B + C) / D) demektir. Soldan sağa işlem sırasına uygun doğru prefix: / * A + B C D.

İfade Gösterimleri

Öncelik, Birleşme ve Elle Dönüşüm

OperatörÖncelikBirleşme
^ (üs)3sağdan sola
* /2soldan sağa
+ −1soldan sağa

Aynı öncelikte soldan sağa: A − B + C = (A − B) + C → postfix A B − C +.

Elle dönüşüm: tam parantezleme
  1. Her işlemi parantezle: ((A * (B + C)) / D)
  2. Postfix: her operatörü kendi parantezinin sağına taşı → A B C + * D /
  3. Prefix: her operatörü kendi parantezinin soluna taşı → / * A + B C D
İşlenenlerin (A, B, C, D) sırası üç gösterimde de aynı kalır; yalnızca operatörlerin yeri değişir. Bu, dönüşümü kontrol etmenin hızlı bir yoludur.

İfade Gösterimleri

Infix → Postfix: Algoritma

Girdi soldan sağa okunur; operatörler ve açık parantezler bir yığında bekletilir.

Okunan simgeYapılacak işlem
İşlenen (A, B, 5 …)Doğrudan çıktıya yaz.
(Yığına koy.
)( görülene kadar yığından çıkarıp çıktıya yaz; ('ı at.
Operatör opÜstte ( olmadığı ve üstteki operatörün önceliği ≥ op olduğu sürece çıkarıp çıktıya yaz; sonra op'u yığına koy.
Girdi bittiYığında kalan tüm operatörleri çıkarıp çıktıya yaz.

“≥” kuralı soldan sağa birleşmeyi sağlar. Sağdan birleşen ^ için yalnızca önceliği > olanlar çıkarılır. Zaman O(n).

İfade Gösterimleri

Infix → Postfix: A * (B + C) / D

A*(B+C)/Dgirdipostfixyığın

Girdi soldan sağa okunur. İşlenenler çıktıya, operatörler ve '(' yığına gider.

A*(B+C)/DgirdiApostfixyığın

A işlenen → doğrudan çıktıya.

A*(B+C)/DgirdiApostfix*yığın← top

*: yığın boş → yığına konur.

A*(B+C)/DgirdiApostfix*(yığın← top

( → her zaman yığına konur.

A*(B+C)/DgirdiABpostfix*(yığın← top

B işlenen → doğrudan çıktıya.

A*(B+C)/DgirdiABpostfix*(+yığın← top

+: üstte '(' var → doğrudan yığına.

A*(B+C)/DgirdiABCpostfix*(+yığın← top

C işlenen → doğrudan çıktıya.

A*(B+C)/DgirdiABC+postfix*yığın← top

) → '(' görülene kadar çıkar: + çıktıya; '(' atılır.

A*(B+C)/DgirdiABC+*postfix/yığın← top

/: üstteki * önceliği ≥ → çıktıya; sonra / yığına.

A*(B+C)/DgirdiABC+*Dpostfix/yığın← top

D işlenen → doğrudan çıktıya.

A*(B+C)/DgirdiABC+*D/postfixyığın

Girdi bitti: yığında kalanlar çıktıya → A B C + * D /

İfade Gösterimleri

Infix → Postfix: A + B * C − D / E

SimgeİşlemYığınÇıktı
AçıktıyaA
+push ++A
Bçıktıya+A B
*push *+ *A B
Cçıktıya+ *A B C
−pop * +, push −−A B C * +
Dçıktıya−A B C * + D
/push /− /A B C * + D
Eçıktıya− /A B C * + D E
sonpop / −A B C * + D E / −
Öncelik* geldiğinde üstte + var; * daha öncelikli olduğu için + yığında bekler.
Soldan birleşme− geldiğinde önce *, sonra eşit öncelikli + çıkarılır.
SonuçA B C * + D E / −
= (A + (B·C)) − (D / E)

İfade Gösterimleri

Infix → Postfix: Java

String infixToPostfix(String ifade) {     // oncelik: + − → 1, * / → 2, ( → 0
    StringBuilder cikti = new StringBuilder();
    Stack<Character> yigin = new Stack<>();
    for (char c : ifade.toCharArray()) {
        if (Character.isLetterOrDigit(c)) cikti.append(c);
        else if (c == '(') yigin.push(c);
        else if (c == ')') {
            while (yigin.peek() != '(') cikti.append(yigin.pop());
            yigin.pop();                                 // '(' atılır
        } else {                                         // operatör
            while (!yigin.isEmpty() && oncelik(yigin.peek()) >= oncelik(c))
                cikti.append(yigin.pop());
            yigin.push(c);
        }
    }
    while (!yigin.isEmpty()) cikti.append(yigin.pop());
    return cikti.toString();
}

Varsayım: tek karakterli işlenenler, boşluksuz ve geçerli ifade. oncelik('(') = 0 olduğundan operatörler ('ı hiç çıkarmaz.

İfade Gösterimleri

Postfix İfadeyi Değerlendirme

  • Soldan sağa oku.
  • Sayı → yığına push.
  • Operatör → iki öğe pop: önce sağ, sonra sol işlenen; sonucu push.
  • Sonda yığında tek öğe kalır: sonuç.
  • Parantez ve öncelik yok → O(n).
412+*3/girdiyığın

A=4, B=1, C=2, D=3 için A B C + * D / → 4 1 2 + * 3 /

412+*3/girdi4yığın← top

4 sayı → yığına.

412+*3/girdi41yığın← top

1 sayı → yığına.

412+*3/girdi412yığın← top

2 sayı → yığına.

412+*3/girdi1 + 2 = 343yığın← top

+: pop 2 (sağ), pop 1 (sol); 1 + 2 = 3 yığına.

412+*3/girdi4 * 3 = 1212yığın← top

*: pop 3 (sağ), pop 4 (sol); 4 * 3 = 12 yığına.

412+*3/girdi123yığın← top

3 sayı → yığına.

412+*3/girdi12 / 3 = 44yığın← top

/: pop 3 (sağ), pop 12 (sol); 12 / 3 = 4 yığına. Sonuç: 4.

İfade Gösterimleri

Infix → Prefix Dönüşümü

  1. Infix ifadeyi ters çevir.
  2. Parantezleri değiştir: ( ↔ ).
  3. Postfix algoritmasını uygula; ama eşit öncelikte çıkarma, yalnızca önceliği > olanları çıkar.
  4. Çıktıyı ters çevir.
AdımA * (B + C) / D
1–2. Ters çevir, parantezleri değiştirD / ( C + B ) * A
3. Postfix (eşitte çıkarmadan)D C B + A * /
4. Ters çevir → prefix/ * A + B C D
3. adımda * geldiğinde üstte eşit öncelikli / vardır; / çıkarılmaz. Böylece ters çevirince soldan birleşme korunur: önce A * (B + C), sonra / D.
ÖrnekA + B * C − D / E → − + A * B C / D E
Prefix değerlendirmePostfix gibi, ama ifade sağdan sola okunur; ilk çıkan sol işlenendir.

Özet

Özet: Yığınla Çözülen Problemler

ProblemYığında tutulanZaman
push / pop / top / isEmpty / size—O(1)
Dize ters çevirmekarakterlerO(n)
Parantez kontrolüeşleşmemiş açık parantezlerO(n)
Sonraki büyük elemansağdaki aday değerler (azalan)O(n)
Stock spanhenüz aşılmamış günlerin indisleriO(n)
Hanoi kulelerialt görevler (n, kaynak, yard., hedef)O(2n)
Infix → postfix / prefixoperatörler ve (O(n)
Postfix değerlendirmeara sonuçlarO(n)

Ortak desen: “en son görülen, henüz sonuçlanmamış öğe” ile ilgileniyorsak yığın kullanırız (LIFO).

Özet

Etkileşimli Simülatörler

Bu bölümün algoritmalarını kendi girdilerinizle adım adım deneyin:

Yığın (Stack)push, pop, peek işlemlerini dizi ve bağlı liste gösterimiyle izleyin.
Hanoi KuleleriDisk sayısını seçin, hamleleri adım adım oynatın.
Stock SpanKendi fiyat dizinizle yığının nasıl değiştiğini görün.
Infix → PostfixOperatör yığınını ve çıktıyı simge simge izleyin.
Infix → PrefixTers çevirme yöntemini adım adım uygulayın.
İpucuÖrnek problemlerin cevaplarını simülatörlerle kontrol edebilirsiniz.

Özet

Örnek Problemler

  1. Boş yığına sırayla push(5), push(3), pop(), push(8), push(1), pop(), pop(), push(4) uygulanıyor. pop()'ların döndürdüğü değerler ve son yığın nedir?
  2. {[()()]} ve {[(])} dizilerinden hangisi geçerlidir? Geçersiz olanda hata hangi karakterde fark edilir?
  3. (A + B) * (C − D) / E ifadesini postfix ve prefix biçimine çevirin.
  4. 5 1 2 + 4 * + 3 − postfix ifadesinin değeri nedir?
  5. Fiyatlar [10, 4, 5, 90, 120, 80] için span dizisini bulun.
  6. {2, 5, 3, 1, 4} dizisi için sonraki büyük eleman dizisini bulun. 4 diskli Hanoi kaç hamle ister?
Cevapları göster
  1. pop'lar: 3, 1, 8. Son yığın (alttan üste): 5, 4.
  2. {[()()]} geçerli. {[(])}: ] geldiğinde üstte ( var → geçersiz.
  3. Postfix: A B + C D − * E / · Prefix: / * + A B − C D E
  4. 1+2 = 3, 3·4 = 12, 5+12 = 17, 17−3 = 14.
  5. [1, 1, 2, 4, 5, 1]
  6. {5, −1, 4, 4, −1}; 24 − 1 = 15 hamle.

Özet

Kaynaklar

  • Goodrich, Tamassia ve Goldwasser, Data Structures and Algorithms in Java, 6. baskı, 2014.
  • Lafore, Data Structures and Algorithms in Java, 2. baskı, 2002.
  • Oracle, Java SE 17 Deque ve ArrayDeque API belgeleri.

Son

Bölüm 4: Yığınlar
Sonraki bölüm: Kuyruklar
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.

← → veya boşluk ile gezinin · F: tam ekran · Home/End: ilk/son slayt