Prinsip greedy: greedy choice property dan optimal substructure, serta implementasi masalah klasik activity selection, Huffman coding, dan fractional knapsack dengan bukti korektivitas.

Setelah di episode 12 kita menutup FASE 3 dengan bit manipulation, pada episode ini kita memulai FASE 4: GREEDY & DYNAMIC PROGRAMMING dengan Greedy Algorithms. Greedy membuat pilihan lokal optimal di setiap langkah dengan harapan menghasilkan global optimal — cepat dan elegan, tetapi tidak selalu benar. Membedakan kapan greedy bisa dipakai dan kapan tidak adalah salah satu keterampilan paling penting dalam merancang algoritma.
Greedy menjadi powerful ketika masalah memiliki dua properti kunci: greedy choice property (pilihan lokal optimal mengarah ke global optimal) dan optimal substructure (solusi optimal masalah besar mengandung solusi optimal sub-masalah). Tanpa properti ini, greedy akan menghasilkan solusi yang salah.
Pilihan lokal yang dibuat sekarang (tanpa melihat ke belakang) adalah bagian dari solusi optimal. Artinya, kita tidak perlu mempertimbangkan alternatif — pilihan greedy sudah cukup.
Solusi optimal masalah besar mengandung solusi optimal dari sub-masalah yang lebih kecil. Ini memungkinkan greedy membangun solusi dari bagian-bagian kecil.
Kedua properti ini harus dibuktikan secara formal untuk setiap masalah sebelum menggunakan greedy. Tanpa bukti, greedy bisa gagal — seperti Travelling Salesman Problem di mana greedy (nearest neighbor) tidak menghasilkan rute optimal.
Activity selection: diberikan aktivitas dengan waktu mulai dan selesai, pilih maksimum aktivitas yang tidak saling tumpang tindih. Greedy: pilih aktivitas yang selesai paling awal.
def activity_selection(activities):
"""activities: list of (start, finish) tuples"""
sorted_acts = sorted(activities, key=lambda x: x[1])
result = [sorted_acts[0]]
last_finish = sorted_acts[0][1]
for act in sorted_acts[1:]:
if act[0] >= last_finish:
result.append(act)
last_finish = act[1]
return resultMengapa greedy works: memilih aktivitas yang selesai paling awal memberikan waktu paling banyak untuk aktivitas berikutnya. Jika ada solusi optimal yang tidak memilih aktivitas pertama (selesai paling awal), kita bisa menggantinya tanpa kehilangan jumlah aktivitas → greedy choice property terpenuhi.
Huffman coding menghasilkan prefix code optimal untuk kompresi data. Karakter dengan frekuensi tinggi mendapat kode pendek; frekuensi rendah mendapat kode panjang.
import heapq
def huffman_codes(freq):
"""freq: dict {char: frequency}"""
heap = [[f, [c, ""]] for c, f in freq.items()]
heapq.heapify(heap)
while len(heap) > 1:
lo = heapq.heappop(heap)
hi = heapq.heappop(heap)
for pair in lo[1:]:
pair[1] = '0' + pair[1]
for pair in hi[1:]:
pair[1] = '1' + pair[1]
merged = [lo[0] + hi[0]] + lo[1:] + hi[1:]
heapq.heappush(heap, merged)
return {pair[0]: pair[1] for pair in heap[0][1:]}
# Contoh
codes = huffman_codes({'a': 45, 'b': 13, 'c': 12, 'd': 16, 'e': 9, 'f': 5})
# 'a': '0', 'b': '101', 'c': '100', 'd': '111', 'e': '1101', 'f': '1100'Korektivitas: Huffman tree menghasilkan expected code length minimum untuk prefix code — terbukti dengan sifat greedy yang selalu menggabungkan dua frekuensi terkecil.
Fractional knapsack: item bisa diambil sebagian (bukan biner seperti 0/1 knapsack). Greedy: ambil berdasarkan rasio value/weight tertinggi.
def fractional_knapsack(values, weights, capacity):
items = sorted(zip(values, weights),
key=lambda x: x[0]/x[1], reverse=True)
total = 0
for v, w in items:
if capacity >= w:
total += v
capacity -= w
else:
total += v * (capacity / w)
break
return totalWarning
Fractional knapsack bisa diselesaikan dengan greedy. Tetapi 0/1 knapsack (item utuh atau tidak sama sekali) tidak bisa — membutuhkan dynamic programming (episode 14). Perbedaan kecil ini mengubah paradigm solusi secara drastis.
Greedy gagal ketika greedy choice property tidak terpenuhi. Contoh klasik: coin change (minimum koin).
# Greedy coin change — TIDAK selalu optimal
# Koin: [1, 3, 4], target: 6
# Greedy: 4 + 1 + 1 = 3 koin
# Optimal: 3 + 3 = 2 koinGreedy memilih koin terbesar dulu — tetapi tidak selalu menghasilkan jumlah minimum. Solusi: dynamic programming (episode 14).
Pada episode 13 ini, kalian telah memahami:
Di episode 14 selanjutnya kita akan membahas Dynamic Programming Fundamentals — overlapping subproblems, optimal substructure, memoization vs tabulation, dan pola 1D state. Sampai jumpa di episode 14!