Tải bản đầy đủ (.doc) (68 trang)

Ứng dụng một số thuật toán vào giải các bài toán xếp lịch công việc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (360.22 KB, 68 trang )

Nội dung Trang
PHẦN I. MỞ ĐẦU
1. Lý do chọn đề tài
2. Mục đích của sáng kiến kinh nghiệm
3. Đối tượng và phạm vi nghiên cứu
4. Cơ sở khoa học của đề tài
PHẦN II. NỘI DUNG
Chương 1. Ứng dụng một số thuật toán, định lí vào việc giải các bài
toán về xếp lịch
1. Xét bài toán xếp lịch
2. Ứng dụng một số thuật toán vào giải các bài toán xếp lịch
a. Thuật toán Johnson
b. Phương pháp Heuristic.
c. Phương pháp duyệt có đặt cận
d. Quy hoạch động
e. Phương pháp làm mịn dần phương án
f. Thuật toán tối ưu cho một số bài đặc biệt
g. Phương pháp thế vị
h. Phương pháp dựa vào luồng và đồ thị hai phía
Chương 2. Một số bài tập về sắp xếp lịch
PHẦN III. KẾT LUẬN VÀ KIẾN NGHỊ
2
2
2
2
4
8
8
11
16
20


25
34
37
41
57
68
1
PHẦN I. MỞ ĐẦU
1. Lý do chọn đề tài.
Vào năm học 2010 – 2011 tôi đã viết về đề tài “Ứng dụng một số thuật toán vào
giải các bài toán xếp lịch công việc” và được xếp bậc 4/4 của Sở GD - ĐT. Sau 3
năm trong quá trình dạy học tôi đã tìm hiểu, nghiên cứu, đúc rút thêm được một số
kiến thức phương pháp giải các bài toán xếp lịch công việc, nên tôi phát triển thêm đề
tài năm 2010 – 2011 để được một đề tài hoàn thiện hơn (Sáng kiến năm 2010 – 2011
tôi đã nêu được 4 phương pháp, năm nay tôi bổ sung thêm 4 phương pháp nữa).
Bài toán xếp lịch công việc nảy sinh từ nhiều vấn đề thực tế khác nhau: giao việc,
gia công các chi tiết máy, đóng gói hàng hóa vào một hoặc nhiều thùng, xếp lịch thi
đấu, lịch học tập, hành trình du lịch, chọn đối tượng và phương án thi công, bài toán
vận tải xếp hàng, điều hành xe, chọn địa điểm xây dựng nhà máy, kế hoạch sản xuất
các sản phẩm, xếp thời gian cho phép (chỉ có thể chọn ra phương án tương đối thích
hợp với điều kiện dữ liệu cụ thể nào đó), hơn nữa nhiều bài dưới dạng tổng quát còn
được xếp vào lớp bài toán còn hạn chế ở một số bài tập gặp trong kì thi học sinh giỏi
Tin học trước đây và nêu một số cách giải thích hợp. Qua việc giới thiệu các bài tập
tôi cố gắng minh họa một số phương pháp thường gặp nhất trong bài toán xếp lịch
Lí do chủ yếu để chọn các phương pháp là cân nhắc tới mức độ tiếp thu của học
sinh phổ thông trung học.
2. Mục đích của sáng kiến kinh nghiệm.
- Ứng dụng các thuật toán để giải các bài toán về xếp lịch, sưu tầm một số bài toán
liên quan đến xếp lịch.
- Đưa vào bồi dưỡng học sinh đi thi học sinh giỏi tỉnh.

- Đưa vào tài liệu của tổ chuyên môn.
3. Đối tượng và phạm vi nghiên cứu:
3.1 Đối tượng nghiên cứu:
- Các bài toán về xếp lịch để giảng dạy cho học sinh đi thi học sinh giỏi tỉnh.
3.2 Phạm vi nghiên cứu:
2
- Với các thuật toán, định lí, các bài toán, tôi áp dụng vào dạy học sinh ôn thi học
sinh giỏi tỉnh.
4. Cơ sở khoa học của đề tài:
4.1 Cơ sở lí luận:
Các bài toán về xếp lịch được ứng dụng rất nhiều trong thực tiễn, với sự phát triển
của CNTT như hiện nay thì việc áp dụng CNTT vào các cơ quan tổ chức là một việc
làm đúng đắn, mà công việc xếp lịch rất phổ biến
4.2 Cơ sở thực tiễn:
- Đây là bài toán có nhiều ứng dụng trong thực tiễn, từ những bài sắp xếp lịch công
việc đơn giản đến các công việc phức tạp hơn như xếp thời khóa biểu của một trường
học,…
3
NỘI DUNG SÁNG KIẾN KINH NGHIỆM
CHƯƠNG 1. ỨNG DỤNG MỘT SỐ THUẬT TOÁN, ĐỊNH LÍ VÀO VIỆC
GIẢI CÁC BÀI TOÁN VỀ XẾP LỊCH
1. Xét bài toán xếp lịch sau:
Giả sử trong một phiên làm việc từ thời điểm 0 đến thời điểm T=8640000, một
trung tâm tính toán phải thực hiện N chương trình, chương trình i thực hiện từ thời
điểm a[i] đến thời điểm b[i],
0 [ ] [ ]a i b i T≤ ≤ <
.
Cho trước một đoạn thời gian (P
1
, Q

1
). Hãy xét xem liệu tại mọi thời điểm của
đoạn đó luôn có chương trình chạy hay không?
Cho trước một đoạn thời gian (R
1
, S
1
). Hãy xét xem liệu tại mọi thời điểm của
đoạn đó luôn không có chương trình nào chạy hay không?
Hãy tìm đoạn thời gian (P, Q) dài nhất sao cho tại mọi điểm của nó luôn có
chương trình chạy.
Hãy tìm đoạn thời gian (R, S) dài nhất sao cho tại mọi điểm của nó đều không có
chương trình nào chạy.
Dữ liệu vào được cho bởi file văn bản LICHTRUC.INP có cấu trúc
- Dòng thứ nhất ghi số nguyên dương
200N

