Yığın (stack): dizi ve bağlı liste ile
LIFO mantığı, push/pop/peek, dizi ve bağlı liste ile iki gerçekleme, overflow ve underflow. Sınavda işlem dizisinden sonra yığının hali ve pop sırası soruları gelir.
Yığın (stack) üst üste konmuş tabaklar gibidir: tabağı en üste koyarsın, alırken de en üsttekini alırsın. Ortadan tabak çekemezsin. Bu kurala LIFO (Last In, First Out) denir. Basit görünür ama derleyicilerin ifade hesaplaması, metot çağrıları, geri al (undo) özelliği ve parantez kontrolü hep yığınla çalışır. Sınav soruları iki tiptir: bir işlem dizisinden sonra ne basıldığını ya da yığında ne kaldığını bulmak, ve push/pop’u dizi ya da bağlı liste ile yazmak.
Altın kurallar:
- Sadece tepeye erişirsin. push tepeye koyar, pop tepeden alır.
- peek bakar, silmez. İz sürerken pop ile peek’i karıştırma.
- Dizide top son elemanın indeksidir. Boş yığında -1.
- Bağlı listede tepe listenin başıdır. Sonu değil.
- Boş yığından pop = underflow, dolu diziye push = overflow.
Soyut veri tipi (ADT)
Yığın bir soyut veri tipidir (abstract data type): hangi işlemlerin olduğunu söyler, nasıl yapıldığını söylemez. İşlemler:
| İşlem | Ne yapar | Süre |
|---|---|---|
push(x) | x’i tepeye koyar | O(1) |
pop() | tepedeki elemanı siler ve döndürür | O(1) |
peek() (veya top()) | tepedekini döndürür, silmez | O(1) |
isEmpty() | yığın boş mu | O(1) |
size() | eleman sayısı | O(1) |
Aynı ADT’yi iki şekilde gerçekleyebiliriz: diziyle ya da bağlı listeyle. Kullanan kod farkı görmez.
Dizi ile yığın (array-based stack)
class ArrayStack {
private int[] data;
private int top = -1; // son elemanın indeksi
ArrayStack(int capacity) { data = new int[capacity]; }
boolean isEmpty() { return top == -1; }
boolean isFull() { return top == data.length - 1; }
int size() { return top + 1; }
void push(int x) {
if (isFull()) throw new RuntimeException("Stack overflow");
data[++top] = x; // önce artır, sonra yaz
}
int pop() {
if (isEmpty()) throw new RuntimeException("Stack underflow");
return data[top--]; // önce oku, sonra azalt
}
int peek() {
if (isEmpty()) throw new RuntimeException("Stack underflow");
return data[top];
}
}
++top ile top-- farkı burada kritik. data[top++] = x yazarsan ilk eleman data[-1]’e yazılmaya çalışılır ve ArrayIndexOutOfBoundsException alırsın.
Çözümlü örnek
ArrayStack s = new ArrayStack(5);
s.push(10); s.push(20); s.push(30);
System.out.println(s.pop());
System.out.println(s.peek());
s.push(40);
System.out.println(s.size() + " " + s.raw()); // raw: dizinin ham hali
30
20
3 [10, 20, 40, 0, 0] top=2
push 10 [10, _, _, _, _] top=0
push 20 [10, 20, _, _, _] top=1
push 30 [10, 20, 30, _, _] top=2
pop → 30 [10, 20, 30, _, _] top=1 (30 dizide duruyor ama yığında değil)
peek → 20 değişiklik yok
push 40 [10, 20, 40, _, _] top=2 (30'un üstüne yazıldı)
TUZAK · pop diziden silmez. Sınavda “dizinin içeriği ne?” diye sorulursa pop edilmiş değerler hâlâ oradadır, sadece top indeksinin üstünde kalırlar. Yeni bir push onların üstüne yazar.
Dolunca büyütmek
Sabit kapasite yerine dolunca iki katı büyüklükte yeni dizi açıp kopyalayabilirsin. O push O(n) sürer, ama kapasite her seferinde ikiye katlandığı için bu nadiren olur. n push’un toplam maliyeti O(n), yani push başına ortalama (amortized) O(1). Java’nın ArrayList ve ArrayDeque sınıfları böyle çalışır.
Bağlı liste ile yığın (linked stack)
Tek yönlü listenin başına ekleme ve baştan silme zaten O(1). Tepe = baş.
class LinkedStack {
private static class Node {
int data; Node next;
Node(int d, Node n) { data = d; next = n; }
}
private Node top;
boolean isEmpty() { return top == null; }
void push(int x) { top = new Node(x, top); }
int pop() {
if (top == null) throw new RuntimeException("Stack underflow");
int x = top.data;
top = top.next;
return x;
}
int peek() {
if (top == null) throw new RuntimeException("Stack underflow");
return top.data;
}
}
Bu gerçeklemede overflow yok (bellek bitene kadar). Bedeli düğüm başına bir next referansı.
| Dizi | Bağlı liste | |
|---|---|---|
| push / pop / peek | O(1) | O(1) |
| Kapasite | sabit (ya da büyütme ile amortized) | sınırsız |
| Overflow | var | yok |
| Ek bellek | kullanılmayan dizi hücreleri | her düğümde next |
Yığın ile yapılan klasik işler
Ters çevirme. Harfleri sırayla it, sonra çek: “DATA” → “ATAD”. Yığına giren sıra, çıkışta tersine döner.
Ondalıktan ikiliğe. n % 2 kalanlarını it, n’yi ikiye böl; sonra hepsini çek. 13 → 1101, 6 → 110, 32 → 100000.
static String toBinary(int n) {
if (n == 0) return "0";
Deque<Integer> s = new ArrayDeque<>();
while (n > 0) { s.push(n % 2); n /= 2; }
StringBuilder sb = new StringBuilder();
while (!s.isEmpty()) sb.append(s.pop());
return sb.toString();
}
Metot çağrıları. Java her metot çağrısında yerel değişkenleri bir yığın çerçevesine (stack frame) koyar; metot bitince çerçeve pop edilir. Sonsuz özyineleme bu yığını doldurur ve StackOverflowError verir (10. hafta).
Diğer uygulamalar (parantez dengesi, infix → postfix, postfix hesaplama) gelecek haftanın konusu.
Pop sırası soruları
“1, 2, 3, 4 sırasıyla push ediliyor, aradaki herhangi bir anda pop yapılabilir. Hangi çıkış sırası mümkün?” Çözmek için simüle et: çıkması gereken sayıya kadar push et, sonra tepe o sayı mı diye bak.
Hedef 3 1 4 2:
3 lazım → push 1, 2, 3 → pop 3 yığın [1, 2]
1 lazım → tepe 2, 1 değil İMKANSIZ
Hedef 3 2 4 1:
push 1,2,3 → pop 3 → [1,2]
pop 2 → [1]
push 4 → pop 4 → [1]
pop 1 MÜMKÜN
Kısa kural: bir sayı çıktıktan sonra, ondan küçük olup henüz çıkmamış sayılar azalan sırada çıkmak zorunda.
Java’da hazır yığın
java.util.Stack sınıfı eskidir (Vector’den türer, senkronize, yavaş). Modern Java’da önerilen Deque arayüzü ve ArrayDeque sınıfıdır:
Deque<Integer> st = new ArrayDeque<>();
st.push(1); st.push(2);
st.peek(); // 2
st.pop(); // 2
Boş ArrayDeque üzerinde pop() NoSuchElementException fırlatır, peek() ise null döner. Ayrıntısı 9. haftada.
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
| peek elemanı siler | Sadece bakar |
| Bağlı yığında tepeyi listenin sonuna koymak | Baş = tepe, ikisi de O(1) |
data[top++] = x (top = -1 iken) | data[++top] = x |
return data[--top] | return data[top--] |
| pop değeri diziden siler | Sadece top azalır, değer durur |
| Boş yığında pop kontrolü yok | if (isEmpty()) throw ... |
| Pop sırası sorusunda içgüdüyle cevap | Adım adım simüle et |
isFull: top == data.length | top == data.length - 1 |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is printed? (s is an empty stack.)
s.push(5); s.push(3);
System.out.print(s.pop() + " ");
s.push(8); s.push(1);
System.out.print(s.pop() + " ");
System.out.print(s.pop() + " ");
s.push(6);
System.out.println(s.peek());Cevabı göster
[5,3] → pop 3 → [5] → [5,8,1] → pop 1 → pop 8 → [5] → push 6 → [5,6], peek 6 (silmez). Çıktı: 3 1 8 6.
What is printed, and what is left in the stack (top first)?
for (int i = 1; i <= 5; i++) {
s.push(i * 2);
if (i % 2 == 0)
System.out.print(s.pop() + " ");
}Cevabı göster
i=1: push 2. i=2: push 4, pop 4. i=3: push 6. i=4: push 8, pop 8. i=5: push 10. Basılan: 4 8. Kalan (tepeden): 10 6 2.
An array stack has capacity 4, top starts at -1 and push does data[++top] = x. After the calls below, what are the contents of the array and the value of top?
push(7); push(2); push(9);
pop(); pop();
push(4);Cevabı göster
push'lar sonrası [7,2,9,0], top 2. İki pop sadece top'u azaltır: top 0. Değerler dizide durur ama mantıken yığında değildir. push(4) indeks 1'e yazar ve 2'nin üstüne gelir: [7,4,9,0], top 1. 9 hâlâ dizide ama yığının parçası değil.
An array stack with capacity 3 runs this loop. What is printed?
try {
for (int i = 1; i <= 4; i++) {
s.push(i);
System.out.print(i + " ");
}
} catch (RuntimeException e) {
System.out.println("-> " + e.getMessage());
}Cevabı göster
Üç push başarılı, her birinden sonra sayı basılır. Dördüncü push'ta dizi dolu (top == length - 1), push istisna fırlatır ve 4 basılmadan catch'e düşer.
Write int pop() for an array-based stack with fields int[] data and int top (top = index of the top element, -1 when empty). Throw an exception on underflow.
Cevabı göster
int pop() {
if (top == -1)
throw new RuntimeException("Stack underflow");
return data[top--]; // önce oku, sonra azalt
}data[--top] yazarsan bir alttaki elemanı döndürürsün. top-- son ek (postfix) olduğu için önce eski top değeriyle okunur. O(1).Write void push(int x) and int pop() for a linked-list stack whose only field is Node top (Node has data and next, and a constructor Node(int d, Node n)).
Cevabı göster
Bağlı listede yığının tepesi listenin başıdır; böylece iki işlem de O(1) olur.
void push(int x) {
top = new Node(x, top);
}
int pop() {
if (top == null)
throw new RuntimeException("Stack underflow");
int x = top.data;
top = top.next;
return x;
}Tepe listenin sonunda olsaydı pop için sondan bir önceki düğümü aramak gerekir, O(n) olurdu.The numbers 1, 2, 3, 4 are pushed in this order; pops may happen at any time. Which of these output sequences are possible? (a) 2 4 3 1 (b) 3 1 4 2 (c) 4 3 2 1 (d) 1 4 2 3
Cevabı göster
(a) push 1, push 2, pop 2, push 3, push 4, pop 4, pop 3, pop 1: mümkün. (c) dördünü it, sırayla çek: mümkün. (b) 3 çıktığında yığında 1 ve 2 var, 2 üstte. 1'i çıkarmak için önce 2 çıkmalı: imkansız. (d) 4 çıkınca 2 ve 3 içeride, 3 üstte, 2 önce çıkamaz: imkansız. Kural: bir sayı çıktıktan sonra ondan küçük olanlar azalan sırada çıkmalı.
What are the time complexities of push, pop and peek for an array stack and for a linked stack?
Cevabı göster
Dizide sadece top indeksi değişir, listede sadece baş düğüm değişir. Hiçbiri n'ye bağlı değil. Dizi dolunca iki katı büyüklükte yeni diziye kopyalayan bir gerçeklemede o tek push O(n) olur ama ortalamada (amortized) yine O(1).
What does this method return for "DATA"?
static String f(String w) {
Deque<Character> st = new ArrayDeque<>();
for (char ch : w.toCharArray()) st.push(ch);
StringBuilder sb = new StringBuilder();
while (!st.isEmpty()) sb.append(st.pop());
return sb.toString();
}Cevabı göster
Harfler D, A, T, A sırasıyla girer; tepe son A. Çekince A, T, A, D çıkar. Yığın sırayı ters çevirir: ATAD.
The values 3 8 6 1 4 are pushed onto s in this order. What is printed?
while (!s.isEmpty()) {
int x = s.pop();
if (x % 2 == 0) t.push(x);
}
while (!t.isEmpty())
System.out.print(t.pop() + " ");Cevabı göster
s'den çıkış sırası 4 1 6 8 3. Çiftler t'ye 4, 6, 8 sırasıyla girer, tepe 8. t'den çıkış 8 6 4. İki kez ters çevrildiği için çiftler orijinal ekleme sırasına döndü.
Write static String toBinary(int n) that converts a positive integer to its binary string using a stack.
Cevabı göster
Kalanlar en düşük basamaktan başlar, ama yazarken en yüksekten başlarız. Yığın sırayı ters çevirir.
static String toBinary(int n) {
if (n == 0) return "0";
Deque<Integer> s = new ArrayDeque<>();
while (n > 0) {
s.push(n % 2);
n /= 2;
}
StringBuilder sb = new StringBuilder();
while (!s.isEmpty()) sb.append(s.pop());
return sb.toString();
}13 → kalanlar 1, 0, 1, 1 → çıkış 1101. 6 → 110, 32 → 100000.Stack s contains (bottom to top) 4 7 2. What is printed?
int sum = 0;
while (s.peek() != 4)
sum += s.pop();
System.out.println(sum + " " + s.peek());Cevabı göster
Tepe 2: 4 değil, pop, sum = 2. Tepe 7: pop, sum = 9. Tepe 4: döngü biter. 4 yığında kalır, peek 4 döner. Çıktı: 9 4. 4 hiç olmasaydı yığın boşalınca peek underflow fırlatırdı.
Doğrulama: ArrayStack ve LinkedStack verify/SEN2211/W05.java içinde yazılıp JDK ile çalıştırıldı (java W05.java). Pop sıralarının mümkün olup olmadığı aynı dosyada 1..n'yi sırayla push eden bir simülasyonla kontrol edildi.