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

C ile Veri Yapıları ve Algoritmalar
LIFOYığın ADTpush / popTaşmaBağlı yığınÇağrı yığınıÇoklu yığın
Sercan KÜLCÜ · Giresun Üniversitesi · Bilgisayar Mühendisliği

Giriş

Öğrenme Çıktıları

LIFO'yu tanımlamak

Yığını S = {a0, …, an−1} listesi olarak tanımlayıp alt ve üst öğeyi göstermek.

Dizi ile gerçeklemek

top indisiyle push/pop yazmak; taşma (overflow) ve alt taşmayı (underflow) denetlemek.

Bağlı yığın kurmak

Düğüm ayırıp (malloc) bağlamak, çıkarılan düğümü free ile bırakmak.

Çağrı yığınını tanımak

Fonksiyon çağrılarının bir yığın gibi çalıştığını ve çağrı yığınının neden taştığını açıklamak.

Tasarım kararı vermek

Sabit dizi, büyüyen dizi ve bağlı liste arasında maliyete göre seçim yapmak.

Çoklu yığını yönetmek

Tek bir bellek dizisinde n yığını top[i] ve sinir[i] ile paylaştırmak.

Yığın ADT

Yığın (Stack): Son Giren İlk Çıkar

Yığın, ekleme (insertion) ve silmenin (deletion) yalnızca bir uçtan yapıldığı sıralı bir listedir: S = {a0, a1, …, an−1} için a0 alt, an−1 üst (top) öğedir. Son eklenen ilk çıkar: LIFO (last-in-first-out).

duvargiriş / çıkış (tek uç)çıkan araç önce en yakındakiABCD

Tek arabalık çıkmaz sokağa park eden araçlar: son giren (D) ilk çıkmak zorunda.

Türkçe kaynaklarda stack için yığın ve yığıt ikisi de kullanılır; bu derste stack için “yığın”, heap veri yapısı için “heap” kullanılır.

Yığın ADT

Yığının Büyümesi ve Küçülmesi

A[0][1][2][3][4]top = 0A eklendi

A eklendi → üst öğe artık A; top = 0.

A[0]B[1][2][3][4]top = 1B eklendi

B eklendi → üst öğe artık B; top = 1.

A[0]B[1]C[2][3][4]top = 2C eklendi

C eklendi → üst öğe artık C; top = 2.

A[0]B[1]C[2]D[3][4]top = 3D eklendi

D eklendi → üst öğe artık D; top = 3.

A[0]B[1]C[2]D[3]E[4]top = 4E eklendi

E eklendi → üst öğe artık E; top = 4.

A[0]B[1]C[2]D[3][4]top = 3EE silindi

Silme yalnız üstten: ilk alınan öğe en son eklenen E olur. Üst öğe yeniden D, top = 3.

  • Ekleme sırası: A, B, C, D, E.
  • Silme sırası (tümü çıkarılsaydı): E, D, C, B, A: tam ters.
  • Ortada bir öğeye (ör. B) erişmek için üstündekilerin hepsi alınmalı.
  • Bu kısıt bir kusur değil, bir sözleşmedir: “en son açılan iş ilk kapanır”.

Yığın ADT

Soyut Veri Tipi: Yığın

İşlemAnlamıÖn koşul / hata
yigit_olustur(max)En çok max öğe alan boş yığın—
yigit_dolu(S)Öğe sayısı max ise doğru—
yigit_ekle(S, x) · pushx'i üste koyarDolu ise taşma (overflow)
yigit_bos(S)Öğe yoksa doğru—
yigit_al(S) · popÜstteki öğeyi çıkarıp döndürürBoş ise alt taşma (underflow)
yigit_ust(S) · peekÜstteki öğeyi çıkarmadan okurBoş ise hata

ADT ne yapıldığını söyler, nasıl yapıldığını söylemez. Aynı arayüz sabit dizi, büyüyen dizi ya da bağlı listeyle gerçeklenebilir (Horowitz–Sahni, ADT 3.1).

Yığın ADT

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

Çağrı yığını

Her fonksiyon çağrısı dönüş adresi ve yerel değişkenleriyle bir çerçeve iter.

İfade değerlendirme

Derleyiciler infix → postfix çevirisi ve postfix hesaplamada yığın kullanır.

