Algoritma dan Struktur Data dengan JavaScript
Algoritma adalah langkah sistematis untuk menyelesaikan masalah. Struktur data adalah cara menyimpan dan mengorganisasi data agar operasi tertentu dapat dilakukan dengan efisien. Keduanya menjadi fondasi penting dalam pemrograman, baik untuk membangun aplikasi sehari-hari maupun menghadapi technical interview.
Tutorial ini memakai JavaScript karena sintaksnya relatif mudah diikuti. Namun, konsep yang dibahas berlaku di banyak bahasa pemrograman.
Tujuan Belajar
Setelah membaca tutorial ini, kamu diharapkan mampu:
- Menjelaskan makna algoritma dan struktur data.
- Membaca kompleksitas waktu dengan notasi Big O sederhana.
- Memilih struktur data dasar seperti array, object map, stack, dan queue.
- Menulis algoritma searching dan sorting sederhana.
- Memahami recursion dan kapan perlu berhati-hati.
- Melatih cara memecah masalah sebelum menulis kode.
Cara Berpikir Sebelum Coding
Sebelum menulis kode, biasakan menjawab pertanyaan berikut:
- Apa input-nya?
- Apa output yang diharapkan?
- Apakah ada batasan ukuran data?
- Apakah data boleh diubah langsung atau harus dipertahankan?
- Kasus tepi apa yang mungkin muncul?
Contoh masalah: "Cari angka terbesar dalam array".
function findMax(numbers) {
if (numbers.length === 0) return null;
let max = numbers[0];
for (const number of numbers) {
if (number > max) {
max = number;
}
}
return max;
}
console.log(findMax([4, 2, 9, 1])); // 9
console.log(findMax([])); // nullKasus array kosong dipikirkan sejak awal, bukan setelah bug muncul.
Big O Singkat
Big O membantu kita membahas bagaimana waktu atau memori bertambah saat ukuran input bertambah.
| Notasi | Contoh | Gambaran |
|---|---|---|
| O(1) | Mengambil item pertama array | Konstan, tidak bergantung panjang input. |
| O(n) | Loop seluruh array | Bertambah seiring jumlah data. |
| O(n log n) | Sorting efisien | Umum pada algoritma sorting praktis. |
| O(n^2) | Nested loop seluruh pasangan | Cepat berat saat data besar. |
Big O bukan alat untuk menebak waktu pasti dalam milidetik. Big O dipakai untuk membaca pola pertumbuhan.
Array
Array cocok untuk daftar berurutan. Mengakses item berdasarkan index biasanya cepat, tetapi mencari data tertentu tetap perlu menelusuri item jika belum ada index bantu.
const lessons = ["HTML", "CSS", "JavaScript"];
console.log(lessons[0]); // HTML
lessons.push("React");
for (const lesson of lessons) {
console.log(lesson);
}Operasi umum:
| Operasi | Kompleksitas umum | Catatan |
|---|---|---|
| Akses by index | O(1) | Jika index diketahui. |
| Cari by value | O(n) | Perlu scan item. |
| Tambah di akhir | O(1) rata-rata | push biasanya efisien. |
| Hapus di tengah | O(n) | Item setelahnya perlu bergeser. |
Object Map
Object atau Map cocok ketika kita perlu mencari data berdasarkan key.
const usersById = {
u1: { id: "u1", name: "Ayu" },
u2: { id: "u2", name: "Bima" },
};
console.log(usersById.u2.name);Dengan Map:
const scores = new Map();
scores.set("ayu", 90);
scores.set("bima", 82);
console.log(scores.get("ayu"));
console.log(scores.has("citra"));Map berguna ketika key bukan hanya string biasa atau ketika kita ingin API yang eksplisit seperti set, get, has, dan delete.
Stack
Stack memakai prinsip LIFO: last in, first out. Data terakhir yang masuk akan keluar lebih dulu. Contoh penggunaan: undo, call stack, dan parsing tanda kurung.
class Stack {
constructor() {
this.items = [];
}
push(item) {
this.items.push(item);
}
pop() {
return this.items.pop();
}
peek() {
return this.items[this.items.length - 1];
}
isEmpty() {
return this.items.length === 0;
}
}Contoh validasi tanda kurung:
function isBalanced(input) {
const stack = [];
const pairs = { ")": "(", "]": "[", "}": "{" };
for (const char of input) {
if (char === "(" || char === "[" || char === "{") {
stack.push(char);
}
if (char === ")" || char === "]" || char === "}") {
const last = stack.pop();
if (last !== pairs[char]) return false;
}
}
return stack.length === 0;
}
console.log(isBalanced("{[()]}")); // true
console.log(isBalanced("{[(])}")); // falseQueue
Queue memakai prinsip FIFO: first in, first out. Data pertama yang masuk akan keluar lebih dulu. Contoh penggunaan: antrean pekerjaan, BFS, dan event processing.
class Queue {
constructor() {
this.items = [];
}
enqueue(item) {
this.items.push(item);
}
dequeue() {
return this.items.shift();
}
isEmpty() {
return this.items.length === 0;
}
}Catatan: shift() pada array JavaScript dapat menjadi O(n) karena item perlu bergeser. Untuk antrean besar, gunakan implementasi dengan pointer index.
class FastQueue {
constructor() {
this.items = [];
this.head = 0;
}
enqueue(item) {
this.items.push(item);
}
dequeue() {
if (this.head >= this.items.length) return undefined;
const item = this.items[this.head];
this.head += 1;
return item;
}
}Linear Search dan Binary Search
Linear search menelusuri data satu per satu.
function linearSearch(items, target) {
for (let index = 0; index < items.length; index += 1) {
if (items[index] === target) return index;
}
return -1;
}Binary search lebih cepat, tetapi syaratnya data harus sudah terurut.
function binarySearch(numbers, target) {
let left = 0;
let right = numbers.length - 1;
while (left <= right) {
const middle = Math.floor((left + right) / 2);
if (numbers[middle] === target) return middle;
if (numbers[middle] < target) {
left = middle + 1;
} else {
right = middle - 1;
}
}
return -1;
}
console.log(binarySearch([1, 3, 5, 8, 13], 8)); // 3Binary search biasanya O(log n), jauh lebih baik dari O(n) untuk data besar yang sudah terurut.
Sorting Sederhana
JavaScript memiliki sort, tetapi memahami sorting dasar tetap berguna. Contoh bubble sort:
function bubbleSort(numbers) {
const result = [...numbers];
for (let i = 0; i < result.length; i += 1) {
for (let j = 0; j < result.length - i - 1; j += 1) {
if (result[j] > result[j + 1]) {
const temp = result[j];
result[j] = result[j + 1];
result[j + 1] = temp;
}
}
}
return result;
}
console.log(bubbleSort([5, 2, 9, 1]));Bubble sort mudah dipahami tetapi tidak efisien untuk data besar karena O(n^2). Untuk aplikasi nyata, gunakan sorting bawaan atau algoritma yang lebih efisien sesuai kebutuhan.
Recursion
Recursion terjadi ketika function memanggil dirinya sendiri. Setiap recursion harus memiliki base case agar berhenti.
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120Recursion cocok untuk struktur yang secara alami bercabang, seperti tree. Namun, recursion yang terlalu dalam dapat menyebabkan call stack overflow. Untuk data sangat besar, pertimbangkan loop atau struktur iteratif.
Problem Solving: Two Sum
Masalah: diberikan array angka dan target, cari dua angka yang jika dijumlahkan menghasilkan target.
Solusi O(n^2):
function twoSumSlow(numbers, target) {
for (let i = 0; i < numbers.length; i += 1) {
for (let j = i + 1; j < numbers.length; j += 1) {
if (numbers[i] + numbers[j] === target) {
return [i, j];
}
}
}
return null;
}Solusi O(n) dengan Map:
function twoSum(numbers, target) {
const seen = new Map();
for (let index = 0; index < numbers.length; index += 1) {
const number = numbers[index];
const complement = target - number;
if (seen.has(complement)) {
return [seen.get(complement), index];
}
seen.set(number, index);
}
return null;
}
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]Struktur data yang tepat dapat mengubah solusi lambat menjadi jauh lebih efisien.
Kesalahan Umum
| Kesalahan | Dampak | Perbaikan |
|---|---|---|
| Langsung coding tanpa memahami input-output | Solusi mudah salah arah | Tulis contoh kasus dulu. |
| Mengabaikan kasus kosong | Error pada data edge case | Uji array kosong, null, dan nilai tidak ditemukan. |
| Memakai nested loop untuk semua hal | Lambat pada data besar | Cari struktur data bantu seperti Map atau Set. |
| Menghafal algoritma tanpa memahami trade-off | Sulit menyesuaikan soal baru | Pahami alasan di balik langkah. |
Coba Sendiri
Kerjakan latihan berikut:
- Buat function
countWords(text)yang menghitung frekuensi kata menggunakanMap. - Buat function
isPalindrome(text)yang mengabaikan spasi dan kapitalisasi. - Buat queue dengan pointer index, bukan
shift. - Ubah
twoSumagar mengembalikan pasangan nilai, bukan index. - Bandingkan jumlah operasi pada
twoSumSlowdantwoSumuntuk array 1000 item.
Contoh awal:
function countWords(text) {
const counts = new Map();
const words = text.toLowerCase().split(/\s+/);
for (const word of words) {
if (!word) continue;
counts.set(word, (counts.get(word) ?? 0) + 1);
}
return counts;
}
console.log([...countWords("aku belajar aku bisa")]);Cek Pemahaman
Kuis
Apa makna umum O(n) dalam analisis algoritma?
Kuis
Mengapa Map membantu solusi Two Sum menjadi lebih efisien?
Ringkasan
Algoritma dan struktur data membantu kita menulis solusi yang bukan hanya benar, tetapi juga masuk akal untuk ukuran data tertentu. Mulailah dari input-output, pahami kompleksitas dasar, lalu pilih struktur data sesuai operasi yang paling sering dilakukan. JavaScript sudah menyediakan array, object, Map, dan Set yang cukup untuk banyak kebutuhan awal, asalkan kita memahami trade-off-nya.