.
- Dòng thứ i+1
(1 )i N≤ ≤
ghi hai số nguyên không âm A[i] và B[i]
- Dòng thứ N+2 ghi hai số nguyên không âm P
1
, Q
1
(
1 1
P Q≤
)
- Dòng thứ N+3 ghi hai số nguyên không âm R

1
, S
1
(
1 1
R S≤
)
Dữ liệu ra ghi vào file văn bản LICHTRUC.OUT có cấu trúc
- Dòng đầu tiên ghi 1 hoặc 0 tùy thuộc kết quả cụ thể (tìm được ghi số 1, không
tìm được ghi số 0).
- Dòng thứ hai cũng ghi 1 hoặc 0 theo nghĩa trên.
- Dòng thứ ba ghi hai số P, Q
- Dòng thứ tư ghi hai số R, S.
4
Ví dụ:
LICHTRUC.INP LICHTRUC.OUT
5
1000 10000
2000 20000
20000 100000
200000 500000
8000000 8500000
1000 100000
0 1000
1
0
8000000 8500000
500001 7999999
Nhận xét: Để giải bài toán này thì ta có thể dùng mảng một chiều A (mỗi phần tử
là một bản ghi gồm 2 trường: thời điểm đầu và thời điểm cuối của mỗi chương trình).

Sắp mảng A theo thời điểm đầu của mỗi chương trình. Sau đó duyệt mảng A để tạo
mảng B (cùng kiểu với A) lưu các đoạn gồm các thời điểm liên tiếp có chương trình,
và tạo mảng C để lưu các đoạn gồm các thời điểm liên tiếp không có chương trình
nào. Dựa vào mảng B và C dẫn ra kết luận thích hợp.
Chương trình
const T=8640000;
max=1000;
fi='lichtruc.inp';
fo='lichtruc.out';
type re=record d,c:longint end;
m1=array[1 max] of re;
var a,b,c:m1;
f:text;
n,sd,sr,i,j:integer;
p,q,r,s:longint;
procedure readfile;
var i:integer;
g:text;
begin
assign(g,fi);
reset(g);
readln(g,n);
for i:=1 to n do
begin
read(g,a[i].d,a[i].c);
readln(g);
end;
readln(g,p,q);
readln(g,r,s);
close(g);

end;
procedure sort;
var i,j:integer;
tgd,tgc:longint;
begin
5
for i:=1 to n -1 do
for j:=i+1 to n do
if a[i].d>a[j].d then
begin
tgd:=a[i].d;
a[i].d:=a[j].d;
a[j].d:=tgd;
tgc:=a[i].c;
a[i].c:=a[j].c;
a[j].c:=tgc;
end;
end;
procedure Create_b_c;
var i,j:integer;
last:longint;
begin
sd:=1;
i:=1;
b[sd].d:=a[i].d;
last:=a[i].c;
while i<n do
begin
if a[i+1].d>last+1 then
begin

b[sd].c:=last;
inc(sd);
b[sd].d:=a[i+1].d;
last:=a[i+1].c;
inc(i);
end
else
begin
if a[i+1].c > last then last:=a[i+1].c;
inc(i);
end;
end;
b[sd].c:=last;
sr:=1;
if b[1].d<>0 then
begin
c[sr].d:=0;
c[sr].c:=b[1].d-1;
inc(sr);
end;
i:=2;
while i<=sd do
begin
c[sr].d:=b[i-1].c+1;
c[sr].c:=b[i].d-1;
inc(sr);
inc(i);
end;
if b[sd].c<T then
begin

c[sr].d:=b[sd].c+1;
c[sr].c:=t;
end;
end;
procedure caua;
6
var i:integer;
begin
for i:=1 to sd do
if (b[i].d<=p) and (b[i].c>=q) then
begin
writeln(f,1);
exit;
end;
writeln(f,0);
end;
procedure caub;
var i:integer;
begin
for i:=1 to sr do
if (c[i].d<=r) and (c[i].c>=s) then
begin
writeln(f,1);
exit;
end;
writeln(f,0);
end;
procedure cauc;
var ld,lc,lmax:longint;
i:integer;

begin
lmax:=-1;
for i:=1 to sd do
if (b[i].c-b[i].d)>lmax then
begin
lmax:=b[i].c-b[i].d;
ld:=b[i].d;
lc:=b[i].c;
end;
writeln(f,ld,' ',lc);
end;
procedure caud;
var ld,lc,lmax:longint;
i:integer;
begin
lmax:=-1;
for i:=1 to sr do
if (c[i].c-c[i].d)>lmax then
begin
ld:=c[i].d;
lc:=c[i].c;
lmax:=c[i].c-c[i].d;
end;
writeln(f,ld,' ',lc);
end;
begin
assign(f,fo);
rewrite(f);
readfile;
sort;