Geri izleme

Labirentte çıkmaza girilince son yol ayrımına dönülür.

Parantez denetimi

Her kapanan, en son açılanla eşleşmeli.

Geri al (undo)

Düzenleyici her işlemi yığına koyar; Ctrl+Z en sonuncuyu geri alır.

Derinlik öncelikli arama

DFS, ziyaret edilecek düğümleri yığında tutar.

Yığın ADT

push ve pop Yalnız Üstte

Dört kez push, ardından pop: çıkan sıra girenin tersidir. Boş yığında pop alt taşmadır.

Dizi ile Yığın

Dizi ile Yığın: top İndisi

[0][1][2][3][4]top = -1boş yığıntop < 0 → boşyigit_al → alt taşmaA[0]B[1]C[2]D[3]E[4]top = 4dolu yığın
Boş: top < 0

Boş yığında top = −1. Bu hâlde öğe almak alt taşma (underflow) hatasıdır.

Dolu: top ≥ MAX_STACK_SIZE − 1

Bu hâlde öğe eklemek taşma (overflow) hatasıdır.

Öğe sayısı = top + 1

İkisini karıştırmak klasik “bir eksik/bir fazla” hatasıdır.

Ne olur?

C dizi sınırını denetlemez: denetimsiz bir push komşu belleğin üzerine yazar.

Dizi ile Yığın

Dizi ile Yığın: C Tanımları

#include <stdio.h>
#include <stdlib.h>
#define MAX_STACK_SIZE 101

typedef struct {
    int deger;
    /* diğer alanlar */
} element;

element yigit[MAX_STACK_SIZE];

void yigit_dolu(void) {
    printf("Yigit dolu!!!\n"); exit(-1);
}
void yigit_bos(void) {
    printf("Yigit bos!!!\n"); exit(-2);
}
  • element: yığında saklanacak kayıt; şimdilik tek alan deger.
  • Yığın küresel (global) bir dizi; top ise çağırandan adresle gelir.
  • Hata yordamları mesaj basıp programı exit ile sonlandırır.
Hocadan “Böyle yapılırsa sıfır alınır, hatta eksi not alınır. Burada bu konu kaç kere anlatıldı, değil mi?”

Dizi ile Yığın

Ekleme ve Silme Yordamları

void yigit_ekle(int *top, element item) {
    if (*top >= MAX_STACK_SIZE - 1)  /* yer var mı? */
        yigit_dolu();         /* uyar ve çık */
    yigit[++*top] = item;     /* önce işaretçiyi artır */
}                             /* sonra öğeyi yerleştir */

element yigit_al(int *top) {
    if (*top < 0)
        yigit_bos();          /* boşsa uyar ve çık */
    return yigit[(*top)--];   /* döndür, sonra azalt */
}
yigit[++*top]

Önce *top bir artar, sonra yeni indise yazılır: boş yığında −1 → 0.

yigit[(*top)--]

Önce eski indisteki öğe okunur, sonra *top azalır. Parantez şart: *top-- işaretçinin kendisini azaltırdı.

Hocadan “Normal şartlarda yordamın formal parametre listesinde “*parametre” olarak gözüken değişken, yordam içinde de “*parametre” olarak kullanılmalıdır.”

Dizi ile Yığın

C'de Öncelik Tuzağı: * ile ++/--

İfadeC bunu nasıl okurSonuç
++*top++(*top): gösterilen tamsayıyı artırDoğru (push)
(*top)--gösterilen tamsayıyı kullan, sonra azaltDoğru (pop)
*top--*(top--): işaretçiyi azalt, eski adresi okuHatalı: top değişmez
*top++*(top++): işaretçiyi ilerletHatalı
Kural

Son ek ++/--, tekli *'dan daha önceliklidir; ön ek ++ ile * aynı düzeydedir ve sağdan sola bağlanır.

Neden adresle?

