diff --git a/cpu_scheduling_algorithms/non_preemptive_sjf_scheduling.cpp b/cpu_scheduling_algorithms/non_preemptive_sjf_scheduling.cpp
index e7d9d370960..60b03bddd79 100644
--- a/cpu_scheduling_algorithms/non_preemptive_sjf_scheduling.cpp
+++ b/cpu_scheduling_algorithms/non_preemptive_sjf_scheduling.cpp
@@ -1,26 +1,47 @@
/**
* @file
- * @brief Implementation of SJF CPU scheduling algorithm
+ * @brief Implementation of the non-preemptive Shortest Job First (SJF) CPU
+ * scheduling algorithm
* @details
- * shortest job first (SJF), also known as shortest job next (SJN), is a
- * scheduling policy that selects for execution the waiting process with the
- * smallest execution time. SJN is a non-preemptive algorithm. Shortest
- * remaining time is a preemptive variant of SJN.
+ * Shortest job first (SJF), also known as shortest job next (SJN), is a
+ * scheduling policy that selects for execution the *already arrived*
+ * process with the smallest burst (execution) time. This is the
+ * non-preemptive variant: once a process starts running it is not
+ * interrupted, even if a shorter job arrives while it is executing.
*
* detailed description on SJF scheduling
- * Author : Lakshmi Srikumar
+ *
+ * A correct implementation must never schedule a process that has not yet
+ * arrived, even if it has the smallest burst time of all the processes
+ * submitted to the scheduler. At every decision point, only processes
+ * that have arrived by the current simulated time are eligible to run; if
+ * none have arrived, the CPU sits idle until the next arrival.
+ *
+ * @author [Lakshmi Srikumar](https://github.com/LakshmiSrikumar) (original
+ * skeleton)
*/
-#include /// for sorting
+#include /// for std::sort
#include /// for assert
#include /// for formatting the output
#include /// for IO operations
#include /// for std::priority_queue
-#include /// random number generation
+#include /// for std::mt19937 based random number generation
+#include /// for std::tuple
#include /// for std::unordered_set
#include /// for std::vector
-using std::cin;
+/**
+ * @namespace scheduling
+ * @brief CPU scheduling algorithms
+ */
+namespace scheduling {
+/**
+ * @namespace non_preemptive_sjf
+ * @brief Functions for the non-preemptive Shortest Job First algorithm
+ */
+namespace non_preemptive_sjf {
+
using std::cout;
using std::endl;
using std::get;
@@ -32,177 +53,174 @@ using std::unordered_set;
using std::vector;
/**
- * @brief Comparator function for sorting a vector
- * @tparam S Data type of Process ID
- * @tparam T Data type of Arrival time
- * @tparam E Data type of Burst time
- * @param t1 First tuplet1
- * @param t2 Second tuplet2
- * @returns true if t1 and t2 are in the CORRECT order
- * @returns false if t1 and t2 are in the INCORRECT order
+ * Result tuple layout, used throughout this file:
+ * 1st element: Process ID
+ * 2nd element: Arrival Time
+ * 3rd element: Burst time
+ * 4th element: Completion time
+ * 5th element: Turnaround time
+ * 6th element: Waiting time
+ */
+template
+using process_result_t = tuple;
+
+/**
+ * @brief Orders raw (id, arrival, burst) tuples by arrival time, tie-broken
+ * by process ID.
+ * @details This is used purely to determine the ORDER in which processes
+ * become available to the scheduler. It does not decide execution order.
*/
template
-bool sortcol(tuple& t1, tuple& t2) {
- if (get<1>(t1) < get<1>(t2) ||
- (get<1>(t1) == get<1>(t2) && get<0>(t1) < get<0>(t2))) {
- return true;
+struct ArrivalOrder {
+ bool operator()(const tuple& a, const tuple& b) const {
+ if (get<1>(a) != get<1>(b)) {
+ return get<1>(a) < get<1>(b);
+ }
+ return get<0>(a) < get<0>(b);
}
- return false;
-}
+};
/**
- * @class Compare
- * @brief Comparator class for priority queue
- * @tparam S Data type of Process ID
- * @tparam T Data type of Arrival time
- * @tparam E Data type of Burst time
+ * @brief Min-heap comparator for the "ready queue" of already-arrived
+ * processes: smallest burst time first, ties broken by earliest arrival,
+ * then smallest process ID.
+ * @details std::priority_queue is a max-heap with respect to the supplied
+ * comparator, so to make the *smallest* burst time appear at top() this
+ * comparator must report the *larger*-burst tuple as having lower priority.
*/
template
-class Compare {
- public:
- /**
- * @param t1 First tuple
- * @param t2 Second tuple
- * @brief A comparator function that checks whether to swap the two tuples
- * or not.
- *
- * detailed description of comparator
- * @returns true if the tuples SHOULD be swapped
- * @returns false if the tuples SHOULDN'T be swapped
- */
- bool operator()(tuple& t1,
- tuple& t2) {
- // Compare burst times for SJF
- if (get<2>(t2) < get<2>(t1)) {
- return true;
+struct ReadyQueueOrder {
+ bool operator()(const process_result_t& a,
+ const process_result_t& b) const {
+ if (get<2>(a) != get<2>(b)) {
+ return get<2>(a) > get<2>(b);
}
- // If burst times are the same, compare arrival times
- else if (get<2>(t2) == get<2>(t1)) {
- return get<1>(t2) < get<1>(t1);
+ if (get<1>(a) != get<1>(b)) {
+ return get<1>(a) > get<1>(b);
}
- return false;
+ return get<0>(a) > get<0>(b);
}
};
/**
* @class SJF
- * @brief Class which implements the SJF scheduling algorithm
+ * @brief Class which implements the non-preemptive SJF scheduling algorithm
* @tparam S Data type of Process ID
* @tparam T Data type of Arrival time
* @tparam E Data type of Burst time
*/
template
class SJF {
- /**
- * Priority queue of schedules(stored as tuples) of processes.
- * In each tuple
- * @tparam 1st element: Process ID
- * @tparam 2nd element: Arrival Time
- * @tparam 3rd element: Burst time
- * @tparam 4th element: Completion time
- * @tparam 5th element: Turnaround time
- * @tparam 6th element: Waiting time
- */
- priority_queue,
- vector>,
- Compare>
- schedule;
+ /// Raw (id, arrival, burst) triples exactly as submitted.
+ vector> processes{};
- // Stores final status of all the processes after completing the execution.
- vector> result;
+ /// Final scheduled results, in the order processes were executed.
+ vector> result{};
- // Stores process IDs. Used for confirming absence of a process while it.
- unordered_set idList;
+ /// Tracks process IDs already added, to reject duplicates.
+ unordered_set idList{};
public:
/**
- * @brief Adds the process to the ready queue if it isn't already there
+ * @brief Registers a process with the scheduler if its ID hasn't been
+ * used already.
* @param id Process ID
* @param arrival Arrival time of the process
* @param burst Burst time of the process
* @returns void
- *
*/
void addProcess(S id, T arrival, E burst) {
- // Add if a process with process ID as id is not found in idList.
if (idList.find(id) == idList.end()) {
- tuple t =
- make_tuple(id, arrival, burst, 0, 0, 0);
- schedule.push(t);
+ processes.emplace_back(id, arrival, burst);
idList.insert(id);
}
}
/**
- * @brief Algorithm for scheduling CPU processes according to
- * the Shortest Job First (SJF) scheduling algorithm.
- *
- * @details Non pre-emptive SJF is an algorithm that schedules processes
- * based on the length of their burst times. The process with the smallest
- * burst time is executed first.In a non-preemptive scheduling algorithm,
- * once a process starts executing,it runs to completion without being
- * interrupted.
- *
- * I used a min priority queue because it allows you to efficiently pick the
- * process with the smallest burst time in constant time, by maintaining a
- * priority order where the shortest burst process is always at the front.
+ * @brief Runs non-preemptive SJF scheduling over every registered
+ * process.
+ * @details We keep two collections:
+ * - `byArrival`: every process, sorted by arrival time, that has NOT
+ * yet been admitted to the ready queue.
+ * - `ready`: a min-heap (by burst time) of every process that HAS
+ * arrived but not yet run.
*
- * @returns void
+ * At each step we admit every process whose arrival time is at most
+ * the current simulated time into `ready`. If `ready` is still empty
+ * (nothing has arrived), the CPU is idle, so we fast-forward the clock
+ * to the next arrival. Otherwise we run whichever admitted process has
+ * the smallest burst time. This guarantees a process can never be
+ * scheduled before it arrives, regardless of how short its burst is.
+ * @returns Results in execution order.
*/
+ vector> scheduleForSJF() {
+ result.clear();
+ if (processes.empty()) {
+ return result;
+ }
- vector> scheduleForSJF() {
- // Variable to keep track of time elapsed so far
- double timeElapsed = 0;
+ vector> byArrival = processes;
+ std::sort(byArrival.begin(), byArrival.end(), ArrivalOrder());
- while (!schedule.empty()) {
- tuple cur = schedule.top();
+ priority_queue,
+ vector>,
+ ReadyQueueOrder>
+ ready;
- // If the current process arrived at time t2, the last process
- // completed its execution at time t1, and t2 > t1.
- if (get<1>(cur) > timeElapsed) {
- timeElapsed += get<1>(cur) - timeElapsed;
- }
+ const size_t n = byArrival.size();
+ size_t nextToArrive = 0;
+ double timeElapsed = 0;
- // Add Burst time to time elapsed
- timeElapsed += get<2>(cur);
+ while (result.size() < n) {
+ // Admit every process that has arrived by now.
+ while (nextToArrive < n &&
+ get<1>(byArrival[nextToArrive]) <= timeElapsed) {
+ const auto& p = byArrival[nextToArrive];
+ ready.push(
+ make_tuple(get<0>(p), get<1>(p), get<2>(p), 0.0, 0.0, 0.0));
+ ++nextToArrive;
+ }
- // Completion time of the current process will be same as time
- // elapsed so far
- get<3>(cur) = timeElapsed;
+ // CPU is idle: nothing has arrived yet, so jump the clock
+ // forward to the next arrival instead of guessing.
+ if (ready.empty()) {
+ timeElapsed = get<1>(byArrival[nextToArrive]);
+ continue;
+ }
- // Turnaround time = Completion time - Arrival time
- get<4>(cur) = get<3>(cur) - get<1>(cur);
+ // Run the shortest job among those that have already arrived.
+ process_result_t cur = ready.top();
+ ready.pop();
- // Waiting time = Turnaround time - Burst time
- get<5>(cur) = get<4>(cur) - get<2>(cur);
+ timeElapsed += get<2>(cur); // Add burst time
+ get<3>(cur) = timeElapsed; // Completion time
+ get<4>(cur) = get<3>(cur) - get<1>(cur); // Turnaround time
+ get<5>(cur) = get<4>(cur) - get<2>(cur); // Waiting time
// Turnaround time >= Burst time
assert(get<4>(cur) >= get<2>(cur));
-
// Waiting time is never negative
assert(get<5>(cur) >= 0);
result.push_back(cur);
- schedule.pop();
}
+
return result;
}
+
/**
- * @brief Utility function for printing the status of
- * each process after execution
+ * @brief Utility function for printing the status of each process
+ * @param toPrint the processes to print
* @returns void
*/
-
- void printResult(
- const vector>& processes) {
+ void printResult(const vector>& toPrint) const {
cout << std::setw(17) << left << "Process ID" << std::setw(17) << left
<< "Arrival Time" << std::setw(17) << left << "Burst Time"
<< std::setw(17) << left << "Completion Time" << std::setw(17)
<< left << "Turnaround Time" << std::setw(17) << left
<< "Waiting Time" << endl;
- for (const auto& process : processes) {
+ for (const auto& process : toPrint) {
cout << std::setprecision(2) << std::fixed << std::setw(17) << left
<< get<0>(process) << std::setw(17) << left << get<1>(process)
<< std::setw(17) << left << get<2>(process) << std::setw(17)
@@ -214,49 +232,69 @@ class SJF {
};
/**
- * @brief Computes the final status of processes after
- * applying non-preemptive SJF scheduling
+ * @brief Independent O(n^2) reference implementation, used only to check
+ * SJF::scheduleForSJF() against in tests.
+ * @details At every scheduling step, scans every not-yet-completed process,
+ * considers only the ones that have already arrived by the current
+ * simulated time, and greedily picks the smallest burst time (ties broken
+ * by arrival, then ID) -- deliberately written differently from the
+ * priority-queue approach above so the two can catch each other's bugs.
* @tparam S Data type of Process ID
* @tparam T Data type of Arrival time
* @tparam E Data type of Burst time
- * @param input A vector of tuples containing Process ID, Arrival time, and
- * Burst time
- * @returns A vector of tuples containing Process ID, Arrival time, Burst time,
- * Completion time, Turnaround time, and Waiting time
+ * @param input A vector of (id, arrival, burst) tuples
+ * @returns Results in execution order.
*/
template
-vector> get_final_status(
+vector> get_final_status(
vector> input) {
- // Sort the processes based on Arrival time and then Burst time
- sort(input.begin(), input.end(), sortcol);
+ const size_t n = input.size();
+ vector done(n, false);
+ vector> result;
+ result.reserve(n);
- // Result vector to hold the final status of each process
- vector> result(input.size());
double timeElapsed = 0;
- for (size_t i = 0; i < input.size(); i++) {
- // Extract Arrival time and Burst time
- T arrival = get<1>(input[i]);
- E burst = get<2>(input[i]);
-
- // If the CPU is idle, move time to the arrival of the next process
- if (arrival > timeElapsed) {
- timeElapsed = arrival;
+ for (size_t scheduled = 0; scheduled < n; ++scheduled) {
+ int best = -1;
+ for (size_t i = 0; i < n; ++i) {
+ if (done[i] || get<1>(input[i]) > timeElapsed) {
+ continue;
+ }
+ if (best == -1 || get<2>(input[i]) < get<2>(input[best]) ||
+ (get<2>(input[i]) == get<2>(input[best]) &&
+ get<1>(input[i]) < get<1>(input[best])) ||
+ (get<2>(input[i]) == get<2>(input[best]) &&
+ get<1>(input[i]) == get<1>(input[best]) &&
+ get<0>(input[i]) < get<0>(input[best]))) {
+ best = static_cast(i);
+ }
}
- // Update timeElapsed by adding the burst time
- timeElapsed += burst;
+ if (best == -1) {
+ // Nothing eligible has arrived yet; jump to the earliest
+ // remaining arrival and retry this same slot.
+ double nextArrival = -1;
+ for (size_t i = 0; i < n; ++i) {
+ if (!done[i] &&
+ (nextArrival < 0 || get<1>(input[i]) < nextArrival)) {
+ nextArrival = get<1>(input[i]);
+ }
+ }
+ timeElapsed = nextArrival;
+ --scheduled;
+ continue;
+ }
- // Calculate Completion time, Turnaround time, and Waiting time
+ done[best] = true;
+ timeElapsed += get<2>(input[best]);
double completion = timeElapsed;
- double turnaround = completion - arrival;
- double waiting = turnaround - burst;
-
- // Store the results in the result vector
- result[i] = make_tuple(get<0>(input[i]), arrival, burst, completion,
- turnaround, waiting);
+ double turnaround = completion - get<1>(input[best]);
+ double waiting = turnaround - get<2>(input[best]);
+ result.push_back(make_tuple(get<0>(input[best]), get<1>(input[best]),
+ get<2>(input[best]), completion, turnaround,
+ waiting));
}
-
return result;
}
@@ -265,52 +303,59 @@ vector> get_final_status(
* @returns void
*/
static void test() {
- // A vector to store the results of all processes across all test cases.
- vector>
- finalResult;
-
- for (int i{}; i < 10; i++) {
- std::random_device rd; // Seeding
- std::mt19937 eng(rd());
- std::uniform_int_distribution<> distr(1, 10);
-
- uint32_t n = distr(eng);
- SJF readyQueue;
- vector>
- input(n);
-
- // Generate random arrival and burst times
- for (uint32_t i{}; i < n; i++) {
- get<0>(input[i]) = i;
- get<1>(input[i]) = distr(eng); // Random arrival time
- get<2>(input[i]) = distr(eng); // Random burst time
- }
+ // Regression case: this is the exact scenario the previous
+ // priority-queue-by-burst-only implementation got wrong. Process A
+ // arrives first with the CPU idle and must run immediately, even
+ // though B (arriving later) has a shorter burst time.
+ {
+ SJF q;
+ q.addProcess(0, 0, 10); // A: arrives at 0, burst 10
+ q.addProcess(1, 1, 1); // B: arrives at 1, burst 1
+
+ auto res = q.scheduleForSJF();
+
+ assert(get<0>(res[0]) == 0); // A must run first
+ assert(get<3>(res[0]) == 10); // A completes at t=10
+ assert(get<5>(res[0]) == 0); // A never waits
+
+ assert(get<0>(res[1]) == 1); // B runs second
+ assert(get<3>(res[1]) == 11); // B completes at t=11
+ assert(get<5>(res[1]) == 9); // B waits 9 units for the CPU
+ }
+
+ // Randomized cross-check against the independent O(n^2) oracle.
+ std::random_device rd;
+ std::mt19937 eng(rd());
+ std::uniform_int_distribution countDist(1, 12);
+ std::uniform_int_distribution timeDist(1, 20);
- // Print processes before scheduling
- cout << "Processes before SJF scheduling:" << endl;
- readyQueue.printResult(input);
+ for (int trial = 0; trial < 500; ++trial) {
+ int n = countDist(eng);
+ SJF readyQueue;
+ vector> input(n);
- // Add processes to the queue
- for (uint32_t i{}; i < n; i++) {
+ for (int i = 0; i < n; ++i) {
+ input[i] = make_tuple(i, timeDist(eng), timeDist(eng));
readyQueue.addProcess(get<0>(input[i]), get<1>(input[i]),
get<2>(input[i]));
}
- // Perform SJF schedulings
- auto finalResult = readyQueue.scheduleForSJF();
-
- // Print processes after scheduling
- cout << "\nProcesses after SJF scheduling:" << endl;
- readyQueue.printResult(finalResult);
+ auto expected = get_final_status(input);
+ auto actual = readyQueue.scheduleForSJF();
+ assert(expected == actual);
}
+
cout << "All the tests have successfully passed!" << endl;
}
+} // namespace non_preemptive_sjf
+} // namespace scheduling
+
/**
* @brief Main function
* @returns 0 on successful exit
*/
int main() {
- test();
+ scheduling::non_preemptive_sjf::test();
return 0;
-}
+}
\ No newline at end of file