Pertemuan 04
Struktur Data dan Algoritma
Universitas Pertahanan RI
Setelah pertemuan ini, mahasiswa mampu:
Double Linked List (DLL) adalah struktur data linear di mana setiap node memiliki dua pointer: prev (ke node sebelumnya) dan next (ke node berikutnya).
Keunggulan utama: Traversal dua arah (bidirectional)
🔗
p04-01-double-linked-list-structure.png
Double Linked List dengan pointer HEAD, TAIL, dan koneksi bidirectional
struct Node {
int data;
Node* prev; // Pointer ke node sebelumnya
Node* next; // Pointer ke node berikutnya
// Constructor
Node(int value) : data(value), prev(nullptr), next(nullptr) {}
};
3 Komponen: data (nilai), prev (pointer mundur), next (pointer maju)
class DoublyLinkedList {
private:
Node* head;
Node* tail;
int size;
public:
DoublyLinkedList() : head(nullptr), tail(nullptr), size(0) {}
void insertFront(int value);
void insertBack(int value);
void deleteFront();
void deleteBack();
void displayForward();
void displayBackward();
int getSize() const { return size; }
bool isEmpty() const { return size == 0; }
};
void DoublyLinkedList::insertFront(int value) {
Node* newNode = new Node(value);
if (head == nullptr) {
head = tail = newNode; // List kosong
}
else {
newNode->next = head; // Node baru → head lama
head->prev = newNode; // Head lama ← node baru
head = newNode; // Update head
}
size++;
}
O(1) Kompleksitas waktu konstan
void DoublyLinkedList::insertBack(int value) {
Node* newNode = new Node(value);
if (tail == nullptr) {
head = tail = newNode; // List kosong
}
else {
newNode->prev = tail; // Node baru ← tail lama
tail->next = newNode; // Tail lama → node baru
tail = newNode; // Update tail
}
size++;
}
O(1) — Keunggulan DLL dengan tail pointer!
➕
p04-02-dll-insert-middle.png
Proses insert node di tengah dengan 4 langkah pointer manipulation
void DoublyLinkedList::insertAt(int value, int position) {
if (position < 0 || position > size) return;
if (position == 0) { insertFront(value); return; }
if (position == size) { insertBack(value); return; }
Node* newNode = new Node(value);
Node* current;
// Traversal optimal: dari depan atau belakang
if (position <= size / 2) {
current = head;
for (int i = 0; i < position; i++) current = current->next;
} else {
current = tail;
for (int i = size - 1; i > position; i--) current = current->prev;
}
// Insert sebelum current
newNode->next = current;
newNode->prev = current->prev;
current->prev->next = newNode;
current->prev = newNode;
size++;
}
void deleteFront() {
if (head == nullptr) return;
Node* temp = head;
if (head == tail) {
head = tail = nullptr;
} else {
head = head->next;
head->prev = nullptr;
}
delete temp;
size--;
}
void deleteBack() {
if (tail == nullptr) return;
Node* temp = tail;
if (head == tail) {
head = tail = nullptr;
} else {
tail = tail->prev;
tail->next = nullptr;
}
delete temp;
size--;
}
O(1) Keduanya konstan — deleteBack() DLL lebih cepat dari SLL!
| Aspek | Single LL | Double LL |
|---|---|---|
| Memori per node | 2 field | 3 field |
| Traversal | Satu arah → | Dua arah ⇄ |
| Insert depan | O(1) | O(1) |
| Delete belakang | O(n) | O(1) |
| Delete node (punya ptr) | O(n) | O(1) |
Circular Linked List adalah linked list di mana node terakhir tidak menunjuk ke NULL, melainkan menunjuk kembali ke node pertama, membentuk lingkaran (cycle).
Dua variasi:
🔄
p04-03-circular-linked-list.png
Single Circular LL (atas) dan Double Circular LL (bawah)
class CircularLinkedList {
private:
Node* tail; // Menggunakan TAIL untuk efisiensi
int size;
public:
CircularLinkedList() : tail(nullptr), size(0) {}
// HEAD dapat diakses via tail->next
Node* getHead() const {
return (tail == nullptr) ? nullptr : tail->next;
}
void insertFront(int value);
void insertBack(int value);
void display();
};
💡 Tip: Simpan tail saja — akses head via tail->next untuk O(1) insert di kedua ujung!
void CircularLinkedList::insertFront(int value) {
Node* newNode = new Node(value);
if (tail == nullptr) {
tail = newNode;
newNode->next = newNode; // Menunjuk ke diri sendiri
} else {
newNode->next = tail->next; // Node baru → head lama
tail->next = newNode; // Tail → node baru (head baru)
}
size++;
}
void CircularLinkedList::insertBack(int value) {
insertFront(value); // Insert di depan
tail = tail->next; // Geser tail ke node baru
}
O(1) Keduanya konstan berkat penggunaan tail
void CircularLinkedList::display() {
if (tail == nullptr) { cout << "List kosong!"; return; }
Node* current = tail->next; // Mulai dari head
do {
cout << current->data;
current = current->next;
if (current != tail->next) cout << " -> ";
} while (current != tail->next); // Berhenti di head
cout << " -> (kembali ke " << tail->next->data << ")" << endl;
}
⚠️ Penting: Gunakan do-while, bukan while! Tidak ada NULL untuk stop condition.
Menggabungkan keuntungan DLL dan Circular LL:
p04-04-dcll-operations.png
void DoublyCircularLinkedList::insertFront(int value) {
Node* newNode = new Node(value);
if (head == nullptr) {
head = newNode;
newNode->next = newNode;
newNode->prev = newNode; // Circular: menunjuk diri sendiri
} else {
Node* tail = head->prev;
newNode->next = head;
newNode->prev = tail;
head->prev = newNode;
tail->next = newNode;
head = newNode; // Update head
}
size++;
}
void DoublyCircularLinkedList::deleteNode(Node* node) {
if (node == nullptr || head == nullptr) return;
// Kasus hanya ada satu node
if (node->next == node) {
head = nullptr;
delete node;
size--;
return;
}
// Hubungkan tetangga
node->prev->next = node->next;
node->next->prev = node->prev;
if (node == head) head = node->next;
delete node;
size--;
}
O(1) Jika sudah punya pointer ke node target!
Sentinel Node (Dummy Node) adalah node khusus yang tidak menyimpan data aktual, digunakan untuk menyederhanakan penanganan edge cases.
Manfaat:
🛡️
p04-05-sentinel-node.png
Sentinel HEAD dan TAIL mengapit data nodes
class DoublyLinkedListWithSentinel {
private:
Node* sentinelHead;
Node* sentinelTail;
public:
DoublyLinkedListWithSentinel() {
sentinelHead = new Node(0);
sentinelTail = new Node(0);
sentinelHead->next = sentinelTail;
sentinelTail->prev = sentinelHead;
}
void insertAfter(Node* node, int value) {
Node* newNode = new Node(value);
newNode->next = node->next;
newNode->prev = node;
node->next->prev = newNode;
node->next = newNode;
}
void insertFront(int value) { insertAfter(sentinelHead, value); }
void insertBack(int value) { insertAfter(sentinelTail->prev, value); }
};
Iterator adalah objek yang memungkinkan traversal melalui elemen container tanpa mengekspos struktur internal.
Operasi Iterator:
*it — Akses elemen saat ini++it — Pindah ke elemen berikutnya--it — Pindah ke elemen sebelumnya (DLL)it != end — Cek masih ada elemen
// Forward traversal
cout << "Forward: ";
for (auto it = dll.begin(); it != dll.end(); ++it) {
cout << *it << " ";
}
// Backward traversal
cout << "Backward: ";
for (auto it = dll.rbegin(); it != dll.rend(); --it) {
cout << *it << " ";
}
// Modifikasi dengan iterator
for (auto it = dll.begin(); it != dll.end(); ++it) {
*it *= 2; // Gandakan setiap nilai
}
Implementasi playlist dengan Double Circular Linked List:
struct Song {
string title;
string artist;
Song* prev;
Song* next;
};
class MusicPlaylist {
Song* current; // Lagu yang sedang diputar
void next() { current = current->next; }
void previous() { current = current->prev; }
void addSong(string title, string artist);
void removeCurrent();
};
✅ Loop otomatis | ✅ Prev/Next O(1) | ✅ Ins/Rmv O(1)
Mendeteksi cycle dalam linked list dengan O(n) waktu dan O(1) ruang:
bool hasCycle(Node* head) {
if (head == nullptr) return false;
Node* slow = head; // Tortoise: 1 langkah
Node* fast = head->next; // Hare: 2 langkah
while (fast != nullptr && fast->next != nullptr) {
if (slow == fast) return true; // Bertemu = ada cycle
slow = slow->next;
fast = fast->next->next;
}
return false; // Tidak ada cycle
}
🐢🐇
p04-07-floyd-cycle-detection.png
Slow (1 langkah) dan Fast (2 langkah) bertemu dalam cycle
| Struktur | Karakteristik | Operasi Utama |
|---|---|---|
| Double LL | prev + next pointer | Insert/Delete ujung: O(1) |
| Single Circular | Last → First (next) | Insert (tail ptr): O(1) |
| Double Circular | Circular + bidirectional | Semua ujung: O(1) |
| Sentinel Node | Dummy untuk edge cases | Simplify code |
Single LL
Memori terbatas, hanya perlu forward traversal
Double LL
Traversal 2 arah, delete cepat jika punya pointer
Circular LL
Data berputar: playlist, jadwal, buffer
Double Circular
Kombinasi semua kebutuhan di atas
Apa kompleksitas deleteBack() pada Single Linked List TANPA tail pointer?
Jawaban: O(n)
Harus traversal dari head untuk menemukan node sebelum tail. Dengan DLL atau tail+prev, menjadi O(1).
📚 Next: Pertemuan 05 — Stack
Silakan bertanya atau diskusi
Referensi: Cormen Ch.10.2 | Weiss Ch.3 | Hubbard Ch.7
Struktur Data dan Algoritma
Pertemuan 04: Double dan Circular Linked List
© 2026 - CC BY 4.0