Johnson¶
Khái quát¶
Thuật toán Johnson là được sử dụng để tối ưu hóa thời gian hoàn thành công việc trong các bài toán lập lịch, đặc biệt là các bài toán với hai máy và một loạt các công việc cần được xử lý theo thứ tự.
Mục tiêu của thuật toán là tìm ra thứ tự thực hiện các công việc nhằm giảm thiểu thời gian hoàn thành tất cả công việc.
Bài toán¶
Yêu cầu¶
Giả sử có n công việc, mỗi công việc phải được xử lý trên hai máy theo thứ tự cố định, máy 1 trước rồi đến máy 2.
Ta cần tìm thứ tự thực hiện sao cho tổng thời gian hoàn thành là nhỏ nhất.
Input¶
Output¶
Giải thích¶
Input:
- Dòng đầu tiên chứa
n
là số công việc. - Mỗi dòng tiếp theo gồm ba số: mã ID công việc, thời gian hoàn thành trên máy 1 và thời gian hoàn thành trên máy 2.
Output:
- Dòng đầu tiên là thứ tự thực hiện các công việc.
Các bước của thuật toán Johnson¶
1. Chia các công việc thành hai nhóm:
- Nhóm 1 gồm các công việc có thời gian xử lý trên máy 1 nhỏ hơn hoặc bằng trên máy 2.
- Nhóm 2 gồm các công việc có thời gian xử lý trên máy 1 lớn hơn trên máy 2.
2. Sắp xếp các nhóm:
- Sắp xếp nhóm 1 theo thứ tự tăng dần của thời gian xử lý trên máy 1.
- Sắp xếp nhóm 2 theo thứ tự giảm dần của thời gian xử lý trên máy 2.
3. Lập lịch:
Thực hiện các công việc theo thứ tự: nhóm 1 trước, rồi đến nhóm 2.
Viết chương trình¶
Đọc input¶
Khởi tạo cấu trúc job
gồm ba thuộc tính: id
, time_1
(thời gian thực hiện trên máy 1) và time_2
(thời gian thực hiện trên máy 2).
Trong C++, ta sử dụng struct
, còn trong Python, ta sử dụng tuple
.
Lưu input vào mảng jobs
.
Thực hiện Johnson¶
1. Chia các công việc thành hai nhóm:
2. Sắp xếp các nhóm.
Trong C++, trước khi gọi hàm sort()
sắp xếp các nhóm, ta tạo hàm so sánh thời gian hoàn thành để làm tham số cho hàm sort()
.
Gọi hàm sort()
để sắp xếp các nhóm.
3. Lập lịch.
Lần lượt đưa công việc trong nhóm 1 và nhóm 2 vào mảng schedule
.
4. Tính tổng thời gian hoàn thành.
Để tính tổng thời gian hoàn thành tất cả công việc, ta cần tính:
- Thời điểm bắt đầu và kết thúc của mỗi công việc trên máy 1.
- Thời điểm bắt đầu và kết thúc của mỗi công việc trên máy 2.
Lưu các thời điểm này trong mảng start_1
, finish_1
, start_2
và finish_2
. Thứ tự của các phần tử trong bốn mảng này tương ứng với thứ tự của mảng schedule
.
Ta có một số công thức sau:
- Vì thời điểm kết thúc công việc
i - 1
cũng chính là thời điểm bắt đầu công việci
nên thời điểm bắt đầu công việc thứi
trên máy 1 là:start_1[i] = finish_1[i - 1]
. - Thời điểm kết thúc công việc
i
trên máy 1 là:finish_1[i] = start_1[i] + time_1
. - Thời điểm bắt đầu công việc
i
trên máy 2 là:start_2[i] = max(finish_1[i], finish_2[i - 1])
. - Thời điểm kết thúc công việc
i
trên máy 2 là:finish_2[i] = start_2[i] + time_2
.
Thời gian hoàn thành tất cả công việc được tính tới thời điểm hoàn thành công việc cuối cùng trên máy 2: finish_2[n - 1]
.
Hàm calculateTime()
sau thực hiện theo ý tưởng trên.
-
Tham số
J
dùng để truyền mảng input các công việcjob
.Tham số
S
dùng để truyền mảng output các công việc đã sắp xếpschedule
. -
S[0]
là ID của công việc đầu tiên trong thứ tự đã sắp xếp tối ưuschedule
.[S[0] - 1]
là nhằm lấy chỉ số của công việc này trong mảng inputjobs
. Do ID và chỉ số chênh lệch nhau 1 đơn vị.J[S[0] - 1].time_1
là thời gian hoàn thành công việc này trên máy 1.
-
Tham số
J
dùng để truyền mảng input các công việcjob
.Tham số
S
dùng để truyền mảng output các công việc đã sắp xếpschedule
. -
S[0]
là ID của công việc đầu tiên trong thứ tự đã sắp xếp tối ưuschedule
.[S[0] - 1]
là nhằm lấy chỉ số của công việc này trong mảng inputjobs
. Do ID và chỉ số chênh lệch nhau 1 đơn vị.J[S[0] - 1]
là công việc có chỉ sốS[0] - 1
trong mảng inputjobs
. Gán công việc này cho biếnfirst_job
.first_job[1]
là thời gian hoàn thành công việc này trên máy 1.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.