create_b_c;
caua;
caub;
cauc;
caud;
7
close(f);
end.
2. ứng dụng một số thuật toán vào giải các bài toán xếp lịch
a. Dựng thut toỏn Johnson
Vớ d: Lp lch gia cụng trờn hai mỏy
Mt sn phm gm N chi tit, mi chi tit phi gia cụng ln lt trờn hai mỏy A v
B (A trc, B sau). Thi gian thc hin chi tit i trờn mỏy A l A
i
, trờn mỏy B l B
i
(i=1, 2, , N). Hóy xp lch hon thnh sn phm vi thi gian ớt nht. Hin lch v
thi gian hon thnh.
D liu vo t file GIACONG.INP cú cu trỳc:
- Dũng u tiờn l s nguyờn dng N
- N dũng tip theo: dũng i+1 l hai s A
i
v B
i
.
D liu ra ghi vo file GIACONG.OUT cú cu trỳc:
- Dũng u tiờn l thi gian ớt nht thc hin N cụng vic
- Dũng th hai ln lt ghi s hiu ca N cụng vic thc hin theo lch ti u.
Vớ d:
GIACONG.INP GIACONG.OUT

5
3 3
4 3
6 2
5 7
6 3
26
1 4 2 5 3
gii bi toỏn trờn ta xột nh lớ sau:
* nh lớ Johnson
Phng ỏn v={v[1], v[2],,v[N]} ln lt thc hin cỏc chi tit v[1], v[2], ,
v[N] vi thi gian nh nht khi v ch khi Min{A
v[k]
, B
v[k+1]
}

{B
v[k]
, A
v[k+1]
}
1,2, , 1,k n =
trong ú A
v[k]
l thi gian thc hin v[k] v A
[k+1]
l thi gian thc hin
v[k+1] trờn mỏy A, cũn B
v[k]

v B
v[k+1
]
tng ng l thi gian thc hin v[k] v v[k+1]
trờn mỏy B.
8
Từ định lí trên có thể đưa ra thuật toán để giải bài toán trên, và chứng minh sự đúng
đắn của thuật toán sau:
* Thuật toán:
+ Bước 1: Chia các chi tiết thành hai nhóm:
Nhóm 1: gồm các chi tiết i mà
i i
A B≤
.
Nhóm 2: gồm các chi tiết i mà
i i
A B>
+ Bước 2: Xếp nhóm 1 theo chiều tăng của A
i
, xếp nhóm 2 theo chiều giảm của B
i
.
+ Bước 3: Nối nhóm 2 vào sau nhóm 1
* Chứng minh:
Nếu các chi tiết v[k] và v[k+1] cùng thuộc nhóm 1 thì
[ 1] [ 1] [ ]v k v k v k
B A A
+ +
≥ ≥
nên

Min{A
v[k]
,B
v[k+1]
}=A
v[k]
, mặt khác
[ ] [ 1] [ ] [ ]
, ,
v k v k v k v k
A A A B
+
≤ ≤
nên
[ ] [ ] [ 1]
{ , }
v k v k v k
A Min B A
+


Vậy Min {A
v[k]
,B
v[k+1]
}

Min {B
v[k]
, A

v[k+1]
}
Nếu chi tiết v[k] và v[k+1] cùng thuộc nhóm 2 thì
[ 1] [ ] [ ]v k v k v k
B B A
+
≤ <
nên Min {A
[k]
,
B
v[k]
}=B
v[k+1]
mặt khác
[ ] [ 1] [ 1] [ 1]
,
v k v k v k v k
B B A B
+ + +
≥ ≥
nên Min {B
v[k]
, A
v[k+1]
}

B
v[k+1]
. Vậy

Min {A
v[k]
, B
v[k]
}

Min {B
v[k]
, A
v[k+1]
}.
Nếu chi tiết v[k] thuộc nhóm 1 và chi tiết v[k+1] thuộc nhóm 2 thì:
Min {A
v[k]
, B
v[k+1]
}

Min {B
v[k]
, A
v[k+1]
} do A
v[k]


B
v[k]
và B
v[k+1]

< A
v[k+1]
.
có thể thực hiện cụ thể như sau: Xây dựng dần dãy kết quả từ hai đầu dồn vào giữa
(dùng mảng một chiều KQ và hai biến: đầu và cuối).
1. Gán giá trị ban đầu: đầu:=0; cuối:=N+1;
2. Thực hiện vòng lặp: trong khi đầu < cuối
a. Tìm chi tiết i chưa làm, có thời gian thực hiện nhỏ nhất.
b. Nếu i thuộc dãy A thì tăng giá trị biến đầu, gán KQ[đầu]:=i, nếu i thuộc dãy
B thì giảm giá trị biến cuối, gán KQ[cuối]:=i;
3. Đánh dấu đã làm chi tiết i.
Cµi ®Æt ch¬ng tr×nh:
const max=100;
fi='giacong.inp';
fo='giacong.out';
9
type m1=array[1 2,1 max] of integer;
mkq=array[1 max] of byte;
mdd=array[1 max] of boolean;
var a:m1;
kq:mkq;
dd:mdd;
n:byte;
g,f:text;
procedure readfile;
var i:byte;
begin
assign(f,fi);
reset(f);
readln(f,n);

for i:=1 to n do
readln(f,a[1,i],a[2,i]);
close(f);
end;
function chitietmin(var may:byte):byte;
var i,j,chitiet:byte;
lmin:integer;
begin
lmin:=maxint;
for i:=1 to 2 do
for j:=n downto 1 do
if not dd[j] then
if (a[i,j]<lmin) then
begin
may:=i;
chitiet:=j;
lmin:=a[i,j];
end;
chitietmin:=chitiet;
end;
procedure johnson;
var may,chitiet,dau,cuoi:byte;
begin
fillchar(dd,sizeof(dd),false);
dau:=0;
cuoi:=n+1;
repeat
chitiet:=chitietmin(may);
if may=1 then
begin

