Topics › Greedy Algorithms › Medium/Hard
Shortest Job First
Java source ↗ Edit this article ↗
Jump to code:
Problem
A CPU has n processes to run. Process i needs bt[i] units of CPU time (its burst time), and every process arrives at time 0. The scheduler is non-preemptive Shortest Job First (SJF): whenever the CPU becomes free, it picks the waiting process with the smallest burst time and runs it to completion. The waiting time of a process is how long it sits in the queue before it starts, which here equals its start time. Return the average waiting time, rounded down to an integer.
Constraints (GfG version): 1 ≤ n ≤ 10^5, 1 ≤ bt[i] ≤ 10^5.
Example 1
bt = [4, 3, 7, 1, 2]
answer = 4
SJF runs the bursts in the order 1, 2, 3, 4, 7. They start at times 0, 1, 3, 6, 10, so the total waiting time is 20 and the average is 20 / 5 = 4.
Example 2
bt = [1, 2, 3, 4]
answer = 2
Start times are 0, 1, 3, 6, total 10, average 2.5, rounded down to 2.
Why SJF at all. Run the processes in some order with bursts b1, b2, ..., bn. Process k waits for everything before it, so the total waiting time is (n-1)·b1 + (n-2)·b2 + ... + 1·b(n-1) + 0·bn: each burst is multiplied by the number of processes queued behind it. To make that sum small, the biggest multipliers must go to the smallest bursts, which is exactly ascending order. Equivalently, if a longer job ever runs immediately before a shorter one, swapping them lowers the shorter job’s wait by more than it raises the longer job’s wait. That exchange argument is why SJF minimises average waiting time when everything arrives together, and it is why this problem sits in the greedy section.
Approach 1: Brute force, simulate the scheduler
Intuition. Do literally what the scheduler does. Keep a clock. At each step, scan all unfinished processes for the one with the smallest burst, record the current clock as its waiting time, advance the clock by its burst, and mark it done. Ties may be broken arbitrarily: equal bursts produce the same multiset of start times whichever goes first.
Algorithm.
clock = 0,totalWait = 0, all processes unfinished.- Repeat
ntimes: find the unfinished process with the smallest burst; addclocktototalWait; add its burst toclock; mark it done. - Return
totalWait / n(integer division of non-negative numbers rounds down).
static long bruteForce(int[] bt) {
int n = bt.length;
if (n == 0) return 0;
boolean[] done = new boolean[n];
long clock = 0, totalWait = 0;
for (int step = 0; step < n; step++) {
int pick = -1;
for (int i = 0; i < n; i++) {
if (!done[i] && (pick == -1 || bt[i] < bt[pick])) pick = i;
}
totalWait += clock; // job 'pick' has waited from time 0 until now
clock += bt[pick];
done[pick] = true;
}
return totalWait / n;
}
Complexity. Time O(n^2): n selections, each scanning n processes. Space O(n) for the done flags. With n = 10^5 that is 10^10 steps, far too slow.
Approach 2: Optimal, sort once
Intuition. The simulation keeps asking “what is the smallest remaining burst?” and the answers, in order, are simply the bursts sorted ascending. So sort once and walk the sorted array with a running clock. Each process waits for the sum of everything before it, which is the clock value when we reach it.
Algorithm.
- Copy and sort the burst times.
clock = 0,totalWait = 0. For each burstbin sorted order:totalWait += clock, thenclock += b.- Return
totalWait / n.
static long optimal(int[] bt) {
int n = bt.length;
if (n == 0) return 0;
int[] sorted = bt.clone();
Arrays.sort(sorted);
long clock = 0, totalWait = 0;
for (int b : sorted) {
totalWait += clock;
clock += b;
}
return totalWait / n;
}
Dry run on example 1, sorted bursts [1, 2, 3, 4, 7]:
| position | burst | waiting time (clock before) | clock after |
|---|---|---|---|
| 1 | 1 | 0 | 1 |
| 2 | 2 | 1 | 3 |
| 3 | 3 | 3 | 6 |
| 4 | 4 | 6 | 10 |
| 5 | 7 | 10 | 17 |
Total waiting time 0 + 1 + 3 + 6 + 10 = 20, average 20 / 5 = 4.
Complexity. Time O(n log n) for the sort; the accumulation is O(n). Space O(n) for the sorted copy (or O(1) extra if you may sort the input in place). Since bursts are bounded by 10^5, a counting sort would make this O(n + max burst), but Arrays.sort is already far inside the limits.
The same loop gives the other usual metrics for free: the turnaround time of a process is its waiting time plus its burst, which is the clock after column, and the average turnaround is the average waiting time plus the average burst.
Edge cases and pitfalls
- Overflow. The total waiting time is roughly
n^2 / 2 · max burst, up to about5 × 10^14for the given limits, soclockandtotalWaitmust belong. Even the average can reach about5 × 10^9, pastInteger.MAX_VALUE, which is why the methods returnlong. The test with 3000 bursts of100000has a total wait of449,850,000,000, which anintaccumulator would wrap. - Rounding. The judge wants the floor. Integer division of non-negative values already floors; do not use
Math.round, which turns 2.5 into 3. - Do not mutate the input. Sorting
btin place changes the caller’s array; the code sorts a clone. - Ties. Equal bursts can run in either order without changing the total.
- Single process. It starts immediately, so the average is 0. An empty input is not allowed by the constraints, but the methods return 0 rather than dividing by zero.
- Different arrival times. If processes arrive at different times, sorting once is no longer enough: you must simulate the clock and, at each decision point, pick the shortest job among those that have already arrived, typically with a min-heap. That variant is a different problem.
Related problems
- Job sequencing Problem is another scheduling greedy, where deadlines and profits drive the order.
- N meetings in one room picks meetings by earliest finish time, a close cousin of “shortest first”.
- Minimum number of platforms required for a railway also reasons about time-ordered events.
- Program for Least Recently Used (LRU) Page Replacement Algorithm is the other operating-systems style simulation in this section.