Doubly linked list adalah linked list yang tiap node-nya punya dua pointer: next ke node sesudahnya, dan prev ke node sebelumnya. List-nya juga menyimpan dua ujung: head (node pertama) dan tail (node terakhir). Mirip rangkaian gerbong kereta: tiap gerbong tersambung ke gerbong depan dan belakangnya, jadi kamu bisa jalan ke dua arah.
🧪 Tebakan cepat: di linked list biasa, menghapus node terakhir harus jalan dari
head. Di doubly linked list, berapa node yang harus dilewati? Tebak dulu, jawabannya ada di bagian "Hapus di belakang".
Node dengan prev dan next
Gambarnya dulu:
head tail
| |
null <- [A] <-------> [B] <-------> [C] -> null
class DNode {
constructor(value) {
this.value = value;
this.prev = null;
this.next = null;
}
}
class DoublyLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
append(value) {
const node = new DNode(value);
if (this.tail === null) {
this.head = node;
this.tail = node;
} else {
node.prev = this.tail;
this.tail.next = node;
this.tail = node;
}
this.size++;
}
}
const list = new DoublyLinkedList();
list.append("A");
list.append("B");
list.append("C");
console.log(list.head.value); // A
console.log(list.tail.value); // C
console.log(list.head.next.value); // B
console.log(list.tail.prev.value); // B
console.log(list.size); // 3
DNodesama sepertiNodedi linked list biasa, ditambahprev.prevmilik node pertama dannextmilik node terakhir tetapnull.tailmenunjuk node terakhir, jadiappendgak perlu jalan dari depan.- Kalau list masih kosong (
tailmasihnull), node baru jadiheadsekaligustail.
list.append("C") ke list A <-> B, langkah demi langkah:
| Langkah | Kode | Yang berubah |
|---|---|---|
| 1 | const node = new DNode("C") |
C sendirian |
| 2 | node.prev = this.tail |
C menunjuk balik ke B |
| 3 | this.tail.next = node |
B menunjuk maju ke C |
| 4 | this.tail = node |
tail sekarang C |
Tiap sambungan baru berarti dua pointer: satu maju, satu mundur. Jumlah langkahnya selalu sama, sepanjang apa pun list-nya, jadi append di sini O(1).
prepend: cerminan append
Menambah di depan memakai langkah yang sama, dengan head di tempat tail dan arah pointer yang dibalik:
append (di belakang) |
prepend (di depan) |
|---|---|
node.prev = this.tail |
node.next = this.head |
this.tail.next = node |
this.head.prev = node |
this.tail = node |
this.head = node |
Aturan list kosong juga berlaku: node pertama yang masuk lewat prepend harus jadi head dan tail sekaligus. Dan this.size++ di keduanya.
Jalan maju dan jalan mundur
Maju: mulai dari head, ikuti next. Mundur: mulai dari tail, ikuti prev.
let current = list.tail;
while (current !== null) {
console.log(current.value);
current = current.prev;
}
// C
// B
// A
Kode ini lanjutan dari list di atas. Mau hasilnya dalam array? Siapkan [], push(current.value) tiap putaran, lalu kembalikan array-nya sesudah loop.
Hapus di belakang
Node sebelum yang terakhir ada di tail.prev, jadi removeLast cukup satu langkah: tail dipindah ke tail.prev, lalu next milik tail yang baru diisi null. Itu jawaban tebakan tadi: gak ada node yang dilewati, O(1). Menghapus node di tengah juga gampang: sambungkan node sebelum dan sesudahnya satu sama lain, karena keduanya bisa dicapai lewat prev dan next.
Singly atau doubly?
| Singly linked list | Doubly linked list | |
|---|---|---|
| Pointer per node | 1 (next) |
2 (prev dan next) |
| Arah jalan | cuma maju | maju dan mundur |
| Hapus node terakhir | jalan dari head, O(n) |
lewat tail.prev, O(1) |
Harganya memori: tiap node menyimpan satu pointer tambahan. Datanya sendiri gak disimpan dua kali. Doubly linked list dipakai kalau kamu butuh bolak-balik, misalnya tombol back dan forward di browser, lagu sebelumnya dan berikutnya di playlist, atau LRU cache.
⚠️ Jebakan yang sering ketemu
- Cuma menyambung satu arah.
this.tail.next = nodetanpanode.prev = this.tailbikin jalan maju benar tapi jalan mundur putus. Tiap sambungan butuh dua pointer. - List kosong. Kalau node pertama cuma dijadikan
head,tailtetapnull, dan jalan mundur gak menemukan apa-apa. Isi keduanya. - Membaca mundur dengan membalik array.
toArrayForward().reverse()memberi urutan yang benar, tapi gak membuktikanprev-nya tersambung. Jalan mundur sungguhan lewatprev.
TL;DR: Doubly linked list: tiap node punya prev dan next, dan list-nya menyimpan head dan tail. append: node.prev = this.tail, this.tail.next = node, this.tail = node. prepend adalah cerminannya lewat head. List kosong: node pertama jadi head sekaligus tail. Maju dari head lewat next, mundur dari tail lewat prev. Tambah dan hapus di kedua ujung O(1), harganya satu pointer tambahan di tiap node.