inc(dau);
kq[dau]:=chitiet;
dd[chitiet]:=true;
end
else
begin
dec(cuoi);
kq[cuoi]:=chitiet;
dd[chitiet]:=true;
end;
until dau=cuoi-1;
end;
procedure writefile;
var i:byte;
begin
10
for i:=1 to n do
if dd[i] then write(g,kq[i],' ');
end;
function max2(a,b:integer):integer;
begin
if a>b then max2:=a else max2:=b;
end;
procedure tinh;
var i,j:byte;
t1, t2:integer;
begin
t1:=0; t2:=0;
for i:=1 to n do
begin

t1:=t1+a[1,kq[i]];
t2:=max2(t1,t2)+a[2,kq[i]];
end;
writeln(g,t2);
end;
begin
assign(g,fo);
rewrite(g);
readfile;
johnson;
tinh;
writefile;
close(g);;
end.
b. Xếp lịch theo heuristic
- Về thuật giải heuristic
Nhiều bài toán xếp lịch có dữ liệu vào lớn không thể áp dụng thuật toán cho lời
giải tối ưu trong thời gian cho phép, trong những trường hợp đó chúng thường được
giải bằng phương pháp heuristic (một chiến thuật tìm kiếm hợp lí nào đó) để có lời
giải tương đối tối ưu thực hiện trong thời gian cho phép.
Bài toán xếp lịch thường có 2 yêu cầu sau:
- Yêu cầu loại 1: gọi là yêu cầu bắt buộc (nội dung cụ thể tùy theo từng bài toán) là
yêu cầu phải đạt được, nếu không đạt được thì coi như lời giải không được chấp nhận.
Ví dụ: yêu cầu phải xếp hết tất cả công việc, hoặc phải xếp xong trước một thời hạn
cho trước nào đó, hoặc mỗi công việc phải tuân thủ yêu cầu về thời gian bắt đầu, thời
gian kết thúc, thời gian hoàn thành của công việc ấy, hoặc công việc này phải được
thực hiện trước công việc kia,…
- Yêu cầu loại 2: Là yêu cầu có thể nhân nhượng phần nào đó (nội dung cụ thể tùy
theo từng bài toán).
11

Ví dụ: Cho phép tổng chi phí (hoặc tổng thời gian) thực hiện xong các công việc đạt
giá trị càng thấp càng tốt (hoặc xong càng sớm càng tốt). Chính nhờ sự nhân nhượng
này mà ta có thể đề xuất ra những cách sắp xếp một hoặc một nhóm công việc tiếp
theo dễ dàng hơn, đó chính là các “heuristic”.
Thuật giải heuristic có những đặc điểm sau:
- Cho lời giải gần tối ưu, thời gian thực hiện chương trình là hợp lí và có thể tổ
chức dữ liệu để thực hiện chương trình.
- Có thể thay đổi một vài heuristic trong quá trình giải để thích ứng với các bộ dữ
liệu khác nhau, kết hợp so sánh kết quả thực hiện các heuristic khác nhau và giữ lại
kết quả tốt hơn.
Ví dụ: Chia N việc cho M máy
Cho N công việc, công việc i hoàn thành trong thời gian t
i
. Các công việc được
thực hiện trên M máy (công suất như nhau, mỗi máy đều có thể thực hiện được công
việc bất kì trong N công việc) mỗi công việc được làm liên tục trên một máy cho đến
khi xong. Hãy tổ chức máy thực hiện đủ N công việc sao cho thời gian hoàn thành
càng nhỏ càng tốt.
Dữ liệu vào từ file CHIA.INP có cấu trúc:
- Dòng đầu là hai số N và M.
- Dòng tiếp theo là N số t
1
, t
2
, …, t
N
.
Dữ liệu ra ghi vào file CHIA.OUT có cấu trúc:
- Dòng đầu là thời gian hoàn thành N công việc.
- M dòng sau: dòng i+1 ghi số hiệu các công việc thực hiện trên máy i

12
Test:
CHIA.INP CHIA.OUT
6 3
2 5 8 1 5 1
8
3
2 1 4
5 6
Phân tích bài toán:
Với bài toán trên ta có thể thực hiện một heuristic sau:
1. Sắp xếp công việc theo thứ tự thời gian hoàn thành giảm dần.
2. Lấy M công việc đầu phân công cho M máy, mỗi máy một việc. Gọi maxT là
thời gian lớn nhất của M công việc này.
3. Vòng lặp 1
+ Vòng lặp 2: Chọn máy có thời gian đã làm nhỏ hơn maxT, xếp thêm một
công việc mới cho máy này (công việc lấy theo thứ tự đã sắp)
Lặp cho đến khi nếu thêm công việc mời thì không còn mày nào có tổng thời
gian dự kiến đã làm của nó nhỏ hơn maxT. {Hết vòng lặp 2}.
+ Tìm một máy có thời gian đã làm nhỏ nhất và phân công công việc tiếp theo
cho máy này, thay đổi lại giá trị của maxT.
Lặp cho đến khi hết việc {Hết vòng lặp 1}
Có 6 công việc thực hiện trên 3 máy công suất như nhau: Thời gian thực hiện các
công việc cho trong bảng sau:
cv1 cv2 cv3 cv4 cv5 cv6
2 5 8 1 5 1
Ta xếp theo thứ tự giảm dần của thời gian thực hiện
cv3 cv2 cv5 cv1 cv4 cv6
8 5 5 2 1 1
Bây giờ ta có thể phân công công việc cho các máy như sau:

