Setelah memahami konsep LIFO di Chapter sebelumnya, sekarang kita membangun stack dengan tiga pendekatan berbeda: berbasis array (menggunakan std::vector), berbasis linked list, dan std::stack<T, Container> dari STL yang parameterizable. Setiap pendekatan punya trade-off — std::vector punya cache locality tinggi, linked list tidak pernah realokasi, dan std::stack memberikan antarmuka yang sudah teruji. Pada Chapter ini kita akan membandingkan ketiganya secara konkret, menguji performa konseptual, dan melihat kapan memilih yang mana.
Pendekatan 1: Stack Berbasis Array (std::vector) #
Mekanisme: vector sebagai Array Dinamis #
std::vector menyimpan elemen dalam blok kontigu. push_back menambah di akhir, pop_back menghapus dari akhir. Karena akhir adalah “top” stack, operasi stack menjadi natural — O(1) amortisasi untuk push_back dan O(1) untuk pop_back.
Kode: ArrayStack #
template <typename T>
class ArrayStack {
public:
void push(const T& v) { data_.push_back(v); }
T pop() {
if (data_.empty()) throw std::out_of_range("stack kosong");
T v = data_.back();
data_.pop_back();
return v;
}
const T& top() const {
if (data_.empty()) throw std::out_of_range("stack kosong");
return data_.back();
}
bool empty() const { return data_.empty(); }
std::size_t size() const { return data_.size(); }
private:
std::vector<T> data_;
};Karakteristik #
- Memori kontigu: cache friendly, prefetcher bekerja, akses cepat.
- Realokasi: bila kapasitas penuh,
std::vectormengalokasikan blok baru 2x lipat dan menyalin elemen lama.push_backmenjadi O(1) amortisasi tetapi ada satu operasi mahal sesekali. - Memori overhead: hanya
capacity - sizeslot tidak terpakai, biasanya < 2x ukuran sebenarnya.
Pendekatan 2: Stack Berbasis Linked List #
Mekanisme: Push ke Depan, Pop dari Depan #
Linked list stack menyimpan head_ sebagai top. push menambah node baru di depan (jadikan head baru); pop menghapus head lama dan mengganti dengan head->next. Operasi O(1) tanpa realokasi.
template <typename T>
struct Node {
T nilai;
Node* next;
Node(const T& v) : nilai(v), next(nullptr) {}
};
template <typename T>
class ListStack {
public:
ListStack() : head_(nullptr), size_(0) {}
~ListStack() {
while (head_ != nullptr) {
Node<T>* nx = head_->next;
delete head_;
head_ = nx;
}
}
void push(const T& v) {
Node<T>* n = new Node<T>(v);
n->next = head_;
head_ = n;
++size_;
}
T pop() {
if (head_ == nullptr) throw std::out_of_range("stack kosong");
T v = head_->nilai;
Node<T>* old = head_;
head_ = head_->next;
delete old;
--size_;
return v;
}
const T& top() const {
if (head_ == nullptr) throw std::out_of_range("stack kosong");
return head_->nilai;
}
bool empty() const { return head_ == nullptr; }
std::size_t size() const { return size_; }
private:
Node<T>* head_;
std::size_t size_;
};Karakteristik #
- Tidak ada realokasi: setiap
pushmengalokasikan tepat satu node. Operasi benar-benar O(1) worst case, bukan amortisasi. - Cache locality rendah: node tersebar di heap, jump pointer ke alamat acak.
- Memori overhead besar: 8 byte pointer ekstra per elemen (sistem 64-bit) — overhead 100% untuk
int. - Destructor wajib traverse: setiap node harus di-delete; RAII penting di sini.
Pendekatan 3: std::stack<T, Container> #
Mekanisme: Adapter Pattern #
std::stack bukan struktur data baru — ia adalah adapter yang membungkus container lain. Default container adalah std::deque, tetapi kita bisa parameterkan ke std::vector atau std::list:
std::stack<int> s1; // default: deque
std::stack<int, std::vector<int>> s2; // vector
std::stack<int, std::list<int>> s3; // list
Container harus mendukung back(), push_back(), pop_back() — syarat yang dipenuhi std::vector, std::deque, dan std::list.
Keuntungan #
- Antarmuka terstandar:
push,pop,top,empty,sizedi semua container seragam. - Sudah teruji: implementasi STL dioptimasi dan diuji oleh jutaan pengguna.
- Switchable: ganti container hanya dengan satu template parameter — misal
deque(default) →vectoruntuk cache locality yang lebih baik.
Kode Lengkap Tiga Pendekatan #
#include <iostream>
#include <vector>
#include <list>
#include <stack>
#include <stdexcept>
// 1. Stack berbasis array (vector)
template <typename T>
class ArrayStack {
public:
void push(const T& v) { data_.push_back(v); }
T pop() {
if (data_.empty()) throw std::out_of_range("stack kosong");
T v = data_.back();
data_.pop_back();
return v;
}
const T& top() const {
if (data_.empty()) throw std::out_of_range("stack kosong");
return data_.back();
}
bool empty() const { return data_.empty(); }
std::size_t size() const { return data_.size(); }
private:
std::vector<T> data_;
};
// 2. Stack berbasis linked list
template <typename T>
struct Node {
T nilai;
Node* next;
Node(const T& v) : nilai(v), next(nullptr) {}
};
template <typename T>
class ListStack {
public:
ListStack() : head_(nullptr), size_(0) {}
~ListStack() {
while (head_ != nullptr) {
Node<T>* nx = head_->next;
delete head_;
head_ = nx;
}
}
void push(const T& v) {
Node<T>* n = new Node<T>(v);
n->next = head_;
head_ = n;
++size_;
}
T pop() {
if (head_ == nullptr) throw std::out_of_range("stack kosong");
T v = head_->nilai;
Node<T>* old = head_;
head_ = head_->next;
delete old;
--size_;
return v;
}
const T& top() const {
if (head_ == nullptr) throw std::out_of_range("stack kosong");
return head_->nilai;
}
bool empty() const { return head_ == nullptr; }
std::size_t size() const { return size_; }
private:
Node<T>* head_;
std::size_t size_;
};
int main() {
std::cout << "=== ArrayStack ===\n";
ArrayStack<int> as;
as.push(10);
as.push(20);
as.push(30);
std::cout << "top = " << as.top() << '\n';
std::cout << "pop = " << as.pop() << '\n';
std::cout << "pop = " << as.pop() << '\n';
std::cout << "size = " << as.size() << '\n';
std::cout << "\n=== ListStack ===\n";
ListStack<std::string> ls;
ls.push("alpha");
ls.push("beta");
ls.push("gamma");
std::cout << "top = " << ls.top() << '\n';
std::cout << "pop = " << ls.pop() << '\n';
std::cout << "pop = " << ls.pop() << '\n';
std::cout << "size = " << ls.size() << '\n';
std::cout << "\n=== std::stack (default deque) ===\n";
std::stack<int> s1;
s1.push(1); s1.push(2); s1.push(3);
std::cout << "top = " << s1.top() << '\n';
s1.pop();
std::cout << "top = " << s1.top() << '\n';
std::cout << "\n=== std::stack berbasis vector ===\n";
std::stack<int, std::vector<int>> s2;
s2.push(100); s2.push(200); s2.push(300);
std::cout << "top = " << s2.top() << '\n';
s2.pop();
std::cout << "top = " << s2.top() << '\n';
std::cout << "\n=== std::stack berbasis list ===\n";
std::stack<int, std::list<int>> s3;
s3.push(7); s3.push(8); s3.push(9);
std::cout << "top = " << s3.top() << '\n';
s3.pop();
std::cout << "top = " << s3.top() << '\n';
return 0;
}Output yang Dihasilkan:
=== ArrayStack ===
top = 30
pop = 30
pop = 20
size = 1
=== ListStack ===
top = gamma
pop = gamma
pop = beta
size = 1
=== std::stack (default deque) ===
top = 3
top = 2
=== std::stack berbasis vector ===
top = 300
top = 200
=== std::stack berbasis list ===
top = 9
top = 8Tabel Perbandingan Implementasi #
| Aspek | ArrayStack (vector) | ListStack (linked list) | std::stack (deque default) |
|---|---|---|---|
push worst case | O(1) amortisasi (realokasi sesekali) | O(1) selalu | O(1) selalu |
pop | O(1) | O(1) | O(1) |
| Memori per elemen (int) | 4 byte | 12 byte (4 + 8 pointer) | ~4-8 byte |
| Cache locality | Tinggi (kontigu) | Rendah (tersebar) | Sedang (deque chunked) |
| Iterator tersedia | Ya (vector asli) | Tidak | Tidak |
| Use case | Stack umum | Sangat banyak elemen, push critical | Produksi default |
Kapan Memilih Yang Mana #
Pilih std::stack<int> (deque default) untuk:
- Kebutuhan produksi standar — deque memadai untuk hampir semua kasus.
- Anda tidak ingin memikirkan trade-off rendah-level.
Pilih std::stack<T, std::vector<T>> untuk:
- Tipe elemen besar (mis.
std::string32 byte+) di mana cache locality signifikan. - Anda membutuhkan peek acak ke elemen ke-i (bukan top) lewat container asli.
Pilih linked list stack custom untuk:
- Kasus di mana realokasi
std::vectortidak boleh terjadi sama sekali (sistem real-time). - Pembelajaran struktur data — memahami pointer dan ownership.
Ringkasan #
- Tiga pendekatan stack:
std::vector-based (cache friendly, realokasi sesekali), linked list-based (selalu O(1), overhead besar),std::stack<T, Container>(adapter STL). std::stackmembungkus container yang mendukungback/push_back/pop_back— defaultstd::deque, bisa digantivectorataulist.std::stacktidak menyediakan iterator — sengaja, karena stack adalah struktur akses terbatas.- Untuk produksi,
std::stack<T>(deque default) hampir selalu cukup. Custom stack hanya untuk pembelajaran atau kebutuhan real-time. - Chapter berikutnya akan membahas queue — kebalikan LIFO, yaitu FIFO (First In First Out).