Stack adalah struktur data LIFO (Last In, First Out): elemen terakhir yang dimasukkan adalah elemen pertama yang dikeluarkan. Bayarkan tumpukan piring: kita ambil dari atas dan menambah dari atas. Stack dipakai di banyak algoritma: pemrosesan rekursi (call stack), evaluasi ekspresi postfix, algoritma backtracking, dan cek kurung seimbang. Pada chapter ini kita akan mengimplementasikan stack dengan dua pendekatan: array statis (kapasitas tetap) dan linked list dinamis (tanpa batas kapasitas), lalu memakainya untuk solver cek kurung seimbang.
Operasi Dasar Stack #
Empat operasi standar stack:
- Push: tambahkan elemen ke atas stack.
- Pop: ambil elemen dari atas stack (dan hapus).
- Peek: baca elemen atas tanpa menghapus.
- IsEmpty / IsFull: cek kondisi stack.
Push dan pop idealnya O(1). Bila stack berbasis array penuh, push bisa gagal atau memicu realokasi O(n). Stack berbasis linked list selalu O(1) untuk push/pop, tetapi setiap elemen membutuhkan overhead pointer next.
Stack Berbasis Array #
Implementasi paling sederhana: array statis dengan indeks top. Indeks -1 berarti kosong; indeks kapasitas-1 berarti penuh.
#include <stdio.h>
#include <stdlib.h>
#define KAPASITAS 8
typedef struct {
int data[KAPASITAS];
int top;
} stack_array;
void sa_init(stack_array *s) { s->top = -1; }
int sa_kosong(stack_array *s) { return s->top == -1; }
int sa_penuh(stack_array *s) { return s->top == KAPASITAS - 1; }
int sa_push(stack_array *s, int val) {
if (sa_penuh(s)) return -1;
s->data[++s->top] = val;
return 0;
}
int sa_pop(stack_array *s, int *out) {
if (sa_kosong(s)) return -1;
*out = s->data[s->top--];
return 0;
}
int sa_peek(stack_array *s, int *out) {
if (sa_kosong(s)) return -1;
*out = s->data[s->top];
return 0;
}
int main(void) {
stack_array s;
sa_init(&s);
for (int i = 10; i <= 60; i += 10) {
if (sa_push(&s, i) == 0)
printf("Push %d (top=%d)\n", i, s.top);
else
printf("Push %d gagal: stack penuh\n", i);
}
int top;
if (sa_peek(&s, &top) == 0)
printf("Peek: %d\n", top);
for (int i = 70; i <= 100; i += 10) {
if (sa_push(&s, i) != 0)
printf("Push %d gagal: stack penuh\n", i);
else
printf("Push %d (top=%d)\n", i, s.top);
}
while (!sa_kosong(&s)) {
int val;
sa_pop(&s, &val);
printf("Pop %d\n", val);
}
int val;
if (sa_pop(&s, &val) != 0)
printf("Pop gagal: stack kosong\n");
return 0;
}Output yang Dihasilkan:
Push 10 (top=0)
Push 20 (top=1)
Push 30 (top=2)
Push 40 (top=3)
Push 50 (top=4)
Push 60 (top=5)
Peek: 60
Push 70 (top=6)
Push 80 (top=7)
Push 90 gagal: stack penuh
Push 100 gagal: stack penuh
Pop 80
Pop 70
Pop 60
Pop 50
Pop 40
Pop 30
Pop 20
Pop 10
Pop gagal: stack kosongStack berbasis array menolak push bila penuh (top == KAPASITAS-1) dan menolak pop bila kosong (top == -1). Output terurut terbalik dari input—karakteristik LIFO.
Mari kita telaah baris-baris kode di atas:
- Baris 7-10 (
stack_arraystruct): struct berisi arraydatadengan kapasitas tetap dan indekstop. Inisialisasitop = -1menandakan stack kosong. - Baris 13 (
sa_penuh): cek apakahtopsudah di indeks terakhir.KAPASITAS - 1karena indeks mulai dari 0. - Baris 16-19 (
sa_push): naikkantopdulu (++s->top), lalu tulisdata[top]. Urutan ini penting: harus naikkan dulu karenatopmulai dari-1. Bila penuh, return-1sebagai kode error. - Baris 21-25 (
sa_pop): bacadata[top], lalu turunkantop. Pakai postfix decrements->top--agar pembacaan terjadi sebelum pengurangan. - Baris 27-31 (
sa_peek): bacadata[top]tanpa mengubahtop. Berguna untuk lihat elemen atas tanpa mengeluarkannya. - Baris 41 (
sa_push(&s, i) == 0): pemanggil wajib cek return code. Push mengembalikan0sukses atau-1gagal (stack penuh).
Stack Berbasis Linked List #
Stack berbasis linked list tidak punya batas kapasitas (selain memori sistem). Setiap push alokasi node baru; setiap pop membebaskan node.
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
int data;
struct node *next;
} node;
typedef struct {
node *top;
size_t ukuran;
} stack_list;
void sl_init(stack_list *s) { s->top = NULL; s->ukuran = 0; }
int sl_kosong(stack_list *s) { return s->top == NULL; }
int sl_push(stack_list *s, int val) {
node *n = malloc(sizeof(node));
if (n == NULL) return -1;
n->data = val;
n->next = s->top;
s->top = n;
s->ukuran++;
return 0;
}
int sl_pop(stack_list *s, int *out) {
if (sl_kosong(s)) return -1;
node *tmp = s->top;
*out = tmp->data;
s->top = tmp->next;
free(tmp);
s->ukuran--;
return 0;
}
int sl_peek(stack_list *s, int *out) {
if (sl_kosong(s)) return -1;
*out = s->top->data;
return 0;
}
void sl_hapus_semua(stack_list *s) {
int val;
while (sl_pop(s, &val) == 0) ;
}
/* Demo: cek kurung seimbang dengan stack */
int cek_kurung(const char *str) {
stack_list s;
sl_init(&s);
int hasil = 1;
for (size_t i = 0; str[i] != '\0'; i++) {
char c = str[i];
if (c == '(' || c == '[' || c == '{') {
sl_push(&s, (int)c);
} else if (c == ')' || c == ']' || c == '}') {
int top;
if (sl_pop(&s, &top) != 0) {
hasil = 0;
break;
}
char pasangan = (char)top;
if ((c == ')' && pasangan != '(') ||
(c == ']' && pasangan != '[') ||
(c == '}' && pasangan != '{')) {
hasil = 0;
break;
}
}
}
if (hasil && !sl_kosong(&s)) hasil = 0;
sl_hapus_semua(&s);
return hasil;
}
int main(void) {
stack_list s;
sl_init(&s);
for (int i = 1; i <= 5; i++) {
sl_push(&s, i * 10);
printf("Push %d (ukuran=%zu)\n", i * 10, s.ukuran);
}
int top;
sl_peek(&s, &top);
printf("Peek: %d\n", top);
while (!sl_kosong(&s)) {
int val;
sl_pop(&s, &val);
printf("Pop %d\n", val);
}
printf("\nCek kurung:\n");
const char *uji[] = {"(a+b)", "(a+b]", "((a+b)*c)", "([{}])", "((())"};
for (int i = 0; i < 5; i++) {
printf(" '%s' -> %s\n", uji[i],
cek_kurung(uji[i]) ? "seimbang" : "tidak seimbang");
}
return 0;
}Output yang Dihasilkan:
Push 10 (ukuran=1)
Push 20 (ukuran=2)
Push 30 (ukuran=3)
Push 40 (ukuran=4)
Push 50 (ukuran=5)
Peek: 50
Pop 50
Pop 40
Pop 30
Pop 20
Pop 10
Cek kurung:
'(a+b)' -> seimbang
'(a+b]' -> tidak seimbang
'((a+b)*c)' -> seimbang
'([{}])' -> seimbang
'((())' -> tidak seimbangDemo cek kurung seimbang memperlihatkan kekuatan stack: setiap kurung buka di-push, setiap kurung tutup di-pop dan dicocokkan. Bila pasangan tidak sesuai atau stack tidak kosong di akhir, kurung tidak seimbang.
Perbandingan Implementasi #
| Aspek | Array | Linked List |
|---|---|---|
| Kapasitas | Tetap (#define) | Dinamis (terbatas memori) |
| Push | O(1) atau gagal | O(1) + malloc |
| Pop | O(1) | O(1) + free |
| Memori overhead | 0 | 1 pointer per elemen |
| Cache locality | Baik | Buruk |
| Kegagalan push | Bila penuh | Bila malloc gagal |