Lần phân công thứ nhất:
Máy 1: cv3=8 đơn vị thời gian
Máy 2: cv2=5 đơn vị thời gian
Máy 3: cv5=5 đơn vị thời gian
13
Như vậy máy làm công việc có thời gian làm lớn nhất là Máy 1 với cv3=8 đơn vị thời
gian.
Lần phân công thứ hai: thì ta phải tìm máy nào có thời gian đã làm nhỏ nhất thì ta
nhận được kết quả là Máy 2 và Máy 3
Máy 2: thêm cv1+cv4  tổng cộng 8 đơn vị thời gian
Máy 3: thêm cv6  tổng cộng 6 đơn vị thời gian
Với cách phân công này ta thấy khi máy 1 làm xong cv3 thì máy 2 làm xong
cv5+cv1+cv4, máy 3 làm xong cv5+cv6
 Thời gian hoàn thành là 8 đơn vị thời gian
Chương trình cài đặt trên Pascal
uses crt;
const maxn=100;
fi='chia.inp';
fo='chia.out';
type m1=array[1 maxn] of longint;
var t,id:m1;
p:m1;
n,m,i:integer;
maxT:longint;
may:m1;
procedure readfile;
var i:integer;
f:text;
begin
assign(f,fi);

reset(f);
readln(f,n,m);
for i:=1 to n do begin read(f,t[i]);id[i]:=i; end;
close(f);
end;
procedure sort;
var i,j:integer;
tg:longint;
begin
for i:=1 to n -1 do
for j:=i+1 to n do
if t[i]<t[j] then
begin
tg:=t[i];
t[i]:=t[j];
t[j]:=tg;
tg:=id[i];
id[i]:=id[j];
id[j]:=tg;
end;
end;
function tim_may_min:integer;
var i:integer;
14
min:longint;
begin
min:=may[1];
for i:=2 to n do
if may[i]<min then
min:=may[i];

tim_may_min:=min;
end;
procedure writefile;
var g:text;
i,j:integer;
tong:longint;
begin
assign(g,fo);
rewrite(g);
writeln(g,maxT);
for j:=1 to m do
begin
tong:=0;
for i:=1 to n do {Viết ra số hiệu các công việc của máy j}
if p[i]=j then
begin
write(g,id[i],' ');
tong:=tong+t[i];
end;
writeln(g,' : ',tong); {lượng thời gian đã làm của máy j}
end;
close(g);
end;
procedure chia;
var i,j,lj:integer;
ok:boolean;
begin
fillchar(may,sizeof(may),0);
fillchar(p,sizeof(p),0);
i:=0;

maxT:=-maxint;
for j:=1 to m do {Phân m công việc đầu cho m máy}
begin
inc(i);
may[j]:=t[i];
p[i]:=j;
if t[i]>maxT then maxT:=t[i];{Thời gian lớn nhất hoàn thành công
việc}
end;
inc(i);
while i<=n do
begin
ok:=false;
for j:=1 to m do
begin
if i>n then break;
while (may[j]+t[i]<=maxT) do
begin
ok:=true;
may[j]:=may[j]+t[i];
p[i]:=j;{Giao công việc i cho máy j}
inc(i);
writeln(ok,j);
15
end;
end;
if not ok then
if i<=n then
begin
j:=tim_may_min;

may[j]:=may[j]+t[i];
p[i]:=j;
if may[j]>maxT then maxT:=may[j];
inc(i);
end;
end;
end;
begin
readfile;
sort;
chia;
writefile;
readln
end.
c. Phương pháp duyệt có đặt cận
Trong phương pháp này nếu chọn cận hợp lí với thời gian cho phép thì lời giải
thường đạt kết quả tương đối tối ưu. Vài lưu ý khi áp dụng:
- Thương sắp tăng hoặc giảm bộ dữ liệu theo một khóa giá trị nào đó trước khi
duyệt.
- Tìm cận là điều kiện cần để khi duyệt tránh đi vào các nhánh không dẫn tới đích.
Cận càng chặt chẽ càng nhanh chóng tới kết quả, nhưng cần phân tích kĩ để tránh
trường hợp bị mất nghiệm do đặt cận chặt chẽ quá mức hợp lí.
Ví dụ: Ta xét bài toán sau:
Cho N công việc, với mỗi công việc cho biết tiền công thu được khi thực hiện
công việc này, thời gian để hoàn thành, thời điểm cuối cùng phải kết thúc. Hãy xếp
lịch thực hiện sao cho thu được nhiều tiền công nhất.
Dữ liệu vào từ file văn bản XEPLICH.INP có cấu trúc:
- Dòng đầu là số nguyên dương N
- N dòng sau, dòng i+1 ghi 3 số tg, tdkt, gt tương ứng là thời gian cần thiết để hoàn
thành, thời điểm bắt buộc phải xong, giá trị tiền công của công việc i.

