Queue adalah struktur data FIFO (First In, First Out): elemen pertama yang dimasukkan adalah elemen pertama yang dikeluarkan—seperti antrian di kasir. Queue dipakai dalam banyak algoritma: BFS (Breadth-First Search), penjadwalan task, buffering data streaming, dan message passing antar thread. Pada chapter ini kita akan mengimplementasikan queue dengan dua pendekatan: array sirkular (kapasitas tetap, wrap-around) dan linked list (dinamis). Setiap implementasi punya tradeoff yang akan kita bahas.
Operasi Dasar Queue #
Empat operasi standar queue:
- Enqueue: tambahkan elemen di belakang queue.
- Dequeue: ambil elemen dari depan queue (dan hapus).
- Peek/Front: baca elemen depan tanpa menghapus.
- IsEmpty / IsFull: cek kondisi queue.
Implementasi naive queue pakai array geser (geser seluruh elemen ke depan setiap dequeue) berbiaya O(n) per dequeue. Implementasi cerdas pakai array sirkular: dua indeks depan dan belakang yang berputar di dalam array, sehingga enqueue dan dequeue keduanya O(1).
Queue Berbasis Array Sirkular #
Pada queue sirkular, depan dan belakang berputar di dalam array lewat operasi modulo. Bila belakang mencapai akhir array, ia kembali ke indeks 0. Trik ini menghindari penggeseran elemen.
#include <stdio.h>
#include <stdlib.h>
#define KAPASITAS 8
typedef struct {
int data[KAPASITAS];
int depan, belakang;
size_t ukuran;
} queue_array;
void qa_init(queue_array *q) {
q->depan = 0;
q->belakang = 0;
q->ukuran = 0;
}
int qa_kosong(queue_array *q) { return q->ukuran == 0; }
int qa_penuh(queue_array *q) { return q->ukuran == KAPASITAS; }
int qa_enqueue(queue_array *q, int val) {
if (qa_penuh(q)) return -1;
q->data[q->belakang] = val;
q->belakang = (q->belakang + 1) % KAPASITAS;
q->ukuran++;
return 0;
}
int qa_dequeue(queue_array *q, int *out) {
if (qa_kosong(q)) return -1;
*out = q->data[q->depan];
q->depan = (q->depan + 1) % KAPASITAS;
q->ukuran--;
return 0;
}
int qa_peek(queue_array *q, int *out) {
if (qa_kosong(q)) return -1;
*out = q->data[q->depan];
return 0;
}
int main(void) {
queue_array q;
qa_init(&q);
for (int i = 10; i <= 60; i += 10) {
if (qa_enqueue(&q, i) == 0)
printf("Enqueue %d (ukuran=%zu)\n", i, q.ukuran);
else
printf("Enqueue %d gagal: queue penuh\n", i);
}
int depan;
qa_peek(&q, &depan);
printf("Peek depan: %d\n", depan);
/* Dequeue 3 elemen */
for (int i = 0; i < 3; i++) {
int val;
qa_dequeue(&q, &val);
printf("Dequeue %d\n", val);
}
/* Enqueue lagi - tes wrap-around */
for (int i = 70; i <= 100; i += 10) {
if (qa_enqueue(&q, i) == 0)
printf("Enqueue %d (ukuran=%zu)\n", i, q.ukuran);
else
printf("Enqueue %d gagal: queue penuh\n", i);
}
/* Dequeue semua */
while (!qa_kosong(&q)) {
int val;
qa_dequeue(&q, &val);
printf("Dequeue %d\n", val);
}
int val;
if (qa_dequeue(&q, &val) != 0)
printf("Dequeue gagal: queue kosong\n");
return 0;
}Output yang Dihasilkan:
Enqueue 10 (ukuran=1)
Enqueue 20 (ukuran=2)
Enqueue 30 (ukuran=3)
Enqueue 40 (ukuran=4)
Enqueue 50 (ukuran=5)
Enqueue 60 (ukuran=6)
Peek depan: 10
Dequeue 10
Dequeue 20
Dequeue 30
Enqueue 70 (ukuran=4)
Enqueue 80 (ukuran=5)
Enqueue 90 (ukuran=6)
Enqueue 100 (ukuran=7)
Dequeue 40
Dequeue 50
Dequeue 60
Dequeue 70
Dequeue 80
Dequeue 90
Dequeue 100
Dequeue gagal: queue kosongQueue sirkular menangani wrap-around: setelah depan dan belakang dequeue/enqueue beberapa kali, belakang bisa kembali ke indeks 0. Operasi modulo % KAPASITAS memastikan indeks selalu valid.
Mari kita telaah baris-baris kode di atas:
- Baris 7-11 (
queue_arraystruct): tiga field—arraydata, indeksdepandanbelakang, sertaukuranuntuk melacak jumlah elemen. Tanpaukuran, kita tidak bisa membedakan queue kosong dari queue penuh (keduanyadepan == belakang). - Baris 17 (
qa_enqueue): tulis data kedata[belakang], lalu majukanbelakangdengan modulo:(belakang + 1) % KAPASITAS. Bilabelakangsudah diKAPASITAS-1, ia kembali ke 0. - Baris 24 (
qa_dequeue): bacadata[depan], lalu majukandepandengan modulo yang sama. Logika simetris dengan enqueue. - Baris 14 (
qa_penuh): cekukuran == KAPASITAS. Tanpa fieldukuran, kita harus membandingkan(belakang + 1) % KAPASITAS == depan—tetapi ini hanya berlaku bila kita mengorbankan satu slot (queue terlihat penuh saat kapasitasKAPASITAS-1). - Baris 50-53 (3 dequeue lalu enqueue lagi): ini menguji wrap-around. Setelah 3 dequeue,
depanpindah ke indeks 3. Setelah 4 enqueue lagi,belakangmelewati akhir array dan kembali ke 0—data tersebar di kedua ujung array.
Queue Berbasis Linked List #
Queue linked list menggunakan dua pointer: depan dan belakang. Enqueue menambah node di akhir, dequeue menghapus node di depan. Keduanya O(1).
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
int data;
struct node *next;
} node;
typedef struct {
node *depan;
node *belakang;
size_t ukuran;
} queue_list;
void ql_init(queue_list *q) {
q->depan = NULL;
q->belakang = NULL;
q->ukuran = 0;
}
int ql_kosong(queue_list *q) { return q->depan == NULL; }
int ql_enqueue(queue_list *q, int val) {
node *n = malloc(sizeof(node));
if (n == NULL) return -1;
n->data = val;
n->next = NULL;
if (q->belakang == NULL) {
q->depan = n;
q->belakang = n;
} else {
q->belakang->next = n;
q->belakang = n;
}
q->ukuran++;
return 0;
}
int ql_dequeue(queue_list *q, int *out) {
if (ql_kosong(q)) return -1;
node *tmp = q->depan;
*out = tmp->data;
q->depan = tmp->next;
if (q->depan == NULL) q->belakang = NULL;
free(tmp);
q->ukuran--;
return 0;
}
void ql_hapus_semua(queue_list *q) {
int val;
while (ql_dequeue(q, &val) == 0) ;
}
int main(void) {
queue_list q;
ql_init(&q);
printf("Simulasi antrian loket:\n");
const char *orang[] = {"Budi", "Siti", "Andi", "Wati", "Rina"};
for (int i = 0; i < 5; i++) {
ql_enqueue(&q, i + 1);
printf(" Datang: %s (ambil nomor %d)\n", orang[i], i + 1);
}
int nomor;
ql_dequeue(&q, &nomor);
printf(" Dilayani: nomor %d (%s)\n", nomor, orang[nomor - 1]);
ql_dequeue(&q, &nomor);
printf(" Dilayani: nomor %d (%s)\n", nomor, orang[nomor - 1]);
ql_enqueue(&q, 6);
printf(" Datang: Dewi (ambil nomor 6)\n");
printf(" Sisa antrian (ukuran=%zu):\n", q.ukuran);
while (!ql_kosong(&q)) {
ql_dequeue(&q, &nomor);
printf(" nomor %d dilayani\n", nomor);
}
ql_hapus_semua(&q);
return 0;
}Output yang Dihasilkan:
Simulasi antrian loket:
Datang: Budi (ambil nomor 1)
Datang: Siti (ambil nomor 2)
Datang: Andi (ambil nomor 3)
Datang: Wati (ambil nomor 4)
Datang: Rina (ambil nomor 5)
Dilayani: nomor 1 (Budi)
Dilayani: nomor 2 (Siti)
Datang: Dewi (ambil nomor 6)
Sisa antrian (ukuran=4):
nomor 3 dilayani
nomor 4 dilayani
nomor 5 dilayani
nomor 6 dilayaniSimulasi antrian loket ini memperlihatkan FIFO: orang dilayani sesuai urutan kedatangan. Budi datang lebih dulu, jadi dilayani pertama. Dewi yang datang belakangan menempati posisi terakhir.
Perbandingan Implementasi #
| Aspek | Array Sirkular | Linked List |
|---|---|---|
| Kapasitas | Tetap (#define) | Dinamis |
| Enqueue | O(1) atau gagal | O(1) + malloc |
| Dequeue | O(1) | O(1) + free |
| Memori overhead | 0 (slot kosong dianggap overhead) | 1 pointer per elemen |
| Wrap-around | Ya (modulo) | Tidak perlu (selalu tambah node) |
| Kegagalan enqueue | Bila penuh | Bila malloc gagal |