Bayangkan seorang freelancer dengan banyak tawaran proyek, masing-masing punya tenggat waktu (deadline) dan bayaran (profit). Ia hanya bisa mengerjakan satu proyek pada satu waktu dan setiap proyek memakan satu unit waktu. Tujuannya: pilih himpunan proyek yang bisa diselesaikan sebelum deadline-nya masing-masing, dengan total bayaran maksimum. Inilah job scheduling problem (kadang disebut job sequencing with deadlines). Algoritma greedy yang menyelesaikannya: sort by profit descending, lalu jadwalkan tiap job di slot terbaru yang masih kosong sebelum deadline-nya. Pada Chapter ini kita akan mengimplementasikannya dengan Disjoint Set Union (DSU) untuk menemukan slot kosong dalam waktu hampir konstan.
Aturan: Maksimalkan Profit dengan Constraint Deadline #
Diberikan n job dengan deadline[i] dan profit[i]. Setiap job membutuhkan satu unit waktu. Hanya satu job yang bisa dijalankan per unit waktu. Job i harus selesai paling lambat pada waktu deadline[i] (artinya dijadwalkan di slot t ≤ deadline[i]). Tujuan: maksimalkan total profit dari job yang berhasil dijadwalkan.
Strategi greedy:
- Sort job by profit descending.
- Inisialisasi array slot kosong dengan ukuran
max(deadline). - Untuk setiap job: cari slot kosong terbesar
t ≤ deadline[i]. Bila ada, isi slot dengan job; bila tidak ada, skip job. - Output total profit dan job yang dijadwalkan.
Greedy choice = profit tertinggi, jadwalkan selambat mungkin. Mengapa selambat mungkin? Karena slot awal lebih fleksibel — bisa dipakai job lain dengan deadline lebih ketat. Menunda pengisian slot awal sebanyak mungkin memaksimalkan peluang job lain dijadwalkan.
Mekanisme: DSU untuk Pencarian Slot Cepat #
Tanpa optimasi, mencari slot kosong terbesar ≤ d membutuhkan O(d) per job, sehingga total O(n · max_deadline). Dengan DSU, kita bisa lakukan dalam O(α(n)) per job.
Ide DSU: parent[t] menyimpan slot kosong terbesar berikutnya yang ≤ t. Setelah slot t diisi, parent[t] = t - 1 sehingga find(t) berikutnya akan melompat ke slot t-1. Path compression membuat operasi ini hampir konstan.
Implementasi C++ dengan DSU #
#include <iostream>
#include <vector>
#include <algorithm>
struct Job {
int id;
int deadline;
int profit;
};
// DSU find dengan path compression
int findSlot(std::vector<int>& parent, int x) {
if (parent[x] == x) return x;
return parent[x] = findSlot(parent, parent[x]);
}
// Job scheduling — return (totalProfit, listOfJobIdScheduled)
std::pair<int, std::vector<int>>
jobScheduling(std::vector<Job> jobs) {
// Sort by profit descending
std::sort(jobs.begin(), jobs.end(),
[](const Job& a, const Job& b) {
return a.profit > b.profit;
});
// Tentukan deadline maksimum untuk ukuran array slot
int maxDeadline = 0;
for (const auto& j : jobs) {
maxDeadline = std::max(maxDeadline, j.deadline);
}
// DSU: parent[i] = slot kosong terbesar ≤ i
std::vector<int> parent(maxDeadline + 1);
for (int i = 0; i <= maxDeadline; ++i) parent[i] = i;
int totalProfit = 0;
std::vector<int> scheduled;
for (const auto& j : jobs) {
int slot = findSlot(parent, j.deadline);
if (slot > 0) {
parent[slot] = slot - 1; // tandai slot terpakai
totalProfit += j.profit;
scheduled.push_back(j.id);
}
// else: tidak ada slot kosong sebelum deadline, skip
}
return {totalProfit, scheduled};
}
int main() {
std::vector<Job> jobs = {
{1, 2, 100}, {2, 1, 19}, {3, 2, 27},
{4, 1, 25}, {5, 3, 15}
};
auto [profit, scheduled] = jobScheduling(jobs);
std::cout << "Job terjadwal (id): ";
for (int id : scheduled) std::cout << id << ' ';
std::cout << '\n';
std::cout << "Jumlah job terjadwal: " << scheduled.size() << '\n';
std::cout << "Total profit: " << profit << '\n';
return 0;
}Output yang Dihasilkan:
Job terjadwal (id): 1 3 5
Jumlah job terjadwal: 3
Total profit: 142Bedah Kode Baris-per-Baris #
struct Job { int id; int deadline; int profit; };: rekam job dengan id, deadline, dan profit.iddipakai untuk identifikasi output.std::sort(jobs.begin(), ..., [](...) { return a.profit > b.profit; }): sort by profit descending. Job dengan profit tertinggi dipertimbangkan lebih dulu — inilah greedy choice.int maxDeadline = 0; for (const auto& j : jobs) maxDeadline = std::max(maxDeadline, j.deadline);: cari deadline maksimum untuk menentukan ukuran array slot. Slot maksimum yang dapat dipakai =maxDeadline.std::vector<int> parent(maxDeadline + 1); for (int i = 0; i <= maxDeadline; ++i) parent[i] = i;: inisialisasi DSU. Awalnya setiap slotimenunjuk ke dirinya sendiri (kosong).int findSlot(std::vector<int>& parent, int x): DSU find dengan path compression. Mencari slot kosong terbesar yang≤ x. Path compression mempercepat query berikutnya.return parent[x] = findSlot(parent, parent[x]);: path compression — setelah find, setiap node di sepanjang path langsung di-point ke root. Kompleksitas amortizedO(α(n)).int slot = findSlot(parent, j.deadline);: cari slot kosong terbesar yang≤ deadline. Bilaslot == 0, tidak ada slot tersedia.if (slot > 0) { parent[slot] = slot - 1; totalProfit += j.profit; scheduled.push_back(j.id); }: bila ada slot kosong, isi slot dengan job, tandaiparent[slot] = slot - 1agar query berikutnya melompat ke slot bawah. Tambahkan profit dan id job.- Output
1 3 5: job 1 (profit 100, deadline 2) → slot 2; job 3 (profit 27, deadline 2) → slot 1; job 5 (profit 15, deadline 3) → slot 3. Job 4 dan 2 di-skip karena slot≤ 1sudah penuh. Total =100 + 27 + 15 = 142.
Trace Step-by-Step #
Job setelah sort by profit desc: J1(100, d=2), J3(27, d=2), J4(25, d=1), J2(19, d=1), J5(15, d=3).
| Step | Job | Profit | Deadline | findSlot(d) | Slot dipakai | parent[] setelah | Total profit |
|---|---|---|---|---|---|---|---|
| 1 | J1 | 100 | 2 | 2 | 2 | parent[2]=1 | 100 |
| 2 | J3 | 27 | 2 | 1 | 1 | parent[1]=0 | 127 |
| 3 | J4 | 25 | 1 | 0 (≤ 0) | — | — | 127 |
| 4 | J2 | 19 | 1 | 0 (≤ 0) | — | — | 127 |
| 5 | J5 | 15 | 3 | 3 | 3 | parent[3]=2 | 142 |
Hasil akhir: 3 job terjadwal di slot {1, 2, 3} dengan total profit 142.
Kompleksitas #
| Aspek | Nilai | Catatan |
|---|---|---|
| Waktu | O(n log n + n · α(n)) | Didominasi sort. DSU find amortized O(α(n)). |
| Ruang | O(n + maxDeadline) | Job array + DSU. |
| Tanpa DSU | O(n · maxDeadline) | Naive scan slot tiap job. |
| Optimal? | Ya | Greedy-choice property terbukti via exchange argument. |
Kompleksitas waktu praktis O(n log n) karena α(n) ≤ 4 untuk n realistis (≤ 10^80).
Pembuktian Greedy-Choice Property (Ringkas) #
Misalkan S* adalah solusi optimal yang tidak memuat job g dengan profit tertinggi. Maka S* memuat job lain dengan profit lebih rendah di salah satu slot ≤ g.deadline. Karena g punya profit lebih tinggi dan bisa menempati slot yang sama (atau slot lain yang masih kosong), tukar job tersebut dengan g. Total profit naik atau sama — kontradiksi dengan asumsi S* optimal bila tidak memuat g.
Argumen serupa untuk greedy choice selanjutnya: setelah g dijadwalkan di slot terbesar yang ≤ g.deadline, sub-masalah adalah penjadwalan job tersisa pada slot kosong. Sub-masalah independen dan punya struktur identik — optimal substructure terpenuhi.
Variasi Job Scheduling #
| Variasi | Strategi | Kompleksitas |
|---|---|---|
| Unit time, deadline, profit (ini) | Greedy + DSU | O(n log n) |
Job dengan durasi t[i] | Penjadwalan lebih kompleks | NP-hard umumnya |
| Preemptive scheduling | Job bisa diinterupsi | Tergantung kriteria |
| Multi-machine scheduling | List scheduling | NP-hard (heuristik greedy) |
| Job with release time | Greedy by EDF (earliest deadline first) | Optimal untuk 1 mesin |
| Weighted job scheduling | DP + binary search | O(n log n) |
Aplikasi Nyata #
- Freelance project selection: pilih proyek dengan bayaran tinggi yang bisa selesai sebelum tenggat.
- Compiler instruction scheduling: pilih instruksi dengan profit (priority) tinggi.
- OS task scheduling (real-time): deadline-aware scheduling untuk task hard real-time.
- Cloud spot instance bidding: pilih job dengan profit tinggi yang bisa selesai sebelum spot dicabut.
- Manufacturing: jadwal produksi batch dengan tenggat pengiriman.
Komparasi dengan Variasi Lain #
| Algoritma | Waktu | Optimal? | Catatan |
|---|---|---|---|
| Greedy + DSU (ini) | O(n log n) | Ya | Unit time, deadline |
| Greedy tanpa DSU | O(n · maxDeadline) | Ya | Naive, lebih lambat |
DP O(n · maxDeadline) | O(n · maxDeadline) | Ya | Alternatif tanpa DSU |
| Brute force | O(2^n · n) | Ya | Coba semua subset |
| List scheduling (multi-mesin) | O(n log n) | Tidak | Heuristik 2-approx |
Ringkasan #
- Job scheduling: pilih himpunan job dengan total profit maksimum, tiap job satu unit waktu, harus selesai sebelum deadline.
- Greedy choice: job dengan profit tertinggi, jadwalkan di slot kosong terbesar yang
≤ deadline. - DSU dengan path compression mempercepat pencarian slot ke
O(α(n))per job. - Kompleksitas total
O(n log n)— didominasi sort. - Optimal karena greedy-choice property: tukar job profit rendah dengan profit tinggi pada slot sama → profit naik.
- Aplikasi: freelance selection, instruction scheduling, real-time OS.
Chapter berikutnya akan membahas Pembuktian Algoritma Greedy — teknik formal (exchange argument, greedy stays ahead, matroid theory) untuk menjamin greedy menghasilkan solusi optimal.