Dữ liệu ra ghi vào file XEPLICH.OUT có cấu trúc:
- Dòng đầu tiên là tổng giá trị tiền công.
16
- Các dòng tiếp theo mỗi dòng ghi 4 số: i, T
1
, T
2
, gt tương ứng là số hiệu, thời điểm
bắt đầu, thời điểm kết thúc, giá trị của công việc được chọn.
XEPLICH.INP XEPLICH.OUT
10
1 4 89
5 5 86
4 11 83
5 7 84
1 2 25
3 11 61
6 11 33
4 7 28
3 10 1
5 14 71
329
5 0 1 25
1 1 2 89
3 2 6 83
6 6 9 61
10 9 14 71
Nhận xét:
- Xếp tăng bộ dữ liệu theo khóa là thời điểm cuối cùng phải kết thúc công việc.
- Đặt cận:

Tổng tiền k công việc+Giá trị tối đa các công việc còn lại>Tổng tiền của phương án
tốt nhất đã có.
Ta thấy rằng khi thử chọn công việc k thì có thể dẫn tới sự kiện hàng loạt công
việc khác bị loại vì việc hoàn thành công việc k sẽ đẩy lùi thời điểm có thể bắt đầu
thực hiện các công việc này dẫn đến đẩy lùi thời điểm hoàn thành chúng và do đó có
thể làm cho một số công việc không xong trước thời hạn kết thúc đã qui định cho nó.
Ngược lại nếu không chọn công việc k nữa thì lại tạo điều kiện có thể thực hiện
những công việc bị loại do chọn k.
Ngoài ra, một cận thứ hai là:
Thời điểm bắt đầu công việc k + thời gian làm công việc k

Thời điểm kết thúc công
việc k.
Cài đặt chương trình sử dụng ngôn ngữ lập trình Pascal:
uses crt;
const max=1000;
fi='xeplich.inp';
fo='xeplich.out';
type pt=record
tg,tien,tdkt,ten:integer;
17
end;
var a,s,ls:array[1 max] of pt;
{a[i] chứa thời gian làm, tiền công, thời gian kết thúc, số hiệu công việc i,
s[i] chứa các công việc thử chọn, ls[i] chứa các công việc được chọn}
d:array[1 max] of integer;{Đánh dấu các công việc}
i,n,top,ltop:integer;
conlai:longint;{Tổng tiền các công việc còn lại}
tien,td,tong:integer;{Lần lượt là tổng tiền đã làm được, thời điểm cuối
của phương án đang tạo, tổng tiền của phương án tốt đã có}

procedure readfile;
var f:text;
i:integer;
begin
assign(f,fi);
reset(f);
readln(f,n);
conlai:=0;
for i:=1 to n do
begin
readln(f,a[i].tg,a[i].tdkt,a[i].tien);
conlai:=conlai+a[i].tien;
a[i].ten:=i;
end;
writeln(conlai);
close(f);
end;
procedure swap(var u,v:pt);
var tg:pt;
begin
tg:=u;
u:=v;
v:=tg;
end;
procedure sort;
var i,j:integer;
begin
for i:=1 to n-1 do
for j:=i+1 to n do
if a[i].tdkt>a[j].tdkt then swap(a[i],a[j]);

end;
procedure select(k:integer);
var j:integer;
begin
tien:=tien+a[k].tien;{Thêm tiền công của công việc k}
d[k]:=k; {đánh dấu đã chọn làm công việc k}
conlai:=conlai-a[k].tien;{Tổng giá trị các công việc còn lại}
inc(top);
s[top].tg:=td;
td:=td+a[k].tg; {Tính thời điểm làm xong công việc k}
s[top].ten:=a[k].ten;
s[top].tdkt:=td;
s[top].tien:=a[k].tien;
for j:=1 to n do {Điều kiện để loại công việc j do chọn công việc k}
if (d[j]=0)and(a[j].tdkt<td+a[j].tg) then
begin
d[j]:=k;
conlai:=conlai-a[j].tien;
end;
18
end;
procedure remove(k:integer);{Không chọn công việc k nữa}
var j:integer;
begin
for j:=1 to n do
if d[j]=k then
begin
d[j]:=0;
conlai:=conlai+a[j].tien;
end;

td:=td-a[k].tg;
tien:=tien-a[k].tien;
dec(top);
end;
procedure save;
begin
ls:=s;
tong:=tien;
ltop:=top;
end;
procedure duyet;
var k:integer;
begin
if tien>tong then save;
for k:=1 to n do
if (d[k]=0) and (td+a[k].tg<=a[k].tdkt) then
begin
select(k);
if tien+conlai>tong then duyet;
remove(k);
end;
end;
procedure writefile;
var f:text;
k:integer;
begin
assign(f,fo);
rewrite(f);
writeln(f,tong);
for k:=1 to ltop do

writeln(f,ls[k].ten,' ',ls[k].tg,' ',ls[k].tdkt,' ',ls[k].tien);
close(f);
end;
begin
fillchar(d,sizeof(d),0);
td:=0;
readfile;
sort;
tong:=-maxint;
duyet;
writefile;
readln
end.
19
d. Ph¬ng ph¸p quy ho¹ch ®éng
Có những bài toán mà quyết định ở bước thứ i phụ thuộc vào quyết định ở các
bước trước đó. Nếu ta xác định được hệ thức diễn đạt mối tương quan của quyết đinh
ở bước thứ i với quyết định ở bước trước đó thì ta có thể triển khai chương trình theo
hệ thức đã tìm được.
Khi đó việc giải các bài toán có kích thước lớn sẽ được chuyển về việc giải các bài
toán cùng kiểu có kích thước nhỏ hơn.Các thuật toán này thường được thể hiện bằng
các chương trình con đệ quy.Tuy nhiên, với kỹ thuật chia để trị thì nhiều bài toán con
phải tính lại nhiều lần.
Vậy ý tưởng cơ bản của quy hoạch động đó là: Tránh tính toán lại các bài toán con
nhiều lần, mà lưu giữ kết quả đã tìm kiếm được vào một bảng làm giá trị giả thiết cho
việc tìm kết quả của trường hợp sau. Chúng ta sẽ lấp đầy dần các giá trị của bảng này
bởi các kết quả của những trường hợp trước đã được giải. Kết quả cuối cùng chính là
kết quả của bài toán cần giải.Cụ thể như sau:
Phương pháp quy hoạch động cùng nguyên lý tối ưu được nhà toán học Mỹ
R.Bellman đề xuất vào những năm 50 của thế kỷ 20. Phương pháp này đã được áp

