Sesi 5 — Data Structures: Stack, Queue, Tree & Binary Tree (incl. Space Complexity)
Sesi 5 — Data Structures: Stack, Queue, Tree & Binary Tree (incl. Space Complexity)
COMP6049001 — Algorithm Design and Analysis
Daftar Isi
- Stack and Queue
- Space Complexity
- Tree Terminology and Traversals
- Binary Tree Properties and Operations
- Time vs. Space Trade-off
Kaitan dengan Sesi Sebelumnya
Sesi ini adalah penerapan dari alat analisis yang sudah dibangun di Sesi 1–4:
| Konsep dari sesi sebelumnya | Diterapkan di Sesi 5 sebagai |
|---|---|
| Notasi O, Ω, Θ (Sesi 4) | Menyatakan kompleksitas operasi stack/queue/BST |
| Worst vs. average case (Sesi 3) | BST height: O(log n) rata-rata vs. O(n) worst-case |
| Space complexity, in-place (Sesi 3 & 4) | Dibahas jauh lebih detail: auxiliary vs. total space |
| Time-space tradeoff (Sesi 4, slide 22) | Diperluas dengan tabel pemilihan struktur data |
1. Stack and Queue
1.1 Stack — LIFO (Last In, First Out)
Stack adalah dynamic set dengan disiplin LIFO: elemen yang terakhir masuk adalah yang pertama keluar. Hanya ada satu titik akses, yaitu TOP.
Analogi: tumpukan piring — piring terakhir yang ditaruh di atas adalah yang pertama diambil.
Operasi Dasar
| Operasi | Arti |
|---|---|
PUSH(S, x) |
Masukkan elemen x ke atas tumpukan |
POP(S) |
Keluarkan dan kembalikan elemen paling atas |
STACK-EMPTY(S) |
Kembalikan TRUE jika stack kosong |
PEEK(S) |
Lihat elemen teratas tanpa mengeluarkannya |
Implementasi Array (CLRS)
S.top = 0 // kondisi awal: stack kosong
PUSH(S, x)
1 S.top = S.top + 1
2 S[S.top] = x
POP(S)
1 if STACK-EMPTY(S)
2 error "underflow"
3 S.top = S.top - 1
4 return S[S.top + 1]
Kenapa semua operasi O(1)? Karena tidak ada loop sama sekali — setiap operasi cuma melakukan sejumlah tetap instruksi (increment/decrement counter, satu akses array). Tidak peduli stack berisi 10 atau 10 juta elemen, jumlah langkahnya sama.
Kompleksitas Stack
| Operasi | Time (worst-case) | Auxiliary Space |
|---|---|---|
| PUSH | O(1) | — |
| POP | O(1) | — |
| PEEK | O(1) | — |
| STACK-EMPTY | O(1) | — |
| SEARCH (cari elemen tertentu) | O(n) | — |
| Total space | — | O(n) |
Implementasi Linked-List
Setiap node menyimpan data + pointer ke node berikutnya. PUSH dan POP dilakukan di head.
Perbandingan dengan array:
| Aspek | Array | Linked List |
|---|---|---|
| Time per operasi | O(1) | O(1) |
| Total space | O(n) — n = kapasitas array | O(k) — k = jumlah elemen saat ini |
| Kelemahan | Kapasitas tetap; ruang terbuang jika tidak penuh | Overhead pointer per node |
Linked list tidak membuang ruang untuk slot kosong, tapi setiap node butuh memori ekstra untuk pointer.
Aplikasi Stack
- Function call stack — bagaimana bahasa pemrograman melacak pemanggilan fungsi bersarang
- Expression evaluation — konversi infix ke postfix, evaluasi ekspresi aritmatika
- DFS (Depth-First Search) — traversal graph/tree secara mendalam
- Undo operations — di text editor, Photoshop, dll
- Balanced parentheses checking — validasi pasangan kurung dalam kode
1.2 Queue — FIFO (First In, First Out)
Queue adalah dynamic set dengan disiplin FIFO: elemen yang pertama masuk adalah yang pertama keluar. Ada dua titik akses: REAR (untuk memasukkan) dan FRONT (untuk mengeluarkan).
Analogi: antrean di kasir — yang datang duluan dilayani duluan.
Operasi Dasar
| Operasi | Arti |
|---|---|
ENQUEUE(Q, x) / PUSH(x) |
Masukkan elemen x di REAR |
DEQUEUE(Q) / POP() |
Keluarkan elemen di FRONT |
QUEUE-EMPTY(Q) / EMPTY() |
Cek apakah queue kosong |
Kompleksitas Queue
| Operasi | Time (worst-case) | Catatan |
|---|---|---|
| ENQUEUE | O(1) | Tinggal tambah di REAR |
| DEQUEUE | O(1) dengan circular array atau linked list | |
| DEQUEUE | O(n) dengan array biasa (non-circular) | Karena semua elemen harus digeser satu posisi ke kiri |
| EMPTY | O(1) | Cukup bandingkan pointer FRONT dan REAR |
| Total space | — | O(n) |
Poin penting untuk latihan: DEQUEUE adalah satu-satunya operasi yang kompleksitasnya bergantung pada implementasi. Dengan array biasa, setelah mengambil elemen depan, semua elemen sisanya harus digeser → O(n). Dengan circular array (indeks FRONT bergerak maju dan "membungkus" ke awal array saat mencapai ujung) atau linked list, tidak ada penggeseran → O(1).
Aplikasi Queue
- BFS (Breadth-First Search) — traversal graph/tree per level
- Job scheduling / print queue — antrean tugas yang dilayani sesuai urutan datang
- Buffering — streaming data, keyboard buffer
1.3 Perbandingan Stack vs. Queue
| Stack | Queue | |
|---|---|---|
| Disiplin | LIFO | FIFO |
| Titik akses | 1 (TOP) | 2 (FRONT & REAR) |
| Traversal yang memakainya | DFS | BFS |
| Operasi inti | O(1) | O(1) |
2. Space Complexity
2.1 Auxiliary vs. Total Space
Ini pembedaan yang krusial dan sering tertukar:
| Istilah | Definisi |
|---|---|
| Auxiliary space | Memori tambahan yang dipakai algoritma, di luar input itu sendiri |
| Total space | Auxiliary space + ruang untuk menyimpan input |
Dalam analisis algoritma, yang biasanya dilaporkan adalah auxiliary space — karena itulah "biaya memori" yang benar-benar ditimbulkan oleh algoritma. Ruang untuk input dianggap sudah ada sebelum algoritma dijalankan, jadi bukan "tanggung jawab" algoritma.
Contoh: Insertion Sort mengurutkan array berukuran n.
- Total space = O(n) — karena arraynya sendiri butuh n slot
- Auxiliary space = O(1) — karena Insertion Sort hanya butuh beberapa variabel sementara (key, i, j), berapapun besar n
Inilah yang dimaksud in-place di Sesi 3: algoritma yang auxiliary space-nya O(1).
2.2 Tabel Auxiliary Space Algoritma Umum
| Algoritma | Time | Auxiliary Space | Alasan |
|---|---|---|---|
| Insertion Sort | O(n²) | O(1) | In-place — hanya geser elemen di array yang sama |
| Merge Sort | O(n log n) | O(n) | Proses merge butuh array sementara |
| Binary Search (rekursif) | O(log n) | O(log n) | Call stack sedalam log n |
| Binary Search (iteratif) | O(log n) | O(1) | Tidak ada rekursi |
| Quick Sort | O(n log n) avg | O(log n) rekursif | Call stack sedalam kedalaman rekursi |
| BFS pada graph | O(V + E) | O(V) | Queue + array visited |
| DFS pada graph | O(V + E) | O(V) | Stack (atau call stack) + array visited |
Catatan: tabel di slide kuliah memuat beberapa angka yang tampaknya keliru ketik (Binary Search tertulis "O(n log n) avg" dan Quick Sort tertulis "O(log n)" di kolom Time). Angka yang benar sesuai CLRS adalah: Binary Search = O(log n) time, Quick Sort = O(n log n) expected / O(n²) worst-case time.
2.3 Stack Space untuk Rekursi
Ini konsep yang sering terlewat: setiap pemanggilan rekursif memakan ruang di call stack, karena komputer harus mengingat "di mana harus kembali" setelah fungsi selesai.
Aturan umum:
$$\text{Auxiliary space rekursi} = O(\text{kedalaman rekursi})$$
| Jenis rekursi | Kedalaman | Auxiliary space | Contoh |
|---|---|---|---|
| Balanced (membelah dua) | log n | O(log n) | Merge Sort, Binary Search |
| Linear (mengurangi satu) | n | O(n) | Fibonacci naif, DFS pada path graph |
Implikasi praktis: rekursi linear pada input besar bisa menyebabkan stack overflow — program crash karena call stack penuh. Inilah alasan mengapa untuk input sangat besar, versi iteratif sering lebih aman daripada versi rekursif.
3. Tree Terminology and Traversals
3.1 Definisi Formal Rooted Tree
Rooted tree T adalah graph tak berarah yang connected, acyclic, dengan satu node ditetapkan sebagai root r.
Tiga syarat ini harus dipenuhi sekaligus:
- Connected — semua node terhubung, tidak ada yang terisolasi
- Acyclic — tidak ada siklus (tidak bisa berputar kembali ke node yang sama)
- Designated root — satu node dipilih sebagai "atas", ini yang menciptakan hierarki
Kalau salah satu syarat tidak terpenuhi:
- Acyclic tapi tidak connected → forest (kumpulan beberapa tree terpisah)
- Connected tapi ada cycle → graph biasa, bukan tree
Sifat yang mengikuti: tree dengan n node selalu punya tepat n − 1 edge.
3.2 Istilah Kunci
Gunakan tree contoh berikut untuk semua definisi:
A <- depth 0
/ | \
B C D <- depth 1
/ \ \
E F G <- depth 2
| Istilah | Definisi | Contoh |
|---|---|---|
| Node (vertex) | Setiap elemen tree | A, B, C, D, E, F, G |
| Root | Node paling atas, tanpa parent | A |
| Parent / Child | x parent dari y jika x satu level di atas y pada path y ke root | A parent dari B, C, D |
| Leaf | Node tanpa anak | C, E, F, G |
| Internal node | Node dengan minimal satu anak | A, B, D |
| Depth of node x | Panjang path dari root ke x | depth(A)=0, depth(B)=1, depth(E)=2 |
| Height of tree | Depth maksimum di seluruh tree | 2 |
| Subtree rooted at x | x beserta semua keturunannya | Subtree di B = {B, E, F} |
Depth vs. Height: Depth dihitung dari root ke bawah (setiap node punya depth sendiri). Height of tree adalah depth terbesar di seluruh tree (satu angka untuk seluruh tree).
3.3 Tree Traversals
Untuk binary tree (setiap node punya left dan right child), ada tiga cara standar menjelajah. Ketiganya adalah varian DFS — bedanya hanya pada kapan node induk dicetak relatif terhadap anak-anaknya.
Preorder (Root → Left → Right)
PREORDER(x)
1 if x ≠ NIL
2 print x.key
3 PREORDER(x.left)
4 PREORDER(x.right)
Kegunaan: menyalin/menduplikasi struktur tree, serialisasi tree.
Inorder (Left → Root → Right)
INORDER(x)
1 if x ≠ NIL
2 INORDER(x.left)
3 print x.key
4 INORDER(x.right)
Kegunaan: menghasilkan output terurut jika dijalankan pada Binary Search Tree. Ini sifat yang sangat penting dan akan sering dipakai.
Postorder (Left → Right → Root)
POSTORDER(x)
1 if x ≠ NIL
2 POSTORDER(x.left)
3 POSTORDER(x.right)
4 print x.key
Kegunaan: menghapus tree (anak dihapus sebelum induk), mengevaluasi expression tree.
Contoh Konkret
Untuk binary tree berikut:
5
/ \
3 7
/ \ / \
1 4 6 8
| Traversal | Hasil |
|---|---|
| Preorder | 5, 3, 1, 4, 7, 6, 8 |
| Inorder | 1, 3, 4, 5, 6, 7, 8 ← terurut! |
| Postorder | 1, 4, 3, 6, 8, 7, 5 |
Kompleksitas Traversal
$$\text{Time} = O(n), \quad \text{Auxiliary Space} = O(h)$$
- O(n) time — setiap node dikunjungi tepat sekali
- O(h) space — h = height tree, karena call stack rekursi sedalam h. Untuk tree seimbang h = O(log n); untuk tree degenerate (menyerupai linked list) h = O(n)
4. Binary Tree Properties and Operations
4.1 Definisi dan Sifat Binary Tree
Binary tree adalah rooted tree di mana setiap node punya maksimal dua anak: left child dan right child.
Sifat-sifat penting:
| Sifat | Penjelasan |
|---|---|
| n node → n − 1 edge | Berlaku untuk semua tree, termasuk binary tree |
| Full binary tree | Setiap node punya 0 atau 2 anak (tidak ada yang punya 1 anak) |
| Complete binary tree | Semua level terisi penuh kecuali level terakhir, yang terisi dari kiri ke kanan |
| Perfect binary tree | Semua leaf berada di depth yang sama; punya tepat 2^(h+1) − 1 node |
| Height complete binary tree | h = ⌊log₂ n⌋ |
Verifikasi rumus perfect binary tree: untuk h=2, rumus memberi 2³ − 1 = 7 node. Cocok dengan contoh tree 5-3-7-1-4-6-8 di atas (7 node, semua leaf di depth 2). ✓
Mengapa height complete binary tree = ⌊log₂ n⌋? Karena setiap level menampung dua kali lipat node level sebelumnya (1, 2, 4, 8, ...). Untuk menampung n node butuh sekitar log₂ n level. Inilah alasan struktur berbasis tree seimbang bisa mencapai O(log n).
4.2 Binary Search Tree (BST)
BST property: untuk setiap node x, semua key di subtree kiri x adalah ≤ x.key, dan semua key di subtree kanan x adalah ≥ x.key.
Sifat inilah yang memungkinkan pencarian efisien: pada setiap node, kita bisa membuang separuh kemungkinan hanya dengan satu perbandingan — mirip prinsip binary search pada array terurut.
Operasi BST
BST-SEARCH(x, k)
1 if x == NIL or k == x.key
2 return x
3 if k < x.key
4 return BST-SEARCH(x.left, k)
5 else
6 return BST-SEARCH(x.right, k)
Semua operasi utama BST berjalan dalam O(h), dengan h = height tree:
| Operasi | Time |
|---|---|
| SEARCH | O(h) |
| INSERT | O(h) |
| DELETE | O(h) |
| MIN / MAX | O(h) |
| SUCCESSOR | O(h) |
Mengapa O(h), bukan O(n)? Karena setiap langkah turun satu level dan membuang seluruh subtree di sisi lain. Jumlah langkah maksimum = tinggi tree.
Masalah Utama BST: Height Tidak Dijamin
Inilah poin paling penting tentang BST:
| Kondisi | Height | Kompleksitas operasi |
|---|---|---|
| Input acak (random BST) | O(log n) expected | O(log n) |
| Input sudah terurut | O(n) | O(n) — degenerate! |
Contoh degenerate: masukkan 1, 2, 3, 4, 5 ke BST kosong secara berurutan:
1
\
2
\
3
\
4
\
5
Hasilnya menyerupai linked list — setiap node cuma punya anak kanan. Height = n − 1, sehingga SEARCH jadi O(n), sama buruknya dengan linear search pada array biasa.
Ini contoh nyata dari prinsip worst-case analysis Sesi 3: struktur data yang "biasanya cepat" bisa sangat lambat pada input tertentu.
Solusi: Balanced BST
Untuk menjamin O(log n) di worst-case (bukan cuma rata-rata), gunakan BST yang menjaga keseimbangan:
- Red-Black Tree (CLRS Ch. 13)
- AVL Tree
Keduanya menerapkan balance invariant yang diperiksa dan diperbaiki (lewat rotasi) setiap kali ada insert/delete, sehingga height selalu dijaga di O(log n).
Catatan terminologi kursus ini: ketika materi menyebut "Balanced BST", yang dimaksud spesifik adalah Red-Black Tree atau AVL Tree — bukan sekadar BST yang kebetulan seimbang.
5. Time vs. Space Trade-off
5.1 Prinsip Umum
Algoritma sering bisa menukar waktu dengan ruang, atau sebaliknya:
| Arah trade-off | Contoh | Efek |
|---|---|---|
| Kurangi waktu, bayar dengan ruang | Memoization pada Fibonacci | Dari O(2ⁿ) → O(n) time, dengan biaya O(n) space |
| Kurangi waktu, bayar dengan ruang | Hash table | O(n) space untuk O(1) average lookup, dibanding O(log n) pada BST |
| Kurangi ruang, bayar dengan waktu | In-place sorting | O(1) auxiliary space, tapi konstanta waktu lebih besar / implementasi lebih rumit |
| Kurangi ruang, bayar dengan waktu | Recompute daripada cache | Hemat memori, tapi kerja berulang |
Contoh Fibonacci (terhubung ke Sesi 1 dan 4): versi rekursif naif berjalan dalam Θ(φⁿ) ≈ eksponensial karena menghitung ulang subproblem yang sama berkali-kali. Dengan menyimpan hasil perhitungan (memoization) dalam array berukuran O(n), running time turun drastis menjadi Θ(n). Ini juga menjadi jembatan ke topik Dynamic Programming di sesi-sesi mendatang.
5.2 Panduan Pemilihan Struktur Data
| Kebutuhan | Struktur Pilihan | Alasan |
|---|---|---|
| Akses LIFO | Stack | Operasi O(1) di satu ujung |
| Akses FIFO | Queue | Operasi O(1) di dua ujung |
| Akses prioritas (max/min dulu) | Heap (Sesi 6) | O(log n) insert/extract, O(1) peek |
| Ordered dynamic set | Balanced BST (Red-Black / AVL) | O(log n) worst-case, dan inorder traversal menghasilkan urutan terurut |
| Lookup cepat by key | Hash table | O(1) average lookup |
| Data hierarkis | Tree | Struktur alami untuk relasi parent-child |
Catatan: tabel di slide kuliah memuat dua baris "LIFO access" berturut-turut; baris kedua seharusnya "FIFO access → Queue".
Kapan pilih Balanced BST, kapan Hash table?
| Balanced BST | Hash table | |
|---|---|---|
| Lookup | O(log n) worst-case | O(1) average, O(n) worst-case |
| Data terurut? | Ya — inorder traversal memberi urutan | Tidak |
| Range query (cari semua key antara a dan b) | Efisien | Tidak bisa langsung |
| MIN / MAX / SUCCESSOR | O(log n) | Harus scan seluruh tabel |
Jika hanya butuh lookup cepat → hash table. Jika butuh data tetap terurut atau operasi berbasis urutan → balanced BST.
Referensi
Cormen, T.H., Leiserson, C.E., Rivest, R.L., & Stein, C. (2022). Introduction to Algorithms, 4th Edition. MIT Press.
- Ch. 10.1 — Simple Array-Based Data Structures: Arrays, Matrices, Stacks, Queues (p. 248)
- Ch. 10.3 — Representing Rooted Trees (p. 265)
- Ch. 13 — Red-Black Trees (p. 308)
Skiena, S.S. (2020). The Algorithm Design Manual, 3rd Edition. Springer.
- Ch. 3.1 — Contiguous vs. Linked Data Structures (p. 69)
- Ch. 3.2 — Containers: Stacks and Queues (p. 75)
- Ch. 3.4 — Binary Search Trees (p. 81)