top çağıranın değişkenidir; yordam onu değiştirebilsin diye &top geçirilir (C'de değerle çağrı vardır).

Dizi ile Yığın · Kodu adım adım çalıştır

Kod İzleme: Dizi ile Yığın

[0][1][2][3]top = -1boş yığıntop = −1

main'de top = −1 (boş). Sırayla ekle A, B, C; al; ekle D, E, F. MAX_STACK_SIZE = 4.

[0][1][2][3]top = -1

-1 ≥ 3? Hayır, yer var.

A[0][1][2][3]top = 0

++*top → top = 0, sonra yigit[0] = A.

A[0][1][2][3]top = 0

0 ≥ 3? Hayır, yer var.

A[0]B[1][2][3]top = 1

++*top → top = 1, sonra yigit[1] = B.

A[0]B[1][2][3]top = 1

1 ≥ 3? Hayır, yer var.

A[0]B[1]C[2][3]top = 2

++*top → top = 2, sonra yigit[2] = C.

A[0]B[1]C[2][3]top = 2

2 < 0? Hayır, yığın boş değil.

A[0]B[1]C[2][3]top = 1

yigit[2] = C döndürülür, ardından top = 1. Hücrede eski değer durur ama artık yığının parçası değildir.

A[0]B[1][2][3]top = 1

1 ≥ 3? Hayır, yer var.

A[0]B[1]D[2][3]top = 2

++*top → top = 2, sonra yigit[2] = D.

A[0]B[1]D[2][3]top = 2

2 ≥ 3? Hayır, yer var.

A[0]B[1]D[2]E[3]top = 3

++*top → top = 3, sonra yigit[3] = E.

A[0]B[1]D[2]E[3]top = 3

3 ≥ 3? Evet: yer yok!

A[0]B[1]D[2]E[3]top = 3

yigit_dolu() mesajı basar ve exit(-1) ile tüm programı bitirir. Kütüphane kodunda bu kaba bir tepkidir; bool döndüren sürüm kararı çağırana bırakır.

void yigit_ekle(int *top, element item) {
  if (*top >= MAX_STACK_SIZE - 1)
    yigit_dolu();  /* "Yigit dolu!!!", exit */
  yigit[++*top] = item;
}
element yigit_al(int *top) {
  if (*top < 0)
    yigit_bos();   /* "Yigit bos!!!", exit */
  return yigit[(*top)--];
}

Dizi ile Yığın

Daha İyi Tasarım: Yığın Bir Nesne Gibi

#include <stdbool.h>
#define KAP 100

typedef struct {
    int veri[KAP];
    int ust;               /* boşken -1 */
} Yigit;

void yigit_baslat(Yigit *y) { y->ust = -1; }
bool yigit_bos(const Yigit *y)  { return y->ust < 0; }
bool yigit_dolu(const Yigit *y) { return y->ust == KAP - 1; }
int  yigit_boyut(const Yigit *y) { return y->ust + 1; }
bool yigit_ust(const Yigit *y, int *x) {
    if (yigit_bos(y)) return false;
    *x = y->veri[y->ust];  return true;
}
  • Yığın ve ust aynı yapıda: iki yığın aynı programda karışmadan yaşar.
  • Küresel değişken yok; her yordam yığını parametre olarak alır.
  • Hata durumunda exit yerine false döner; kararı çağıran verir.
Hocadan “Global değişken kullanmak yasak! İçeride yığın, kuyruk, local değişken, ne gerekiyorsa kullanılabilir. Ama global değişken yok!”

Dizi ile Yığın · Kodu adım adım çalıştır

Kod İzleme: bool Döndüren push / pop

[0][1][2]ust = -1KAP = 3boyut = ust + 1 = 0

yigit_baslat(&y) sonrası ust = −1. Sırayla push 5, 8, 2, 9; sonra dört kez pop. KAP = 3.

[0][1][2]ust = -1KAP = 3boyut = ust + 1 = 0

-1 == 2? Hayır, yer var.

5[0][1][2]ust = 0KAP = 3boyut = ust + 1 = 1

++y->ust → 0; veri[0] = 5; true döner.

5[0][1][2]ust = 0KAP = 3boyut = ust + 1 = 1

0 == 2? Hayır, yer var.

5[0]8[1][2]ust = 1KAP = 3boyut = ust + 1 = 2

++y->ust → 1; veri[1] = 8; true döner.

5[0]8[1][2]ust = 1KAP = 3boyut = ust + 1 = 2

1 == 2? Hayır, yer var.

5[0]8[1]2[2]ust = 2KAP = 3boyut = ust + 1 = 3

++y->ust → 2; veri[2] = 2; true döner.

5[0]8[1]2[2]ust = 2KAP = 3boyut = ust + 1 = 3

ust == KAP − 1 (2 == 2) → return false. Program sürer; çağıran taşmayı öğrenir.

5[0]8[1]2[2]ust = 1KAP = 3boyut = ust + 1 = 2

Boş değil; *x = veri[2] = 2, sonra ust = 1.

5[0]8[1][2]ust = 0KAP = 3boyut = ust + 1 = 1

Boş değil; *x = veri[1] = 8, sonra ust = 0.

5[0][1][2]ust = -1KAP = 3boyut = ust + 1 = 0

Boş değil; *x = veri[0] = 5, sonra ust = -1.

[0][1][2]ust = -1KAP = 3boyut = ust + 1 = 0

ust < 0 → return false: alt taşma, *x'e dokunulmadı.

bool push(Yigit *y, int x) {
  if (y->ust == KAP - 1) return false; /* taşma */
  y->veri[++y->ust] = x;
  return true;
}
bool pop(Yigit *y, int *x) {
  if (y->ust < 0) return false;   /* alt taşma */
  *x = y->veri[y->ust--];
  return true;
}

Dizi ile Yığın

Bileşen Gibi Yaz: yigit.h ve yigit.c

/* yigit.h: kullanıcı yalnız bunu görür */
#ifndef YIGIT_H
#define YIGIT_H
#include <stdbool.h>
typedef struct Yigit Yigit;  /* opak */
Yigit *yigit_olustur(int kapasite);
void yigit_yok_et(Yigit *y);
bool yigit_ekle(Yigit *y, int x);
bool yigit_al(Yigit *y, int *x);
bool yigit_bos(const Yigit *y);
#endif

Kullanan kod struct'ın içini göremez; yalnız işlemleri çağırır. Gerçekleme diziden bağlı listeye geçse bile kullanan kod değişmez.

/* yigit.c: gerçekleme */
struct Yigit { int *veri; int ust, kap; };

Yigit *yigit_olustur(int kap) {
  Yigit *y = malloc(sizeof *y);
  if (y == NULL) return NULL;
  y->veri = malloc(kap * sizeof *y->veri);
  if (y->veri == NULL) {
    free(y);  return NULL;
  }
  y->ust = -1;  y->kap = kap;
  return y;
}
void yigit_yok_et(Yigit *y) {
  if (y) { free(y->veri); free(y); }
}

Dizi ile Yığın · Kodu adım adım çalıştır

Kod İzleme: Yığınla Dizge Ters Çevirme

0Y1I2G3I4Tsust = -1

Girdi "YIGIT"; boş yığın kuruldu.

0Y1I2G3I4T▲ isYust = 0

s[0] = 'Y' ≠ '\0' → push('Y').

0Y1I2G3I4T▲ isYIust = 1

s[1] = 'I' ≠ '\0' → push('I').

0Y1I2G3I4T▲ isYIGust = 2

s[2] = 'G' ≠ '\0' → push('G').

0Y1I2G3I4T▲ isYIGIust = 3

s[3] = 'I' ≠ '\0' → push('I').

0Y1I2G3I4T▲ isYIGITust = 4

s[4] = 'T' ≠ '\0' → push('T').

0T1I2G3I4T▲ isYIGIust = 3

pop → 'T', s[0] = 'T'.

0T1I2G3I4T▲ isYIGust = 2

pop → 'I', s[1] = 'I'.

0T1I2G3I4T▲ isYIust = 1

pop → 'G', s[2] = 'G'.

0T1I2G3I4T▲ isYust = 0

pop → 'I', s[3] = 'I'.

0T1I2G3I4Y▲ isust = -1

pop → 'Y', s[4] = 'Y'.

0T1I2G3I4Ysust = -1

Yığın boş: pop false döner, döngü biter. Sonuç "TIGIY": n push + n pop → O(n).

void ters_cevir(char *s) {
  Yigit y;  int c, i;
  yigit_baslat(&y);
  for (i = 0; s[i] != '\0'; i++)
    push(&y, s[i]);     /* KAP yeterli varsayıldı */
  for (i = 0; pop(&y, &c); i++)
    s[i] = (char)c;
}

Bağlı Yığın

Bağlı Liste Kullanarak Yığın

topCBANULLüst (son eklenen)alt (ilk eklenen)

A, B, C sırasıyla eklendi: top liste başını gösterir.

  • Her öğe bir düğüm (node): deger + sonraki düğüme bag.
  • Ekleme ve silme liste başında: ikisi de O(1), kaydırma yok.
  • Kapasite sınırı yok: yalnız malloc başarısız olursa taşma olur.
Neden baş, neden son değil? Silerken son öğeden geri gelmek gerekir; tek yönlü bağlı listede bir önceki düğümü bulmak O(n)'dir; bu yüzden top = liste başı.
Hocadan “Top, liste başını tutar. Silerken son öğeden geriye gelmek gerekeceği için ya iki işaretçiyle son tutulur ya da tek işaretçiyle baş tutulur.”

Bağlı Yığın · Kodu adım adım çalıştır

Kod İzleme: Bağlı push

*topNULL

Başlangıç: dugum *top = NULL; (boş yığın). Sırayla push 10, 20, 30.

*topNULL?← yeni

malloc yeni bir düğüm ayırır; NULL değilse devam. Alanları henüz belirsiz.

*topNULL10← yeni

yeni->deger = 10.

*topNULL10NULL← yeni

yeni->bag = *top: yeni düğüm eski üstü (NULL) gösterir. Sıra önemli: önce bağla, sonra *top'u değiştir.

*top10NULL← yeni

*top = yeni: çağıranın top'u artık 10'u gösterir. Yığın: 10.

*top?← yeni10NULL

malloc yeni bir düğüm ayırır; NULL değilse devam. Alanları henüz belirsiz.

*top20← yeni10NULL

yeni->deger = 20.

*top20← yeni10NULL

yeni->bag = *top: yeni düğüm eski üstü (düğüm(10)) gösterir. Sıra önemli: önce bağla, sonra *top'u değiştir.

*top20← yeni10NULL

*top = yeni: çağıranın top'u artık 20'u gösterir. Yığın: 20 → 10.

*top?← yeni2010NULL

malloc yeni bir düğüm ayırır; NULL değilse devam. Alanları henüz belirsiz.

*top30← yeni2010NULL

yeni->deger = 30.

*top30← yeni2010NULL

yeni->bag = *top: yeni düğüm eski üstü (düğüm(20)) gösterir. Sıra önemli: önce bağla, sonra *top'u değiştir.

*top30← yeni2010NULL

*top = yeni: çağıranın top'u artık 30'u gösterir. Yığın: 30 → 20 → 10.

*top302010NULL

Üç push sonrası: 30 → 20 → 10 → NULL. Her push sabit sayıda iş: O(1).

bool push(dugum **top, int x) {
  dugum *yeni = malloc(sizeof *yeni);
  if (yeni == NULL) return false;  /* bellek yok */
  yeni->deger = x;
  yeni->bag = *top;      /* eski üstü göster */
  *top = yeni;           /* yeni üst */
  return true;
}

Bağlı Yığın · Kodu adım adım çalıştır

Kod İzleme: Bağlı pop

*top302010NULL

Yığın: 30 → 20 → 10. Dört kez pop çağrılacak; her başarılı sonucu main yazdırır.

*top302010NULLeski

eski = *top: yalnızca bir işaretçi kopyası; yeni bellek ayrılmaz.

*top302010NULLeski

*x = 30 (değer, düğüm silinmeden önce alınmalı).

*top302010NULLeski

*top = eski->bag → düğüm(20).

*top30free edildi2010NULL

free(eski): 30 düğümünün belleği sisteme döner. main 30 yazar.

*top30free edildi2010NULLeski

eski = *top: yalnızca bir işaretçi kopyası; yeni bellek ayrılmaz.

*top30free edildi2010NULLeski

*x = 20 (değer, düğüm silinmeden önce alınmalı).

*top30free edildi2010NULLeski

*top = eski->bag → düğüm(10).

*top30free edildi20free edildi10NULL

free(eski): 20 düğümünün belleği sisteme döner. main 20 yazar.

*top30free edildi20free edildi10NULLeski

eski = *top: yalnızca bir işaretçi kopyası; yeni bellek ayrılmaz.

*top30free edildi20free edildi10NULLeski

*x = 10 (değer, düğüm silinmeden önce alınmalı).

*topNULL30free edildi20free edildi10NULLeski

*top = eski->bag → NULL.

*topNULL30free edildi20free edildi10NULLfree edildi

free(eski): 10 düğümünün belleği sisteme döner. main 10 yazar.

*topNULL30free edildi20free edildi10NULLfree edildi

eski = NULL → yığın boş, return false. Döngü burada durur.

bool pop(dugum **top, int *x) {
  dugum *eski = *top;      /* malloc yok! */
  if (eski == NULL) return false;  /* boş */
  *x = eski->deger;
  *top = eski->bag;        /* üstü ilerlet */
  free(eski);              /* belleği bırak */
  return true;
}

Bağlı Yığın

Bağlı Yığında Bellek Sorumluluğu

typedef struct dugum {
    int deger;
    struct dugum *bag;
} dugum;

void yigit_temizle(dugum **top) {
    while (*top != NULL) {
        dugum *eski = *top;
        *top = eski->bag;
        free(eski);
    }
}                  /* sonunda *top == NULL */
  • eski bir işaretçi değişkenidir, yeni düğüm değil: ona malloc gerekmez.
  • Önce bağ kurtarılır (*top = eski->bag), sonra free çağrılır: tersi serbest bırakılmış belleği okur.
  • Program bitmeden yigit_temizle çağrılır: her malloc'un bir free'si olmalı.
Hocadan “Her pointer’a mallocla yer ayırmak gerekmiyor, yok öyle bir şey!”
Hocadan “Pointerlar bu yaşlarda öğrenilirse öğrenilir; yoksa 4. sınıfta bile pointer öğrenilmemiş olur.”

Bağlı Yığın

Dizi Tabanlı mı, Bağlı mı?

ÖlçütSabit diziBüyüyen diziBağlı liste
KapasiteDerleme anında sabitGerektikçe ×2Bellek bitene kadar
push / popO(1)O(1) amortizeO(1), her push'ta malloc
Öğe başına ek bellekYokYok (boş kapasite hariç)Bir işaretçi (8 bayt)
Önbellek (cache)Çok iyiÇok iyiZayıf (düğümler dağınık)
TaşmaMümkünYalnız realloc başarısızsaYalnız malloc başarısızsa
Hata riskiSınır denetimirealloc sonucufree unutmak, sarkan işaretçi

Üst sınır biliniyorsa sabit dizi; bilinmiyorsa büyüyen dizi pratikte en hızlısıdır. Bağlı yığın, düğümlerin zaten başka yapılarda yaşadığı ya da tek tek yaratılıp silindiği durumlar için uygundur.

Çağrı Yığını

Çağrı Yığını da Taşar

Çağrı yığını bir yığındır: her çağrı dönüş adresi, parametreler ve yerellerden oluşan bir çerçeveyi push eder, return onu pop eder; en son çağrılan fonksiyon ilk biter.

Sonsuz özyineleme

Taban durumu olmayan ya da ona ulaşmayan özyineleme her çağrıda çerçeve iter; sistem yığını (tipik olarak birkaç MB) dolunca program stack overflow ile çöker.

Büyük yerel diziler

int a[1000000]; gibi yerel bir dizi çerçevenin içindedir; tek başına yığını taşırabilir. Büyük veriler için malloc ile heap bellek kullanılır.

Yerel adres döndürme

return &yerel; hatalıdır: çerçeve pop edilince o bellek başka çağrılarca yeniden kullanılır (sarkan işaretçi).

Özyinelemeden kurtulmak

Her özyinelemeli algoritma, kendi yığınını tutan bir döngüye çevrilebilir.

Çoklu Yığın

Çoklu Yığın: Önce İki Yığın

0318213456672▲ top1 = 2▲ top2 = 6yığın 1 sağa büyüryığın 2 sola büyür
  • Yığın 1 0'dan yukarı, yığın 2 m−1'den aşağı büyür.
  • Boş: top1 = −1, top2 = m.
  • Dolu: top1 + 1 == top2: ikisi ortada buluşunca.
Avantaj: bir yığın az, öteki çok öğe tutsa bile hiç hücre boşa gitmez; dizi ancak toplam m öğe olunca dolar. Üç ve daha fazla yığın için bu hile yetmez.

Çoklu Yığın · Kodu adım adım çalıştır

Kod İzleme: Tek Bellekte n Yığın

01234567891011yığın 0: top[0] = -1yığın 1: top[1] = 3yığın 2: top[2] = 7sinir = [-1, 3, 7, 11]

MEM = 12, n = 3: her yığına 4 hücre. Başta top[i] = sinir[i] = (m/n)·i − 1, sinir[n] = m − 1.

051234567891011yığın 0: top[0] = 0yığın 1: top[1] = 3yığın 2: top[2] = 7sinir = [-1, 3, 7, 11]

Yer var; bellek[0] = 5, top[0] = 0.

0512347567891011yığın 0: top[0] = 0yığın 1: top[1] = 4yığın 2: top[2] = 7sinir = [-1, 3, 7, 11]

Yer var; bellek[4] = 7, top[1] = 4.

05192347567891011yığın 0: top[0] = 1yığın 1: top[1] = 4yığın 2: top[2] = 7sinir = [-1, 3, 7, 11]

Yer var; bellek[1] = 9, top[0] = 1.

051923475678491011yığın 0: top[0] = 1yığın 1: top[1] = 4yığın 2: top[2] = 8sinir = [-1, 3, 7, 11]

Yer var; bellek[8] = 4, top[2] = 8.

0519223475678491011yığın 0: top[0] = 2yığın 1: top[1] = 4yığın 2: top[2] = 8sinir = [-1, 3, 7, 11]

Yer var; bellek[2] = 2, top[0] = 2.

05192238475678491011yığın 0: top[0] = 3yığın 1: top[1] = 4yığın 2: top[2] = 8sinir = [-1, 3, 7, 11]

Yer var; bellek[3] = 8, top[0] = 3.

05192238475678491011yığın 0: top[0] = 3yığın 1: top[1] = 4yığın 2: top[2] = 8sinir = [-1, 3, 7, 11]

top[0] == sinir[1] (3 == 3) → yığın 0 dolu, oysa bellekte 6 boş hücre var! Horowitz'in stack_full'u burada yığınları kaydırıp yer açar.

0519223845678491011yığın 0: top[0] = 3yığın 1: top[1] = 3yığın 2: top[2] = 8sinir = [-1, 3, 7, 11]

top[1] ≠ sinir[1] → boş değil; *x = 7, top[1] = 3.

/* yığın i: bellek[sinir[i]+1 .. sinir[i+1]] */
bool ekle(int i, int x) {
  if (top[i] == sinir[i + 1]) return false; /* dolu */
  bellek[++top[i]] = x;
  return true;
}
bool al(int i, int *x) {
  if (top[i] == sinir[i]) return false;     /* boş */
  *x = bellek[top[i]--];
  return true;
}

Çoklu Yığın

n Yığının Paylaşımı: Artılar ve Eksiler

Tek boyutlu bir dizi (memory[MEMORY_SIZE]) n kesime (segment) bölünerek n yığın kurulabilir; yığınların top ve sınır değerleri iki yardımcı dizide tutulur.

Artılar

  • Tek malloc, tek dizi: yönetimi kolay.
  • push/pop yine O(1).
  • Aynı kuyruk fikri de uygulanabilir (n kuyruk).

Eksiler

  • Bir kesim dolunca, bellekte yer olsa bile “dolu” hatası.
  • Yer açmak için yığınları kaydırmak O(m): kaydırma pahalıdır.
  • Kesim boyutlarını önceden kestirmek zor.
Horowitz–Sahni'nin çözümü: stack_full(i), önce sağdaki, yoksa soldaki boş yeri bulur ve arada kalan yığınları bir hücre kaydırır. En kötü durumda her ekleme O(m) olabilir.

Özet

Hangi Çıkış Sırası Mümkün?

1, 2, 3, 4 bu sırayla push edilecek; aralara istenen sayıda pop konabilir. Aşağıdaki çıkış sıralarından hangileri elde edilebilir?

Çıkış sırasıMümkün mü?
1 2 3 4
gösterEvet: her push'tan hemen sonra pop.
4 3 2 1
gösterEvet: dört push, dört pop.
2 4 3 1
gösterEvet: push1 push2 pop(2) push3 push4 pop(4) pop(3) pop(1).
3 1 4 2
gösterHayır: 3 çıktığında 1 ve 2 yığındadır, 2 üsttedir; 1, 2'den önce çıkamaz.

Genel kural: çıkışta bir x'ten sonra, x'ten önce itilmiş ve henüz çıkmamış öğeler ters sırada çıkmak zorundadır. n öğe için olası sıra sayısı Catalan sayısıdır (n = 4 için 14).

Özet

Karmaşıklık Özeti

İşlemSabit diziBüyüyen diziBağlı
pushO(1)O(1) amortize, en kötü O(n)O(1)
popO(1)O(1)O(1)
peek / bos / doluO(1)O(1)O(1)
n öğelik bellekMAX (sabit)≤ 2nn · (öğe + işaretçi)
TemizlemeO(1)O(1) (tek free)O(n)
fakt(n) çağrı yığını——O(n) çerçeve
Çoklu yığın ekleO(1); yer açma O(m)——

Özet

Sık Yapılan Hatalar

1Boş yığından pop / dolu yığına push: sınır denetimini atlamak.
2*top-- yazmak: işaretçiyi azaltır; doğrusu (*top)--.
3top değerini öğe sayısıyla karıştırmak (sayı = top + 1).
4Bağlı push'ta önce *top = yeni yazıp eski listeyi kaybetmek.
5pop'ta değeri free'den sonra okumak ya da düğümü hiç bırakmamak.
6Geçici işaretçiye gereksiz malloc yapıp bellek sızdırmak.

Özet

Alıştırmalar

  1. Dizi yığınında yigit_ekle'yi exit yerine bool döndürecek biçimde yeniden yaz.
  2. Tek dizide iki yığın için ekle(k, x) ve al(k) yordamlarını yaz (k = 1 ya da 2).
  3. Bağlı yığını, top ile birlikte boyut alanı da tutan bir struct içine taşı.
  4. Bir dizgeyi yığın kullanarak ters çeviren void ters(char *s) yordamını yaz.
  5. Yalnız push/pop kullanarak bir yığının en alttaki öğesini döndüren yordamı yaz (yardımcı yığın serbest).
  6. fakt(5) çağrısında yığında en çok kaç çerçeve olur? main dahil çiz.
İpuçları / cevaplar
  1. Taşma varsa return false;, yoksa yigit[++*top] = item; return true;.
  2. Boş: top1 = −1, top2 = m. Dolu: top1 + 1 == top2. k = 2 için ekleme --top2, alma top2++.
  3. typedef struct { dugum *top; int boyut; } BYigit;; push'ta ++boyut, başarılı pop'ta --boyut.
  4. Karakterleri sırayla push et, sonra pop ederek aynı diziye geri yaz: O(n) zaman, O(n) ek bellek.
  5. Tümünü yardımcı yığına aktar, sonuncuyu sakla, sonra geri aktar: O(n).
  6. fakt(5), fakt(4), …, fakt(1) + main = 6 çerçeve.

Özet

Bölüm Özeti

LIFO

Ekleme ve silme yalnız üstte; son giren ilk çıkar.

Dizi + top

top = −1 boş, top = MAX − 1 dolu; ++*top ve (*top)--.

Hata yönetimi

exit yerine durum döndür; taşma/alt taşma çağıranın kararı.

Bağlı yığın

top = liste başı; push/pop O(1); her çıkan düğüme free.

Çağrı yığını

Her çağrı bir çerçeve: dönüş adresi, yereller, önceki çerçeve.

Çoklu yığın

Tek bellek, top[i] ve sinir[i]; kesim dolarsa kaydırma gerekir.

Özet

Kaynaklar ve Devam

  • E. Horowitz, S. Sahni, S. Anderson-Freed, Fundamentals of Data Structures in C, 2. bs., Bölüm 3.1–3.2, 3.7 ve 4.3.
  • T. H. Cormen vd., Introduction to Algorithms, 3. bs., Bölüm 10.1 (yığınlar) ve 17 (amortize analiz).
  • R. Sedgewick, Algorithms in C, Kısım 1–4, Bölüm 4.

Son

Bölüm 6: Yığınlar
Sonraki bölüm: Bölüm 7: Kuyruklar · Ders sayfası
1 / 1 C ile Veri Yapıları