dụng để giải hàng loạt bài toán thực tế trong các quá trình kỹ thuật của công nghệ, tổ
chức sản xuất, kế hoạch hoá kinh tế… Tuy nhiên cần lưu ý rằng có một số bài toán
giải bằng quy hoạch động tỏ ra không thích hợp.
Trong thực tế, ta thường gặp một số bài toán tối ưu loại sau: Có một đại lượng g
hình thành trong một quá trình gồm nhiều giai đoạn và ta chỉ quan tâm đến kết quả
cuối cùng là giá trị của g phải lớn nhất hoặc nhỏ nhất, ta gọi chung là giá trị tối ưu của
g. Giá trị của g phụ thuộc vào những đại lượng xuất hiện trong bài toán mà mỗi bộ giá
trị của chúng được gọi là một trạng thái của hệ thống và cũng phụ thuộc vào cách
thức đạt được giá trị g trong từng giai đoạn mà mỗi cách thức được gọi là điều khiển.
Đại lượng g thường được gọi là hàm mục tiêu và quá trình đạt được giá trị tối ưu của
g được gọi là quá trình điều khiển tối ưu.
20
Bellman phát biểu nguyên lý tối ưu (cũng gọi là nguyên lý Bellman) mà ý tưởng
cơ bản là như sau: "Với mỗi quá trình điều khiển tối ưu, đối với trạng thái ban đầu A
0
,
với mọi trạng thái A trong quá trình đó, phần quá trình kể từ trạng thái A xem như
trạng thái bắt đầu cũng là tối ưu".
Chú ý rằng nguyên lý này được thừa nhận mà không chứng minh.
Phương pháp tìm điều khiển tối ưu theo nguyên lý Bellman thường được gọi là
quy hoạch động. Thuật ngữ này nói lên thực chất của quá trình điều khiển là động:Có
thể trong số bước đầu tiên lựa chọn điều khiển tối ưu dường như không tốt nhưng
chung cả quá trình lại là tốt nhất.
Ta có thể giải thích ý này qua bài toán sau: Cho một dãy số nguyên A
1
,A
2
,….A
n
.

Hãy tìm cách xóa đi một số ít nhất số hạng để dãy còn lại là dãy đơn điệu hay nói
cách khác hãy chọn một số nhiều nhất các số sao cho dãy B gồm các số hạng đó theo
trình tự xuất hiện trong dãy A là đơn điệu.
Quá trình chọn dãy B được điều khiển qua N giai đoạn để đạt được mục tiêu là
số lượng số hạng của dãy B là nhiều nhất, điều khiển ở giai đoạn i thể hiện việc chọn
hay không chọn dãy Ai vào dãy B.
Giả sử dãy đã cho là 1 8 10 2 4 6 7. Nếu ta chọn lần lượt 1, 8, 10 thì chỉ chọn
được 3 số hạng nhưng nếu bỏ qua 8 và 10 thì ta chọn được 5 số hạng 1, 2, 4, 6, 7.
Khi giải một số bài toán bằng cách "chia để trị", chuyển việc giải bài toán kích
thước lớn về việc giải nhiều bài toán cùng kiểu có kích thước nhỏ hơn thì các thuật
toán này thường được thể hiện bằng các chương trình con đệ quy. Khi đó, trên thực tế,
nhiều kết quả trung gian phải được tính nhiều lần.
Nói các khác phương pháp quy hoạch động đã thể hiện sức mạnh của nguyên
lý chia để trị đến cao độ.
Trong một số trường hợp, khi giải một bài toán A, trước hết ta tìm họ bài toán
A(p) phụ thuộc tham số p (có thể là một véc tơ) mà A(p
0
)=A với p
0
là trạng thái ban
đầu của bài toán A. Sau đó tìm cách giải họ bài toán A(p) với tham số p bằng cách áp
21
dụng nguyên lý tối ưu của Bellman. Cuối cùng cho p=p0 sẽ nhận được kết quả của
bài toán A ban đầu.
Các thuật ngữ dùng trong quy hoạch động:
Bài toán giải theo phương pháp quy hoạch động gọi là bài toán quy hoạch động.
Công thức phối hợp nghiệm của các bài toán con để có nghiệm của bài toán lớn gọi là
công thức truy hồi của quy hoạch động. Tập các bài toán có ngay lời giải để từ đó giải
quyết các bài toán lớn hơn gọi là cơ sở quy hoạch động. Không gian lưu trữ lời giải
các bài toán con để tìm cách phối hợp chúng gọi là bảng phương án của quy hoạch

