Session 3
Task Scheduling Algorithms
A task scheduling problem is formulated as an optimization problem which in which we need to determine the set of tasks from the given tasks that can ve accomplished withintheir deadlines along with their order of scheduling such that the profilt is maximum. So, this is a maximixation optimization problem with a constraint that tasks must be completed within their deadlines.
Objective
The main objectives of this session are to:
- Implement a task scheduling algorithm
- Test the algorithm on different problems instances
- Differentiate between various task scheduling approaches based on their performance and efficiency
- Brute Force Approach
- Efficient task scheduling algorithm
Question 1
Problem Statement
Apply a brute force approach to schedule three jobs J1, J2 and J3 with service times as 5,8,12 respectively. The actual service time units are not relevant to the problems. Make all possible job schedules , calculate the total times spent in jobs by the system. Find the optimal schedule (total time). If there are N jobs , what would be the total number of job schedules?
Mathematical Logic
For a sequence of jobs (J_a, J_b, J_c) with service times (t_a, t_b, t_c):
J_afinishes att_aJ_bfinishes att_a + t_bJ_cfinishes att_a + t_b + t_c
The total time spent in jobs by the system is the sum of the finishing times of all jobs, which can be calculated as: Total Time = (t_a) + (t_a + t_b) + (t_a + t_b + t_c) = 3*t_a + 2*t_b + t_c
Implementation
Python
from itertools import permutations
def solve_brute_force(jobs):
# jobs is a list of tuples: (name, time)
all_configs = list(permutations(jobs))
best_time = float("inf")
best_seq = None
for config in all_configs:
current_finish_time = 0
total_system_time = 0
# Calculate completion time for each job in this specific order
for name, time in config:
current_finish_time += time
total_system_time += current_finish_time
print(
f"Schedule {'-'.join([j[0] for j in config])}: Total Time = {total_system_time}"
)
# Track the minimum time found
if total_system_time < best_time:
best_time = total_system_time
best_seq = config
print(f"\nOptimal: {'-'.join([j[0] for j in best_seq])} with Time: {best_time}")
job_list = [("J1", 5), ("J2", 8), ("J3", 12)]
solve_brute_force(job_list)
C Language
#include <stdio.h>
#include <string.h>
void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; }
// Function to calculate system time for a given permutation
int calculate_time(int arr[], int n) {
int current_finish = 0, total = 0;
for(int i = 0; i < n; i++) {
current_finish += arr[i];
total += current_finish;
}
return total;
}
void find_optimal(int arr[], int start, int n, int *min_time) {
if (start == n) {
int current_total = calculate_time(arr, n);
if (current_total < *min_time) *min_time = current_total;
return;
}
for (int i = start; i < n; i++) {
swap(&arr[start], &arr[i]); // Generate permutation
find_optimal(arr, start + 1, n, min_time);
swap(&arr[start], &arr[i]); // Backtrack
}
}
int main() {
int times[] = {5, 8, 12};
int n = 3;
int min_time = 100000; // Initialize with a large value
find_optimal(times, 0, n, &min_time);
printf("Optimal Total System Time: %d\n", min_time);
return 0;
}
Rust
fn calculate_total_time(sequence: &Vec<(&str, i32)>) -> i32 {
let mut current_time = 0;
let mut total_time = 0;
for &(_, time) in sequence {
current_time += time; // Finish time of current job
total_time += current_time; // Cumulative sum
}
total_time
}
fn permute(jobs: &mut Vec<(&str, i32)>, start: usize, best: &mut (i32, Vec<String>)) {
if start == jobs.len() {
let time = calculate_total_time(jobs);
if time < best.0 {
best.0 = time;
best.1 = jobs.iter().map(|(id, _)| id.to_string()).collect();
}
return;
}
for i in start..jobs.len() {
jobs.swap(start, i); // Swap to create new permutation
permute(jobs, start + 1, best);
jobs.swap(start, i); // Backtrack
}
}
fn main() {
let mut jobs = vec![("J1", 5), ("J2", 8), ("J3", 12)];
let mut best = (i32::MAX, vec![]);
permute(&mut jobs, 0, &mut best);
println!("Optimal Schedule: {:?} Total Time: {}", best.1, best.0);
}
Question 2
Problem Statement
Implement the task scheduling algorithm on your system to minimize the total amount of time spent in the system for the following problem:
| Job | Service Time |
|---|---|
| 1 | 5 |
| 2 | 10 |
| 3 | 7 |
| 4 | 8 |
Mathematical Logic
To minimize the total amount of time spent in the system, we can use the Shortest Job First (SJF) scheduling algorithm. This algorithm schedules jobs in order of their service times, starting with the shortest job first. The total time spent in the system can be calculated as follows:
- Schedule the jobs in ascending order of their service times: J1 (5), J3 (7), J4 (8), J2 (10)
- Calculate the finishing times for each job:
- J1 finishes at 5
- J3 finishes at 5 + 7 = 12
- J4 finishes at 12 + 8 = 20
- J2 finishes at 20 + 10 = 30
- The total time spent in the system is the sum of the finishing times: 5 + 12 + 20 + 30 = 67
Implementation
Python
def minimize_total_time(jobs):
# Sort jobs by service time (index 1 of the tuple)
jobs.sort(key=lambda x: x[1])
current_time = 0
total_spent = 0
sequence = []
for job_id, service_time in jobs:
current_time += service_time # Time when this specific job finishes
total_spent += current_time # Add this finish time to the system total
sequence.append(str(job_id))
print(f"Optimal Sequence: {' -> '.join(sequence)}")
print(f"Total Time Spent in System: {total_spent}")
# Input: (Job ID, Service Time)
job_list = [(1, 5), (2, 10), (3, 7), (4, 8)]
minimize_total_time(job_list)
C Language
#include <stdio.h>
#include <stdlib.h>
struct Job {
int id;
int service_time;
};
// Comparator for qsort: sorts in ascending order of service_time
int compare_jobs(const void *a, const void *b) {
return ((struct Job*)a)->service_time - ((struct Job*)b)->service_time;
}
int main() {
struct Job jobs[] = {{1, 5}, {2, 10}, {3, 7}, {4, 8}};
int n = 4;
// Sort jobs using Shortest Job First strategy
qsort(jobs, n, sizeof(struct Job), compare_jobs);
int finish_time = 0;
int total_system_time = 0;
printf("Optimal Sequence: ");
for (int i = 0; i < n; i++) {
finish_time += jobs[i].service_time; // Completion time of job i
total_system_time += finish_time; // Cumulative system wait time
printf("%d%s", jobs[i].id, (i == n - 1) ? "" : " -> ");
}
printf("\nTotal Time Spent in System: %d\n", total_system_time);
return 0;
}
Rust
#[derive(Debug)]
struct Job {
id: i32,
service_time: i32,
}
fn main() {
let mut jobs = vec![
Job {
id: 1,
service_time: 5,
},
Job {
id: 2,
service_time: 10,
},
Job {
id: 3,
service_time: 7,
},
Job {
id: 4,
service_time: 8,
},
];
// Sort jobs by service_time ascending
jobs.sort_by_key(|j| j.service_time);
let mut finish_time = 0;
let mut total_system_time = 0;
let mut sequence = Vec::new();
for job in jobs {
finish_time += job.service_time; // Cumulative time for this job
total_system_time += finish_time; // Total time spent across all jobs
sequence.push(job.id.to_string());
}
println!("Optimal Sequence: {}", sequence.join(" -> "));
println!("Total Time Spent in System: {}", total_system_time);
}
Question 3
Problem Statement
Consider the following jobs, deadlines and profits. Implement the task scheduling algorithm with deadlines to maximize the total profits.
| Job | Deadline | Profits |
|---|---|---|
| 1 | 3 | 50 |
| 2 | 4 | 20 |
| 3 | 5 | 70 |
| 4 | 3 | 15 |
| 5 | 2 | 10 |
| 6 | 1 | 47 |
| 7 | 1 | 60 |
Mathematical Logic
To maximize the total profits while scheduling jobs with deadlines, we can use a greedy approach. We will sort the jobs based on their profits in descending order and then schedule them as late as possible before their deadlines.
- Sort the jobs based on profits: J3 (70), J7 (60), J1 (50), J6 (47), J2 (20), J4 (15), J5 (10)
- Schedule the jobs:
- J3 is scheduled at time 5 (profit 70)
- J7 is scheduled at time 4 (profit 60)
- J1 is scheduled at time 3 (profit 50)
- J6 is scheduled at time 2 (profit 47)
- J2 is scheduled at time 1 (profit 20)
- J4 and J5 cannot be scheduled as their deadlines have passed.
- The total profit is the sum of the profits of the scheduled jobs: 70 + 60 + 50 + 47 + 20 = 247
- The optimal schedule is: J2 (1), J6 (2), J1 (3), J7 (4), J3 (5) with a total profit of 247.
Implementation
Python
def solve_job_sequencing(jobs):
# 1. Sort jobs by profit in descending order
# (Job ID, Deadline, Profit)
jobs.sort(key=lambda x: x[2], reverse=True)
# 2. Find max deadline to size the timeline
max_d = max(job[1] for job in jobs)
# 3. Initialize slots (0 is ignored, using 1 to max_d)
slots = [-1] * (max_d + 1)
total_profit = 0
count_jobs = 0
for job_id, deadline, profit in jobs:
# 4. Try to place job in the latest available slot from deadline down to 1
for j in range(deadline, 0, -1):
if slots[j] == -1:
slots[j] = job_id
total_profit += profit
count_jobs += 1
break
# Filter out empty slots for the result
scheduled_jobs = [slots[i] for i in range(1, max_d + 1) if slots[i] != -1]
return scheduled_jobs, total_profit
# Data: (ID, Deadline, Profit)
job_data = [
(1, 3, 50),
(2, 4, 20),
(3, 5, 70),
(4, 3, 15),
(5, 2, 10),
(6, 1, 47),
(7, 1, 60),
]
sequence, profit = solve_job_sequencing(job_data)
print(f"Scheduled Sequence: {sequence}")
print(f"Maximum Profit: {profit}")
C Language
#include <stdio.h>
#include <stdlib.h>
struct Job {
int id, deadline, profit;
};
// Sort descending based on profit
int compare(const void* a, const void* b) {
return ((struct Job*)b)->profit - ((struct Job*)a)->profit;
}
void schedule_jobs(struct Job jobs[], int n) {
qsort(jobs, n, sizeof(struct Job), compare);
int max_d = 0;
for (int i = 0; i < n; i++)
if (jobs[i].deadline > max_d) max_d = jobs[i].deadline;
int slots[max_d + 1];
int result[max_d + 1];
for (int i = 0; i <= max_d; i++) slots[i] = 0; // Initialize as free
int total_profit = 0;
for (int i = 0; i < n; i++) {
// Find a slot from its deadline backwards
for (int j = jobs[i].deadline; j > 0; j--) {
if (slots[j] == 0) {
slots[j] = 1; // Mark as occupied
result[j] = jobs[i].id;
total_profit += jobs[i].profit;
break;
}
}
}
printf("Maximum Profit: %d\nScheduled Sequence: ", total_profit);
for (int i = 1; i <= max_d; i++) {
if (slots[i]) printf("%d ", result[i]);
}
printf("\n");
}
int main() {
struct Job jobs[] = {{1,3,50}, {2,4,20}, {3,5,70}, {4,3,15}, {5,2,10}, {6,1,47}, {7,1,60}};
int n = sizeof(jobs) / sizeof(jobs[0]);
schedule_jobs(jobs, n);
return 0;
}
Rust
#[derive(Debug)]
struct Job {
id: i32,
deadline: usize,
profit: i32,
}
fn main() {
let mut jobs = vec![
Job {
id: 1,
deadline: 3,
profit: 50,
},
Job {
id: 2,
deadline: 4,
profit: 20,
},
Job {
id: 3,
deadline: 5,
profit: 70,
},
Job {
id: 4,
deadline: 3,
profit: 15,
},
Job {
id: 5,
deadline: 2,
profit: 10,
},
Job {
id: 6,
deadline: 1,
profit: 47,
},
Job {
id: 7,
deadline: 1,
profit: 60,
},
];
// Sort by profit descending
jobs.sort_by(|a, b| b.profit.cmp(&a.profit));
let max_deadline = jobs.iter().map(|j| j.deadline).max().unwrap_or(0);
let mut slots = vec![0; max_deadline + 1]; // 0 indicates an empty slot
let mut total_profit = 0;
let mut sequence = Vec::new();
for job in jobs {
// Find a free slot for this job (from deadline backwards)
for t in (1..=job.deadline).rev() {
if slots[t] == 0 {
slots[t] = job.id;
total_profit += job.profit;
sequence.push(job.id);
break;
}
}
}
println!("Max Profit: {}", total_profit);
println!("Scheduled Jobs (in order of assignment): {:?}", sequence);
}
Conclusion
In this session, we have explored various task scheduling algorithms and their implementations. We have seen how to apply a brute force approach to schedule jobs and calculate the total time spent in the system. We also implemented the Shortest Job First (SJF) algorithm to minimize the total time spent in the system and a greedy approach to maximize profits while scheduling jobs with deadlines. These algorithms are fundamental in understanding how to efficiently manage tasks in various applications, such as operating systems, project management, and manufacturing processes.