- Pengenalan Kompleksitas
- Big O Notation
- Jenis-jenis Kompleksitas Waktu
- Kompleksitas Ruang (Space Complexity)
- Analisis Kompleksitas Struktur Data
- Perbandingan Struktur Data
- Contoh Analisis Algoritma
- Best, Average, dan Worst Case
- Tips Optimasi
- Latihan Analisis
Kompleksitas algoritma adalah ukuran yang digunakan untuk menganalisis efisiensi algoritma dan struktur data. Analisis kompleksitas membantu kita memahami bagaimana performa algoritma berubah seiring bertambahnya ukuran input.
- Prediksi Performa - Memperkirakan waktu eksekusi dan penggunaan memori
- Perbandingan Algoritma - Memilih algoritma terbaik untuk masalah tertentu
- Skalabilitas - Memahami bagaimana algoritma berperilaku dengan data besar
- Optimasi - Mengidentifikasi bottleneck dan area perbaikan
| Jenis | Deskripsi | Pertanyaan |
|---|---|---|
| Time Complexity | Jumlah operasi yang dilakukan | "Berapa lama waktu yang dibutuhkan?" |
| Space Complexity | Jumlah memori yang digunakan | "Berapa banyak memori yang dibutuhkan?" |
Big O Notation adalah notasi matematika yang menggambarkan batas atas (upper bound) dari kompleksitas algoritma. Big O fokus pada pertumbuhan fungsi saat input mendekati tak hingga, mengabaikan konstanta dan terms yang lebih kecil.
f(n) = 3n² + 5n + 100
Big O: O(n²)
- Konstanta 3 diabaikan
- Term 5n diabaikan (lebih kecil dari n²)
- Konstanta 100 diabaikan
| Notasi | Nama | Deskripsi | Contoh |
|---|---|---|---|
| O(1) | Constant | Tidak bergantung pada ukuran input | Akses array dengan index |
| O(log n) | Logarithmic | Berkurang setengah setiap iterasi | Binary search |
| O(n) | Linear | Berbanding lurus dengan input | Linear search |
| O(n log n) | Linearithmic | n kali logaritma | Merge sort, Quick sort |
| O(n²) | Quadratic | Kuadrat dari input | Bubble sort, Selection sort |
| O(n³) | Cubic | Kubik dari input | Matrix multiplication naif |
| O(2ⁿ) | Exponential | Dua pangkat n | Fibonacci rekursif |
| O(n!) | Factorial | Faktorial dari n | Permutasi brute force |
O(1) < O(log n) < O(√n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ) < O(n!)
Untuk n = 1000:
O(1) = 1
O(log n) = ~10
O(n) = 1,000
O(n log n) = ~10,000
O(n²) = 1,000,000
O(n³) = 1,000,000,000
O(2ⁿ) = Tidak terhitung (terlalu besar)
^
| O(n!)
| O(2ⁿ)
| O(n³)
Time| O(n²)
| O(n log n)
| O(n)
| O(√n)
| O(log n)
| O(1)
+------------------------------------------------->
n (ukuran input)
Waktu eksekusi tetap sama tidak peduli ukuran input.
// Contoh O(1)
public int getFirst(int[] arr) {
return arr[0]; // Selalu satu operasi
}
public void push(int value) {
stack[++top] = value; // Operasi konstan
}
public int peek() {
return stack[top]; // Akses langsung
}Karakteristik:
- Tidak ada loop yang bergantung pada n
- Operasi langsung tanpa iterasi
Waktu eksekusi meningkat secara logaritmik. Biasanya terjadi ketika masalah dipecah menjadi setengah setiap iterasi.
// Binary Search - O(log n)
public int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1; // Abaikan setengah kiri
} else {
right = mid - 1; // Abaikan setengah kanan
}
}
return -1;
}Karakteristik:
- Masalah dipecah menjadi setengah setiap iterasi
- Contoh: Binary search, operasi pada balanced BST
Analisis:
n = 16
Iterasi 1: n = 16
Iterasi 2: n = 8
Iterasi 3: n = 4
Iterasi 4: n = 2
Iterasi 5: n = 1
Total iterasi = log₂(16) = 4
Waktu eksekusi berbanding lurus dengan ukuran input.
// Linear Search - O(n)
public int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) { // Loop n kali
if (arr[i] == target) {
return i;
}
}
return -1;
}
// Traverse Linked List - O(n)
public void display() {
Node current = head;
while (current != null) { // Loop n kali
System.out.print(current.data + " ");
current = current.next;
}
}
// Mencari nilai dalam stack - O(n)
public int search(int value) {
for (int i = top; i >= 0; i--) { // Loop n kali
if (stack[i] == value) {
return top - i + 1;
}
}
return -1;
}Karakteristik:
- Satu loop yang iterasi n kali
- Setiap elemen diproses sekali
Kombinasi linear dan logarithmic. Biasanya terjadi pada algoritma divide and conquer.
// Merge Sort - O(n log n)
public void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid); // T(n/2)
mergeSort(arr, mid + 1, right); // T(n/2)
merge(arr, left, mid, right); // O(n)
}
}
// Recurrence: T(n) = 2T(n/2) + O(n) = O(n log n)Karakteristik:
- Divide: membagi masalah (log n level)
- Conquer: menggabungkan hasil (n operasi per level)
- Total: n × log n
Waktu eksekusi meningkat secara kuadratik.
// Bubble Sort - O(n²)
public void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) { // Loop n kali
for (int j = 0; j < n - i - 1; j++) { // Loop n kali
if (arr[j] > arr[j + 1]) {
// Swap
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// Nested Loop - O(n²)
public void printPairs(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
System.out.println(arr[i] + ", " + arr[j]);
}
}
}Karakteristik:
- Nested loop (loop di dalam loop)
- Setiap elemen dibandingkan dengan setiap elemen lain
Waktu eksekusi berlipat ganda setiap penambahan input.
// Fibonacci Rekursif - O(2ⁿ)
public int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Setiap pemanggilan menghasilkan dua pemanggilan baru
// Tree rekursi:
// fib(5)
// / \
// fib(4) fib(3)
// / \ / \
// fib(3) fib(2) fib(2) fib(1)
// ...Karakteristik:
- Setiap langkah menghasilkan dua atau lebih submasalah
- Sangat tidak efisien untuk input besar
Space complexity mengukur jumlah memori yang digunakan oleh algoritma, termasuk:
- Input space - Memori untuk menyimpan input
- Auxiliary space - Memori tambahan yang digunakan algoritma
- Total space = Input space + Auxiliary space
// O(1) Space - Konstanta
public int sum(int[] arr) {
int total = 0; // Satu variabel
for (int i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
// Hanya menggunakan variabel total dan i, tidak bergantung pada n
// O(n) Space - Linear
public int[] copy(int[] arr) {
int[] newArr = new int[arr.length]; // Array baru ukuran n
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];
}
return newArr;
}
// O(n) Space - Rekursi
public int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // Call stack depth = n
}
// Setiap rekursi menambah frame di call stack| Struktur Data | Space Complexity |
|---|---|
| Array | O(n) |
| Linked List | O(n) |
| Stack (array) | O(n) |
| Stack (linked list) | O(n) |
| Queue (array) | O(n) |
| Queue (linked list) | O(n) |
Berikut adalah class diagram ringkasan untuk semua struktur data yang dianalisis kompleksitasnya:
classDiagram
class Array {
-int[] data
-int size
-int capacity
+get(index: int) int
+set(index: int, value: int) void
+insert(index: int, value: int) void
+delete(index: int) int
+search(value: int) int
+size() int
}
note for Array "Access: O(1)\nSearch: O(n)\nInsert: O(n)\nDelete: O(n)"
class SingleLinkedList {
-Node head
-int size
+insertAtBeginning(data: int) void
+insertAtEnd(data: int) void
+deleteAtBeginning() int
+deleteAtEnd() int
+search(value: int) int
+get(index: int) int
}
note for SingleLinkedList "Access: O(n)\nSearch: O(n)\nInsert Head: O(1)\nDelete Head: O(1)"
class DoubleLinkedList {
-DoubleNode head
-DoubleNode tail
-int size
+insertAtBeginning(data: int) void
+insertAtEnd(data: int) void
+deleteAtBeginning() int
+deleteAtEnd() int
}
note for DoubleLinkedList "Insert/Delete Head: O(1)\nInsert/Delete Tail: O(1)"
class Stack {
-int[] array
-int top
+push(value: int) void
+pop() int
+peek() int
+isEmpty() boolean
}
note for Stack "Push: O(1)\nPop: O(1)\nPeek: O(1)"
class Queue {
-int[] array
-int front
-int rear
+enqueue(value: int) void
+dequeue() int
+peek() int
+isEmpty() boolean
}
note for Queue "Enqueue: O(1)\nDequeue: O(1)\nPeek: O(1)"
class HashTable {
-Entry[] table
-int size
+put(key: K, value: V) void
+get(key: K) V
+remove(key: K) void
+containsKey(key: K) boolean
}
note for HashTable "Insert: O(1) avg\nSearch: O(1) avg\nDelete: O(1) avg"
| Operasi | Time Complexity | Space Complexity |
|---|---|---|
| Access by index | O(1) | O(1) |
| Search (unsorted) | O(n) | O(1) |
| Search (sorted) | O(log n) | O(1) |
| Insert at end | O(1) amortized | O(1) |
| Insert at beginning | O(n) | O(1) |
| Insert at middle | O(n) | O(1) |
| Delete at end | O(1) | O(1) |
| Delete at beginning | O(n) | O(1) |
| Delete at middle | O(n) | O(1) |
Penjelasan:
// Access by index - O(1)
int value = arr[5]; // Langsung mengakses alamat memori
// Insert at beginning - O(n)
public void insertAtBeginning(int[] arr, int value) {
// Shift semua elemen ke kanan
for (int i = arr.length - 1; i > 0; i--) { // O(n)
arr[i] = arr[i - 1];
}
arr[0] = value;
}
// Delete at middle - O(n)
public void deleteAt(int[] arr, int index) {
// Shift semua elemen setelah index ke kiri
for (int i = index; i < arr.length - 1; i++) { // O(n)
arr[i] = arr[i + 1];
}
}| Operasi | Time Complexity | Penjelasan |
|---|---|---|
| Access by index | O(n) | Harus traverse dari head |
| Search | O(n) | Harus cek setiap node |
| Insert at beginning | O(1) | Update head pointer |
| Insert at end (tanpa tail) | O(n) | Traverse ke akhir |
| Insert at end (dengan tail) | O(1) | Akses tail langsung |
| Insert at middle | O(n) | Traverse ke posisi |
| Delete at beginning | O(1) | Update head pointer |
| Delete at end | O(n) | Cari node sebelum tail |
| Delete at middle | O(n) | Traverse ke posisi |
// Insert at beginning - O(1)
public void insertAtBeginning(int data) {
Node newNode = new Node(data); // O(1)
newNode.next = head; // O(1)
head = newNode; // O(1)
}
// Total: O(1)
// Insert at end tanpa tail pointer - O(n)
public void insertAtEnd(int data) {
Node newNode = new Node(data);
if (head == null) {
head = newNode;
return;
}
Node current = head;
while (current.next != null) { // O(n) - traverse
current = current.next;
}
current.next = newNode;
}
// Total: O(n)
// Search - O(n)
public boolean search(int value) {
Node current = head;
while (current != null) { // O(n) - worst case semua node
if (current.data == value) {
return true;
}
current = current.next;
}
return false;
}| Operasi | Time Complexity | Penjelasan |
|---|---|---|
| Insert at beginning | O(1) | Update head dan prev pointer |
| Insert at end | O(1) | Update tail dan next pointer |
| Delete at beginning | O(1) | Update head |
| Delete at end | O(1) | Update tail (berbeda dari single LL) |
| Delete by value | O(n) | Cari node dulu |
// Delete at end (Double LL) - O(1)
public void deleteAtEnd() {
if (tail == null) return;
if (head == tail) {
head = tail = null;
} else {
tail = tail.prev; // O(1) - langsung akses prev
tail.next = null; // O(1)
}
}
// Total: O(1) - berbeda dari Single LL yang O(n)| Operasi | Single LL | Double LL | Circular Single | Circular Double |
|---|---|---|---|---|
| Insert at head | O(1) | O(1) | O(1) | O(1) |
| Insert at tail | O(n)* | O(1) | O(1) | O(1) |
| Delete at head | O(1) | O(1) | O(1) | O(1) |
| Delete at tail | O(n) | O(1) | O(n) | O(1) |
| Search | O(n) | O(n) | O(n) | O(n) |
| Reverse | O(n) | O(n) | O(n) | O(n) |
*O(1) jika ada tail pointer
| Operasi | Array-based | Linked List-based |
|---|---|---|
| Push | O(1) amortized | O(1) |
| Pop | O(1) | O(1) |
| Peek/Top | O(1) | O(1) |
| isEmpty | O(1) | O(1) |
| isFull | O(1) | N/A |
| Search | O(n) | O(n) |
| Size | O(1) | O(1) |
Space Complexity: O(n) untuk keduanya
// Push (Array) - O(1) amortized
public void push(int value) {
if (top == stack.length - 1) {
// Resize array - O(n), tapi jarang terjadi
resize();
}
stack[++top] = value; // O(1)
}
// Amortized O(1) karena resize tidak sering
// Push (Linked List) - O(1)
public void push(int value) {
StackNode newNode = new StackNode(value); // O(1)
newNode.next = top; // O(1)
top = newNode; // O(1)
}
// Total: O(1) - selalu konstan, tidak ada resize
// Search - O(n)
public int search(int value) {
Node current = top;
int position = 1;
while (current != null) { // O(n) worst case
if (current.data == value) {
return position;
}
current = current.next;
position++;
}
return -1;
}| Operasi | Linear Array | Circular Array | Linked List |
|---|---|---|---|
| Enqueue | O(1) | O(1) | O(1) |
| Dequeue | O(n)* | O(1) | O(1) |
| Peek Front | O(1) | O(1) | O(1) |
| Peek Rear | O(1) | O(1) | O(1) |
| isEmpty | O(1) | O(1) | O(1) |
| isFull | O(1) | O(1) | N/A |
*O(n) karena harus shift elemen
Space Complexity: O(n) untuk semua implementasi
// Dequeue (Linear Array) - O(n)
public int dequeue() {
int value = queue[front];
// Shift semua elemen ke kiri
for (int i = 0; i < rear; i++) { // O(n)
queue[i] = queue[i + 1];
}
rear--;
return value;
}
// Total: O(n) - operasi shift mahal
// Dequeue (Circular Array) - O(1)
public int dequeue() {
int value = queue[front];
front = (front + 1) % maxSize; // O(1) - hanya update index
size--;
return value;
}
// Total: O(1) - tidak ada shift
// Dequeue (Linked List) - O(1)
public int dequeue() {
int value = front.data;
front = front.next; // O(1) - hanya pindah pointer
if (front == null) {
rear = null;
}
size--;
return value;
}
// Total: O(1)| Operasi | Array | Single LL | Double LL | Stack | Queue | Circular Queue |
|---|---|---|---|---|---|---|
| Access | O(1) | O(n) | O(n) | O(n)* | O(n)* | O(n)* |
| Search | O(n) | O(n) | O(n) | O(n) | O(n) | O(n) |
| Insert (front) | O(n) | O(1) | O(1) | - | - | - |
| Insert (end) | O(1)** | O(n)*** | O(1) | O(1) | O(1) | O(1) |
| Delete (front) | O(n) | O(1) | O(1) | - | O(1) | O(1) |
| Delete (end) | O(1) | O(n) | O(1) | O(1) | - | - |
| Space | O(n) | O(n) | O(n) | O(n) | O(n) | O(n) |
*Tidak sesuai use case **Amortized O(1), bisa O(n) saat resize ***O(1) dengan tail pointer
┌─────────────────────────────────────────────────────────────────────┐
│ PANDUAN PEMILIHAN STRUKTUR DATA │
├─────────────────────────────────────────────────────────────────────┤
│ Kebutuhan │ Pilihan Terbaik │
├─────────────────────────────────────────────────────────────────────┤
│ Akses random cepat │ Array │
│ Insert/delete di awal │ Linked List │
│ Insert/delete di kedua ujung │ Double Linked List / Deque │
│ LIFO (Last In First Out) │ Stack │
│ FIFO (First In First Out) │ Queue │
│ Traversal berulang (circular) │ Circular Linked List │
│ Traversal dua arah │ Double Linked List │
│ Memory terbatas, ukuran fixed │ Array │
│ Ukuran dinamis │ Linked List │
│ Cache performance penting │ Array │
└─────────────────────────────────────────────────────────────────────┘
| Skenario | Pilih | Kompleksitas |
|---|---|---|
| Akses by index | Array | O(1) |
| Akses elemen terakhir | Array atau Double LL dengan tail | O(1) |
| Akses elemen pertama | Semua struktur | O(1) |
| Akses elemen tengah | Array | O(1) |
| Skenario | Pilih | Kompleksitas |
|---|---|---|
| Insert di awal | Linked List | O(1) |
| Insert di akhir | Array (amortized) atau Double LL | O(1) |
| Insert di tengah | Linked List (jika posisi diketahui) | O(1) insert, O(n) cari posisi |
| Insert terurut | Array (binary search) | O(log n) cari + O(n) shift |
| Skenario | Pilih | Kompleksitas |
|---|---|---|
| Delete di awal | Linked List | O(1) |
| Delete di akhir | Double Linked List | O(1) |
| Delete by value | Hash Table | O(1) average |
| Delete di tengah | Linked List | O(1) delete, O(n) cari |
| Skenario | Pilih | Kompleksitas |
|---|---|---|
| Search by key | Hash Table | O(1) average |
| Search by value (sorted) | Sorted Array + Binary Search | O(log n) |
| Search by value (unsorted) | Array atau Linked List | O(n) |
| Search min/max | Heap atau Sorted Array | O(1) |
START
│
▼
┌─────────────────────────┐
│ Apakah ukuran data │
│ diketahui dan tetap? │
└───────────┬─────────────┘
│
┌─────────┴─────────┐
│ │
YES NO
│ │
▼ ▼
┌───────────┐ ┌────────────┐
│ ARRAY │ │ Linked List│
└─────┬─────┘ │ atau │
│ │ Dynamic │
│ │ Array │
│ └──────┬─────┘
│ │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ Perlu akses │ │ Insert/Delete │
│ random? │ │ di mana? │
└───────┬────────┘ └───────┬────────┘
│ │
┌────┴────┐ ┌────┴────────┐
YES NO │ │
│ │ Awal/Akhir Tengah
▼ ▼ │ │
┌───────┐ ┌───────┐ ▼ ▼
│ Array │ │ Stack/│ ┌───────┐ ┌───────┐
│ │ │ Queue │ │ LinkedList │Array │
└───────┘ └───────┘ └───────┘ └───────┘
| Aspek | Array | Linked List | Hash Table | Stack/Queue |
|---|---|---|---|---|
| Kecepatan akses | Sangat cepat | Lambat | Cepat (by key) | Hanya ujung |
| Penggunaan memory | Efisien | Overhead pointer | Overhead hash | Efisien |
| Fleksibilitas ukuran | Terbatas | Sangat fleksibel | Fleksibel | Tergantung impl |
| Cache performance | Sangat baik | Buruk | Sedang | Tergantung impl |
| Kompleksitas impl | Mudah | Sedang | Kompleks | Mudah |
public void reverse() {
Node prev = null; // O(1)
Node current = head; // O(1)
Node next = null; // O(1)
while (current != null) { // Loop n kali
next = current.next; // O(1)
current.next = prev; // O(1)
prev = current; // O(1)
current = next; // O(1)
}
head = prev; // O(1)
}Analisis:
- Inisialisasi: O(1) + O(1) + O(1) = O(1)
- Loop: n × O(1) = O(n)
- Assignment akhir: O(1)
- Total Time Complexity: O(n)
- Space Complexity: O(1) - hanya 3 variabel pointer
public boolean isPalindrome(String str) {
ArrayStack stack = new ArrayStack(str.length()); // O(n) space
// Push semua karakter - O(n)
for (int i = 0; i < str.length(); i++) {
stack.push(str.charAt(i)); // O(1) per operasi
}
// Pop dan compare - O(n)
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) != stack.pop()) { // O(1) per operasi
return false;
}
}
return true;
}Analisis:
- Membuat stack: O(n) space
- Loop push: n × O(1) = O(n)
- Loop compare: n × O(1) = O(n)
- Total Time Complexity: O(n) + O(n) = O(2n) = O(n)
- Space Complexity: O(n) - stack menyimpan n karakter
// Metode 1: Two Pass - O(n) time, O(1) space
public Node findMiddle1(Node head) {
// Pass 1: Hitung panjang
int length = 0;
Node current = head;
while (current != null) { // O(n)
length++;
current = current.next;
}
// Pass 2: Traverse ke tengah
current = head;
for (int i = 0; i < length / 2; i++) { // O(n/2)
current = current.next;
}
return current;
}
// Time: O(n) + O(n/2) = O(n)
// Space: O(1)
// Metode 2: Slow-Fast Pointer - O(n) time, O(1) space
public Node findMiddle2(Node head) {
Node slow = head;
Node fast = head;
while (fast != null && fast.next != null) { // O(n/2)
slow = slow.next; // Maju 1 langkah
fast = fast.next.next; // Maju 2 langkah
}
return slow;
}
// Time: O(n/2) = O(n)
// Space: O(1)Perbandingan:
- Keduanya O(n) time dan O(1) space
- Metode 2 lebih efisien (hanya satu pass)
public void bfs(int[][] graph, int start) {
int n = graph.length;
boolean[] visited = new boolean[n]; // O(n) space
LinkedQueue queue = new LinkedQueue();
visited[start] = true;
queue.enqueue(start); // O(1)
while (!queue.isEmpty()) { // O(V) iterasi
int current = queue.dequeue(); // O(1)
System.out.print(current + " ");
for (int i = 0; i < n; i++) { // O(V) per iterasi
if (graph[current][i] == 1 && !visited[i]) {
visited[i] = true;
queue.enqueue(i); // O(1)
}
}
}
}Analisis:
- Setiap vertex diproses sekali: O(V)
- Untuk setiap vertex, cek semua edge: O(E) total
- Time Complexity: O(V + E)
- Space Complexity: O(V) - untuk visited array dan queue
| Case | Deskripsi |
|---|---|
| Best Case | Skenario paling menguntungkan |
| Average Case | Skenario tipikal/rata-rata |
| Worst Case | Skenario paling tidak menguntungkan |
public int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}| Case | Skenario | Kompleksitas |
|---|---|---|
| Best | Target di index 0 | O(1) |
| Average | Target di tengah | O(n/2) = O(n) |
| Worst | Target tidak ada / di akhir | O(n) |
public void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}| Case | Skenario | Kompleksitas |
|---|---|---|
| Best | Pivot selalu di tengah | O(n log n) |
| Average | Pivot acak | O(n log n) |
| Worst | Array sudah sorted (pivot selalu di ujung) | O(n²) |
| Operasi | Best | Average | Worst |
|---|---|---|---|
| Push | O(1) | O(1) | O(n)* |
| Pop | O(1) | O(1) | O(1) |
| Peek | O(1) | O(1) | O(1) |
| Search | O(1) | O(n/2) | O(n) |
*Worst case O(n) terjadi saat array perlu resize
// ❌ Buruk: Sering insert di awal dengan array
for (int i = 0; i < n; i++) {
insertAtBeginning(array, value); // O(n) per operasi
}
// Total: O(n²)
// ✅ Baik: Gunakan Linked List
for (int i = 0; i < n; i++) {
list.insertAtBeginning(value); // O(1) per operasi
}
// Total: O(n)// ❌ Tanpa tail pointer - O(n)
public void insertAtEnd(int data) {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = new Node(data);
}
// ✅ Dengan tail pointer - O(1)
public void insertAtEnd(int data) {
Node newNode = new Node(data);
tail.next = newNode;
tail = newNode;
}// ❌ Linear Queue Dequeue - O(n)
public int dequeue() {
int value = queue[0];
for (int i = 0; i < size - 1; i++) {
queue[i] = queue[i + 1];
}
size--;
return value;
}
// ✅ Circular Queue Dequeue - O(1)
public int dequeue() {
int value = queue[front];
front = (front + 1) % maxSize;
size--;
return value;
}// ❌ Buruk: Menghitung size di setiap iterasi
while (list.calculateSize() > 0) { // O(n) per iterasi
// ...
}
// Total: O(n²)
// ✅ Baik: Maintain size variable
while (list.size > 0) { // O(1) per iterasi
// ...
}
// Total: O(n)// ❌ Hemat space, lambat
public int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2); // O(2ⁿ)
}
// ✅ Gunakan extra space untuk kecepatan (memoization)
public int fibonacci(int n, int[] memo) {
if (memo[n] != 0) return memo[n];
if (n <= 1) return n;
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo);
return memo[n]; // O(n) time, O(n) space
}public void mystery(int n) {
for (int i = 1; i <= n; i *= 2) {
for (int j = 0; j < n; j++) {
System.out.println(i + ", " + j);
}
}
}Jawaban:
- Loop luar: i = 1, 2, 4, 8, ... n → log n iterasi
- Loop dalam: n iterasi
- Time Complexity: O(n log n)
- Space Complexity: O(1)
public void process(int[] arr) {
int n = arr.length;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
System.out.println(arr[i] + arr[j]);
}
}
}Jawaban:
- Iterasi: n + (n-1) + (n-2) + ... + 1 = n(n+1)/2
- Time Complexity: O(n²)
- Space Complexity: O(1)
Pertanyaan: Anda perlu menyimpan data yang:
- Sering di-insert di awal dan akhir
- Sering di-delete di awal dan akhir
- Jarang diakses secara random
Struktur data terbaik?
Jawaban: Double Linked List atau Deque
- Insert di awal: O(1)
- Insert di akhir: O(1)
- Delete di awal: O(1)
- Delete di akhir: O(1)
Kode berikut tidak efisien:
public boolean hasDuplicate(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
return true;
}
}
}
return false;
}
// Time: O(n²)Bagaimana cara mengoptimasi?
Jawaban: Gunakan HashSet
public boolean hasDuplicate(int[] arr) {
HashSet<Integer> seen = new HashSet<>();
for (int num : arr) {
if (seen.contains(num)) { // O(1) average
return true;
}
seen.add(num); // O(1) average
}
return false;
}
// Time: O(n), Space: O(n)public int sum(int n) {
if (n == 0) return 0;
return n + sum(n - 1);
}Jawaban:
- Rekursi depth: n
- Setiap level: O(1) operasi
- Time Complexity: O(n)
- Space Complexity: O(n) - karena call stack
┌────────────────────────────────────────────────────────────────────┐
│ KOMPLEKSITAS CHEAT SHEET │
├────────────────────────────────────────────────────────────────────┤
│ Struktur │ Access │ Search │ Insert │ Delete │ Space │
├────────────────────────────────────────────────────────────────────┤
│ Array │ O(1) │ O(n) │ O(n) │ O(n) │ O(n) │
│ Linked List │ O(n) │ O(n) │ O(1)* │ O(1)* │ O(n) │
│ Stack │ O(n) │ O(n) │ O(1) │ O(1) │ O(n) │
│ Queue │ O(n) │ O(n) │ O(1) │ O(1) │ O(n) │
├────────────────────────────────────────────────────────────────────┤
│ *jika posisi sudah diketahui │
└────────────────────────────────────────────────────────────────────┘
- Big O fokus pada worst case dan growth rate
- Pilih struktur data berdasarkan operasi yang paling sering
- Trade-off antara time dan space complexity
- Konstanta dan lower order terms diabaikan
- Amortized analysis penting untuk operasi yang jarang mahal
-
Analisis kompleksitas waktu dan ruang untuk semua operasi di Double Linked List implementasi Anda
-
Implementasikan dan bandingkan performa Linear Queue vs Circular Queue untuk 10000 operasi enqueue/dequeue
-
Optimalkan algoritma berikut dan jelaskan perbaikannya:
public int findMax(LinkedList list) { int max = Integer.MIN_VALUE; for (int i = 0; i < list.size(); i++) { // size() adalah O(n) if (list.get(i) > max) { // get(i) adalah O(n) max = list.get(i); } } return max; }
-
Buktikan secara matematis bahwa nested loop dengan i dari 0 sampai n dan j dari i sampai n menghasilkan O(n²)
-
Desain struktur data hybrid yang mendukung:
- Insert: O(1)
- Delete: O(1)
- Get Min: O(1)
- Get Max: O(1)
Selamat belajar!