động.
Ví dụ:
Cho thuê máy tính
Tại thời điểm 0, ông chủ cho thuê máy tính nhận được đơn đặt hàng thuê sử dụng
máy của N khách hàng. Các khách hàng được đánh số từ 1 đến N. Khách hàng i cần
sử dụng máy từ thời điểm d
i
đến thời điểm c
i
(d
i
và c
i
là các số nguyên và 0<d
i
<c
i

1000000) và sẽ trả tiền sử dụng máy là p
i
(p
i
nguyên, 0<p
i

1000000). Hãy xác định
xem ông chủ cần nhận phục vụ những khách hàng nào sao cho khoảng thời gian sử
dụng máy tính của hai khách hàng được nhận phục vụ bất kì không giao nhau và tổng
tiền thu được từ phục vụ là lớn nhất.
Dữ liệu vào từ file văn bản MAYTINH.INP có cấu trúc

- Dòng đầu ghi số N (0<N

1000)
- Dòng thứ i trong N dòng tiếp theo ghi ba số d
i
, c
i
, p
i
cách nhau bởi dấu trống, i=1,
2, …, N
Dữ liệu ra ghi vào file MAYTINH.OUT có cấu trúc:
- Dòng đầu tiên ghi hai số nguyên dương theo thứ tự là số lượng khách hàng nhận
được phục vụ và tổng tiền thu được.
- Dòng tiếp theo ghi chỉ số của khách hàng được phục vụ.
Test:
MAYTINH.INP MAYTINH.OUT
22
3
150 500 150
1 200 100
400 800 80
2 180
3 2
MAYTINH.INP MAYTINH.OUT
4
400 821 800
200 513 500
100 325 200
600 900 600

2 1100
4 2
Ý tưởng:
Sắp xếp các khoảng thời gian thuê sử dụng máy theo điểm đầu của những khoảng
này.
Gọi NH[i] là số tiền tối ưu thu được khi xét xong các công việc từ 1 đến i (theo thứ tự
đã sắp).
Khởi tạo NH[i]=0;
Sau đó sửa nhãn từ NH[1] đến NH[N] như sau:
Gán NH[i]=p
i
. Để sửa nhãn NH[i], tìm công việc j có nhãn lớn nhất trong các công
việc từ (i-1) đến 1 mà thời điểm hoàn thành của công việc j sớm hơn thời điểm bắt
đầu của công việc i, sử dụng công thức:
NH[i]=Max{NH[i],NH[i]+p
i
,

j: c
j
<d
i
}
Cài đặt chương trình
const fi='maytinh.inp';
fo='maytinh.out';
type khach=record d,c,p:longint;
tt:integer;
end;
var a:array[0 1000] of khach;

nh:array[0 1000] of longint;
t:array[1 1000] of integer;
n,i:integer;
procedure readfile;
var i:integer;
f:text;
23
begin
assign(f,fi);
reset(f);
a[0].d:=0;a[0].c:=0;a[0].p:=0;
readln(f,n);
for i:=1 to n do
with a[i] do
readln(f,d,c,p);
for i:=1 to n do a[i].tt:=i;
for i:=1 to n do write(a[i].d,' ');
end;
procedure QuickSort;
procedure Sort(l,r:integer);
var
i,j:integer;
x:longint;
y:khach;
begin
i:=l;j:=r;x:=a[(l+r) div 2].d;
repeat
while a[i].d<x do inc(i);
while x<a[j].d do dec(j);
if i<=j then

begin
y:=a[i];a[i]:=a[j];a[j]:=y;
inc(i);dec(j);
end;
until i>j;
if l<j then sort(l,j);
if i<r then sort(i,r);
end;
begin
sort(1,n);
end;
procedure thuchien;
var i,j:integer;
begin
fillchar(t,sizeof(t),0);
quicksort;
nh[0]:=0;
for i:=1 to n do
begin
nh[i]:=a[i].p;
for j:=i-1 downto 0 do
if a[j].c<a[i].d then
begin
if nh[i]<nh[j]+a[i].p then
begin
nh[i]:=nh[j]+a[i].p;
t[i]:=j;
end;
end;
end;

end;
procedure writefile;
var max:longint;
i,li,sl:integer;
f:text;
begin
24
assign(f,fo);
rewrite(f);
max:=-maxint;
for i:=1 to n do
if max<nh[i] then
begin
max:=nh[i];
li:=i;
end;
sl:=0;
i:=li;
repeat
inc(sl);
i:=t[i];
until i=0;
writeln(f,sl,' ',max:0);
i:=li;
repeat
write(f,a[i].tt,' ');
i:=t[i];
until i=0;
close(f);
end;

begin
readfile;
thuchien;
writefile;
for i:=1 to n do write(a[i].d,' ');
readln;
end.
e. Phương pháp làm mịn dần kết quả
Xét bài toán sau:
Bài 1.
Một nhà máy chạy một dây chuyền sản xuất. Có 2 nguyên công (2 giai đoạn độc
lập nối tiếp nhau) cần phải thực hiện đối với mỗi một sản phẩm theo trình tự sau: đầu
tiên thực hiện nguyên công A, sau đó thực hiện nguyên công B. Có một số máy để
thực hiện từng nguyên công. Hình 1.1 chỉ ra cách tổ chức dây chuyền sản xuất hoạt
động như sau:
Máy kiểu A lấy sản phẩm từ băng chuyền vào, thực hiện nguyên công A và đặt sản
phẩm vào băng chuyền trung gian. Máy kiểu B lấy sản phẩm từ băng chuyền trung
gian thực hiện nguyên công B và đặt sản phẩm vào băng chuyền ra. Mọi máy đều có
thể làm việc song song và độc lập với nhau, mỗi máy làm việc với thời gian xử lí cho
trước. Thời gian xử lí là số đơn vị thời gian cần thiết để thực hiện nguyên công bao
25

×