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

Tiểu luận Thuật toán tham lam GREEDY

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 (305.28 KB, 26 trang )

SỞ GD&ĐT THÁI NGUYÊN
TRƯỜNG THPT CHUYÊN THÁI NGUYÊN

BÀI TIỂU LUẬN MÔN TIN HỌC
Tên đề tài:

THUẬT TOÁN THAM LAM
Nhóm thực hiện:
1. Lưu Văn Đức
2. Tống Như Mai
3. Nông Hoàng Hiệp
Học sinh lớp: Tin 11 - K26


GREEDY ALGORITHMS

Thái Nguyên, tháng 12/2015

LỜI NÓI ĐẦU
Giải thuật cho những bài toán tối ưu thường đi qua một số bước, với một tập hợp
các chọn lựa tại mỗi bước. Với nhiều bài toán tối ưu hoá có thể sử dụng phương pháp
đơn giản và hiệu quả hơn phương pháp qui hoạch động.
Tham Lam hiểu một cách dân gian là: trong một mâm có nhiều món ăn, món nào
ngon nhất ta sẽ ăn trước và ăn cho hết món đó thì chuyển sang món ngon thứ hai, lại
ăn hết món ngon thứ hai này và chuyển sang món ngon thứ ba… Phương pháp tham
lam luôn chọn phương án tốt nhất vào thời điểm hiện tại. Nó chọn tối ưu cục bộ với hy
vọng rằng lựa chọn này sẽ dẫn đến một kết quả tối ưu toàn cục. Phương pháp tham
lam không phải lúc nào cũng mang lại các kết quả tối ưu, nhưng có nhiều bài toán nó
có thể giải quyết được một cách tối ưu
Tham lam là một trong những phương pháp phổ biến nhất để thiết kế giải thuật.
Rất nhiều thuật toán nổi tiếng được thiết kế dựa trên tư tưởng của tham lam, ví dụ như


thuật toán tìm đường đi ngắn nhất của Dijkstra, thuật toán cây khung nhỏ nhất của
Kruskal, v.v. Trong bài tiểu luận này, chúng ta sẽ tìm hiểu phương pháp thiết kế giải
thuật này và ứng dụng của nó.
Mặc dù tiểu luận đã tham khảo và chọn lọc khá kỹ về nội dung, tuy nhiên, vì thời
gian thực hiện có hạn, đồng thời có nhiều hạn chế về ngôn ngữ, nhóm chúng em
không thể tránh khỏi được những sai sót. Rất mong được phản hồi từ thầy để chúng
em hoàn chỉnh được bài tiểu luận một cách chính xác nhất.
Thái Nguyên, tháng 12/2015
Nhóm học sinh chuyên Tin K26 thực hiện

2


GREEDY ALGORITHMS

MỤC LỤC
LỜI NÓI ĐẦU_______________________________________2
1. ĐẶT VẤN ĐỀ_____________________________________4
2. CƠ SỞ LÝ THUYẾT VÀ BÀI TẬP___________________4
2.1. KHÁI NIỆM_________________________________4
2.2. PHƯƠNG PHÁP CÀI ĐẶT_____________________6
2.3. BÀI TẬP ĐIỂN HÌNH_________________________8
3. ỨNG DỤNG_____________________________________16
3.1. THUẬT TOÁN DIJKSTRA ___________________16
3.2. THUẬT GIẢI HEURISTIC___________________20
KẾT LUẬN________________________________________25
TÀI LIỆU THAM KHẢO____________________________26

3



GREEDY ALGORITHMS
1. ĐẶT VẤN ĐỀ
Trong mọi khía cạnh của cuộc sống hàng ngày, công việc và nghiên cứu, con
người phải luôn đối mặt với các vấn đề (problem). Có ai đó còn phát biểu: sống là một
quá trình cố gắng để giải quyết những bài toán, những vấn đề. Trong số những vấn đề
đó, có những vấn đề chỉ đơn giản là cần có giải pháp nhưng cũng có những vấn đề
khắt khe hơn đòi hỏi không chỉ là giải pháp (Solution) mà giải pháp đó phải là giải
pháp tốt nhất (best solution) (nhanh nhất, đơn giản nhất, hiệu quả nhất ...) đó được gọi
là những vấn đề cần tối ưu (Optimization Problem).
Ý tưởng Greedy (tham lam) là một trong những hướng suy nghĩ được sử dụng
trong tình huống này. Các thuật toán sử dụng Greedy như nền tảng logic để tiếp cận,
giải quyết vấn đề được gọi là những Greedy Algorithms. Các thuật toán ứng dụng
Greedy thường diễn ra, hoạt động qua nhiều chặng (phase) về không gian hay thời
gian, và tại mỗi chặng chúng ta thường không biết thông tin toàn bộ dữ liệu của cả quá
trình mà chỉ biết tình trạng hiện tại và thông tin cho bước đi kế tiếp. Ý tưởng chính của
Greedy là chúng ta không cần quan tâm tới dữ liệu tổng thể mà chỉ từ những dữ liệu
tại từng chặng chọn ra giải pháp tối ưu tại mỗi chặng đó với hy vọng là tổng hợp
những giải pháp tối ưu cục bộ sẽ mang lại một giải pháp tối ưu cho tổng thể. Từ ý
tưởng đó cho thấy, kết quả có được từ Greedy Algorithm chỉ mang tính tương đối,
thông thường giải pháp cuối cùng không phải là tối ưu mà chỉ là gần với tối ưu. Tuy
nhiên với những điều kiện, thông tin thường không mấy rỏ ràng của đầu vào, và cách
tiếp cận vấn đề khá trong sáng của mình, Greedy thường đưa ra giải pháp tương đối tốt
trong giới hạn có thể chấp nhận và đặc biệt có tốc độ nhanh.
2. CƠ SỞ LÝ THUYẾT VÀ BÀI TẬP
2.1. Khái niệm:
Giải thuật tham lam (tiếng Anh: Greedy algorithm) là một thuật toán giải quyết
một bài toán theo kiểu metaheuristic để tìm kiếm lựa chọn tối ưu địa phương ở mỗi
bước đi với hy vọng tìm được tối ưu toàn cục. Chẳng hạn áp dụng giải thuật tham lam
với bài toán hành trình của người bán hàng ta có giải thuật sau: “Ở mỗi bước hãy đi

đến thành phố gần thành phố hiện tại nhất“.

4


GREEDY ALGORITHMS
Lựa chọn giải pháp nào được cho là tốt nhất ở thời điểm hiện tại và sau đó giải
bài toán con nảy sinh từ việc thực hiện lựa chọn vừa rồi. Lựa chọn của thuật toán tham
lam có thể phụ thuộc vào các lựa chọn trước đó. Nhưng nó không thể phụ thuộc vào
một lựa chọn nào trong tương lai hay phụ thuộc vào lời giải của các bài toán con.
Giải thuật tham lam quyết định sớm và thay đổi đường đi thuật toán theo quyết
định đó, và không bao giờ xét lại các quyết định cũ để thay đổi, vì vậy có thể là một
thuật toán không tối ưu. Tuy nhiên, cần nhấn mạnh rằng, trong một số bài toán, nếu
xây dựng được hàm xác định sự tốt nhất của những lựa chọn thích hợp thì có thể cho
ra nghiệm tối ưu. Dĩ nhiên, ta phải chứng minh rằng một lựa chọn tham lam tại mỗi
bước đưa ra một giải pháp tối ưu toàn cục, và đây là nơi cần có sự thông minh. Trong
nhiều bài toán khác, thuật toán này chỉ tìm ra nghiệm gần đúng với nghiệm tối ưu.
VD: Với mục tiêu đạt được tổng lớn nhất dưới đây, tại mỗi bước, thuật toán tham
lam sẽ chỉ tối ưu trước mắt là chọn đường đi có số lớn hơn, nên sẽ chọn số 3 thay vì số
12. Khiến cho kết quả không phải là giải pháp tốt nhất, có chứa số 99

Nói chung, giải thuật tham lam có năm thành phần:
1.

Một tập hợp các ứng viên (candidate), để từ đó tạo ra lời giải

2.

Một hàm lựa chọn, để theo đó lựa chọn ứng viên tốt nhất để bổ sung vào lời


3.

Một hàm khả thi (feasibility), dùng để quyết định nếu một ứng viên có thể

giải
được dùng để xây dựng lời giải
4.

Một hàm mục tiêu, ấn định giá trị của lời giải hoặc một lời giải chưa hoàn

chỉnh
5.

Một hàm đánh giá, chỉ ra khi nào ta tìm ra một lời giải hoàn chỉnh.

5


GREEDY ALGORITHMS
Có hai thành phần quyết định nhất tới quyết định tham lam:
• Tính chất lựa chọn tham lam
Chúng ta có thể lựa chọn giải pháp nào được cho là tốt nhất ở thời điểm hiện tại
và sau đó giải bài toán con nảy sinh từ việc thực hiện lựa chọn vừa rồi. Lựa chọn của
thuật toán tham lam có thể phụ thuộc vào các lựa chọn trước đó. Nhưng nó không thể
phụ thuộc vào một lựa chọn nào trong tương lai hay phụ thuộc vào lời giải của các bài
toán con. Thuật toán tiến triển theo kiểu thực hiện các chọn lựa theo một vòng lặp,
cùng lúc đó thu nhỏ bài toán đã cho về một bài toán con nhỏ hơn. Đấy là khác biệt
giữa thuật toán này và giải thuật Quy Hoạnh Động. Giải thuật quy hoạch động duyệt
hết và luôn đảm bảo tìm thấy lời giải. Tại mỗi bước của thuật toán, quy hoạch động
đưa ra quyết định dựa trên các quyết định của bước trước, và có thể xét lại đường đi

của bước trước hướng tới lời giải. Giải thuật tham lam quyết định sớm và thay đổi
đường đi thuật toán theo quyết định đó, và không bao giờ xét lại các quyết định cũ.
Đối với một số bài toán, đây có thể là một thuật toán không chính xác.
• Cấu trúc con tối ưu
Một bài toán có cấu trúc con tối ưu nếu giải pháp tối ưu cho bài toán này chứa trong
nó các giải pháp tối ưu cho các bài toán con. Thuộc tính này là điểm để quyết định ta
có thể giải quyết bài toán bằng phương pháp tham lam được hay không.
2.2. Phương pháp cài đặt:
Điều kiện để bài toán có thể giải bằng phương phám Tham lam đó là chúng thuộc
lớp các bài toán tối ưu tổ hợp là một trường hợp riêng của bài toán tối ưu .
Các bài toán tối ưu là các bài toán có dạng tổng quát như sau:
 Hàm f(X) được gọi là hàm mục tiêu , xác định trên một tập hữu hạn các
phần tử D.
 Mỗi phần tử X thuộc D có dạng X=(x1,x2,….,xn) được gọi là một phương
án.
 Tìm một phương án X0 thuộc D sao cho f(X) đạt max hoặc min trên D.
Thì X0 được gọi là phương án tối ưu.
 Tập D được gọi là tập các phương án của bài toán.
Ví dụ như các dạng bài toán sau:

6


GREEDY ALGORITHMS
 Một tập các đối tượng
 Một dãy các đối tượng đã lựa chọn
 Một hàm để xem một tập các đối tượng có lập thành một giải pháp hay
không (không nhất thiết tối ưu)
 Một hàm để xem một tập đối tượng có là tiềm năng hay không
 Một hàm để lựa chọn ứng viên có triển vọng nhất

 Một hàm đích cho một giá trị của một giải pháp (để tối ưu hóa)

Giả sử nghiệm của bài toán có thể biểu diễn dưới dạng một vector (x1, x2,..., xn),
mỗi thành phần xi (i=1,2,...,n) được chọn ra từ tập Si. Mỗi nghiệm của bài toán X=(x1,
x2,..., xn), được xác định "độ tốt" bằng một hàm ƒ(X) và mục tiêu cần tìm nghiệm có
giá trị ƒ(X) càng lớn càng tốt (hoặc càng nhỏ càng tốt).
Tư tưởng của phương pháp tham lam như sau: Ta xây dựng vector nghiệm X dần
từng bước, bắt đầu từ vector không ( ). Giả sử đã xây dựng được (k-1) thành phần (x1,
x2,..., xk-1) của nghiệm và khi mở rộng nghiệm ta sẽ chọn xk "tốt nhất" trong các ứng
cử viên trong tập Sk để được (x1, x2,..., xk). Việc lựa chọn như vậy được thực hiện bởi
một hàm chọn. Cứ tiếp tục như vậy cho đến khi xây dưng xong hết tất cả các thành
phần của nghiệm.
Lược đồ tổng quát của phương pháp tham ăn:
procedure Greedy;
begin
X:= φ ;
i:=0;
while (Chua_xay_dung_het_cac_thanh_phan_cua_nghiem)
do
begin
i:=i+1;
<Xac_đỉnh_Si>;
x ← Select(Si);
end;
end;
Trong lược đồ tổng quát trên, Select là hàm chọn, để chọn ra từ tập các ứng cử viên Si
một ứng cử viên được xem là tốt nhất, nhiều hứa hẹn nhất.
2.3. Bài tập điển hình:



Bài toán người du lịch

7


GREEDY ALGORITHMS
Bài toán: Cho n thành phố được đánh số từ 1 đến n và các tuyến đường giao thông hai
chiều giữa chúng, mạng lưới giao thông này được cho bởi mảng C[1..n,1..n], ở
đây Cij=Cji là chi phí đi đoạn đường trực tiếp từ thành phố i đến thành phố j.
Một người du lịch xuất phát từ thành phố 1, muốn đi thăm tất cả các thành phố
còn lại mỗi thành phố đúng 1 lần và cuối cùng quay lại thành phố 1. Hãy chỉ ra
cho người đó hành trình với chi phí ít nhất. Bài toán được gọi là bài toán người
du lịch hay bài toán người chào hàng (Travelling Salesman Problem - TSP)
Dữ liệu vào trong file “TSP.INP” có dạng:
-Dòng đầu chứa số n(1-n dòng tiếp theo, mỗi dòng n số mô tả mảng C
Output: file “TSP.OUT” có dạng:
-Dòng đầu là chi phí ít nhất
-Dòng thứ hai mô tả hành trình
TEST 1:
TSP.INP
4
0
20
35
42

20
0
34

30

35
34
0
12

42
30
12
0

TSP.OUT
97
1->2->4->3->l

Hình minh họa

TEST 2:
TSP.INP
4

0
20
35
10

20
0
90

50

35
60
0
12

10
50
12
0

TSP.OUT
117
1->2->4->3->l

Hình minh họa

Hướng dẫn: Có nhiều thuật toán tham lam cho bài này, một thuật toán với ý tưởng
đơn giản như sau: Xuất phát từ thành phố 1, tại mỗi bước ta sẽ chọn thành phố tiếp

8


GREEDY ALGORITHMS
theo là thành phố chưa đến thăm mà chi phí từ thành phố hiện tại đến thành phố đó là
nhỏ nhất, cụ thể:
+ Hành trình cần tìm có dạng (x1 = 1, x2, ..., xn, xn+1 = 1), trong đó dãy (x1,
x2, ..., xn) lập thành một hoán vị của (1, 2, ..., n).
+ Ta xây dựng nghiệm từng bước, bắt đầu từ x1=1, chọn x2 là thành phố gần

x1 nhất, sau đó chọn x3 là thành phố gần x2 nhất (x3 khác x1)... Tổng quát:
chọn xi là thành phố chưa đi qua mà gần xi-1 nhất.(2 ≤ i ≤ n).
Code:
program TSP;
const
oo
fi
fo

MAX = 100;
=1000000;
=’TSP.INP’;
=’TSP.OUT’;

var c
:array[1..MAX,1..MAX] of longint;
x
:array[1..MAX]of longint;
d
:array[1..MAX]of longint;
n
:longint;
sum :longint;
procedure input;
var f :text;
i,j,k :longint;
begin
assign(f,fi); reset(f);
read(f,n);
for i:=1 to n do

for j:=1 to n do read(f,C[i,j]);
close(f);
end;
procedure output;
var f :text;
i :longint;
begin
assign(f,fo); rewrite(f);
writeln(f,sum);
for i:=1 to n do write(f,x[i],'->');
write(f,x[1]);
close(f);
end;
procedure Greedy;
var i,j,xi :longint;

9


GREEDY ALGORITHMS
best :longint;
begin
X[1]:=1;
d[1]:=1;
i:=1;
while iinc(i);
/ / chon ung cu vien tot nhat
best:=oo;
for j:=1 to n do

if (d[j]=0) and (c[x[i-1],j]begin
best:=c[x[i-1],j];
xi:=j;
end;
x [ i ] : = x i ; //ghi nhan nghiem thu i
d[xi]:=1;
sum:=sum+c[x[i-1],x[i]];
end;
sum:=sum+c[x[n],x[1]];
end;
BEGIN
input;
Greedy;
output;
END.
Nhận xét:
 TEST 1: Xuất phát từ thành phố 1, phương pháp tham lam xây dựng
được hành trình 1 → 2 → 4 → 3→ 1 với chi phí 97, đây là phương án
tối ưu.
 TEST 2: Xuất phát từ thành phố 1, phương pháp tham lam xây dựng
được hành trình 1 → 4 → 3 → 2→ 1 với chi phí 132, nhưng kết quả tối
ưu là 117.

10


GREEDY ALGORITHMS



Bài toán máy rút tiền tự động ATM

Bài toán: Một máy ATM hiện có n (n < 20) tờ tiền có giá t 1 , t 2 ,..., tn. Hãy tìm cách
trả ít tờ nhất với số tiền đúng bằng S.
Input: từ file “ATM.INP” có dạng:
-Dòng đầu là 2 số n và s
-Dòng thứ 2 gồm n số t 1 ,t 2 ,..,t n
Output: file “ATM.OUT” có dạng: Nếu có thể trả tiền đúng bằng S thì đưa ra số tờ ít
nhất cần trả và đưa ra cách trả, nếu không ghi -1.
TEST
ATM.INP

ATM.OUT

10 390
200 10 20 20 50 50 50 50 100 100

5
20 20 50 100 200

Hướng dẫn: Thuật toán với ý tưởng tham lam đơn giản, hàm chọn như sau: Tại mỗi
bước ta sẽ chọn tờ tiền lớn nhất còn lại không vượt quá lượng tiền còn phải trả, cụ thể:
+ Sắp xếp các tờ tiền giảm dần theo giá trị.
+ Lần lượt xét các tờ tiền từ giá trị lớn đến giá trị nhỏ, nếu vẫn còn chưa lấy
đủ s và tờ tiền đang xét có giá trị nhỏ hơn hoặc bằng s thì lấy luôn tờ tiền
đó.
Code:
const MAX = 100;
fi =’ATM.INP’;
fo =’ATM.OUT’;

type
vector =array[1..MAX]of longint;
var t
:array[1..MAX]of longint;
x
:vector;
c,n,s:longint;
procedure input;
var f :text;
i :longint;
begin
assign(f,fi); reset(f); readln(f,n, s);
for i:=1 to n do read(f,t[i]);
close(f);

11


GREEDY ALGORITHMS
end;
procedure greedy;
var i,j :longint;
tmp :longint;
begin
fillchar(x,sizeof(x),0);
// Sep cac to theo gia tri giam dan
for i:=1 to n-1 do
for j:=i+1 to n do
if t[i]begin

tmp:=t[i];
t[i]:=t[j];
t[j]:=tmp;
end;
c:=0;
for i:=1 to n do if s>=t [i] then
begin
inc (c); //So luong to lay
x [ i ] : = 1 ; //To duoc lay
s:=s-t [i];
end;
end;
procedure printResult;
var i :longint;
f :text;
begin
assign(f,fo); rewrite(f);
if s=0 then
begin
writeln(f,c);
for i:=1 to n do
if x[i]=1 then write(f,t[i],’ ’);
end
else
write (f,'-1');//Khong lay duoc du S
close(f);
end;
BEGIN
input;
greedy;

PrintResult;
END.

12


GREEDY ALGORITHMS
Nhận xét: Khi dùng phương phám tham lam với các TEST thử nghiệm như sau:
TEST
1
2
3

Dữ liệu vào

Kết quả

10 390
200 10 20 20 50 50 50 50 100 100
11 100
50 20 20 20 20 20 2 2 2 2 2
6 100
50 20 20 20 20 20

5
200 100 50 20 20
8
50 20 20 2 2 2 2 2
-1


 TEST 1: Thuật toán tham lam cho ra kết quả tối ưu
 TEST 2: Thuật toán tham lam không cho nghiệm tối ưu
 TEST 3: Thuật toán tham lam không tìm ra nghiệm mặc dù có nghiệm



Bài toán lập lịch giảm trễ hạn:

Bài toán: Có n công việc đánh số từ 1 đến n và có một máy để thực hiện, biết:
-

pi là thời gian cần thiết để hoàn thành công việc i.

-

d i là thời hạn hoàn thành công việc i.

Máy bắt đầu hoạt động từ thời điểm 0. Mỗi công việc cần được thực hiện liên tục từ
lúc bắt đầu cho tới khi kết thúc, không được phép ngắt quãng. Giả sử ci là thời điểm
hoàn thành công việc i. Khi đó, nếu c i > d i ta nói công việc i bị hoàn thành trễ hạn,
còn nếu c i > d i thì ta nói công việc i được hoàn thành đúng hạn.
Yêu cầu: Tìm trình tự thực hiện các công việc sao cho số công việc hoàn thành
trễ hạn là ít nhất (hay số công việc hoàn thành đúng hạn là nhiều nhất).
Input: trong file “JS.INP” có dạng:
- Dòng đầu là số n (n < 100) là số công việc
- Dòng thứ hai gồm n số là thời gian thực hiện các công việc
- Dòng thứ ba gồm n số là thời hạn hoàn thành các công việc
Output: file “JS.OUT” có dạng: gồm một dòng là trình tự thực hiện các công việc.
TEST
JS.INP


JS.OUT
5 1 3 4 2

5
6 3 5 7 2
8 4 15 20 3
Hướng dẫn: Ta sẽ chỉ quan tâm đến việc xếp lịch cho các công việc hoàn thành đúng

13


GREEDY ALGORITHMS
hạn, còn các công việc bị trễ hạn có thể thực hiện theo trình tự bất kỳ.
+ Giả sử ]s là tập gồm k công việc (mà cả k công việc này đều có thể thực
hiện đúng hạn) và σ = ( i 1 , i 2 , . . , i k ) là một hoán vị của các công việc
trong J s sao cho d ≤ d ≤ ... ≤ d thì thứ tự ơ là thứ tự để hoàn thành đúng
i1 i 2
ik
hạn được cả k công việc.
Ví dụ: J s gồm 4 công việc 1, 3, 4, 5 (4 công việc này đều có thể thực hiện đúng
hạn), ta có thứ tự thực hiện σ = (5,1, 3,4) vì d5 = 3 ≤ d 1 = 8 ≤ d3 = 15 ≤ d4 = 20
để cả 4 công việc đều thực hiện đúng hạn.
Sử dụng chiến lược tham ăn, ta xây dựng tập công việc J s theo từng bước, ban
đầu J s = φ . Hàm chọn được xây dựng như sau: tại mỗi bước ta sẽ chọn công việc
]obi mà có thời gian thực hiện nhỏ nhất trong số các công việc còn lại cho vào
tập Js. Nếu sau khi kết nạp ]obi, các công được đánh số theo thứ tự thời gian
thực hiện tăng dần p1≤ p2≤...≤ pn. Ta có lược đồ thuật toán tham ăn như sau:
procedure JobScheduling;
begin

Js = φ ;
for i:=1 to n do
if cac_cong_viec_trong_(Js 
{Job i })_hoan_thanh_dung_han
then
Js := Js  {i};
for i:=1 to n do
if Job i ∉ Js then Js  {Jobi};
end;

Code:
const
fi
fo
type

MAX = 100;
=’js.inp’;
=’js.out’;
TJob
=record
p, d :longint;
name :longint;
end;
TArrJobs
=array[1..MAX]of TJob;
var jobs,Js :TArrJobs;
d
:array[1..MAX]of longint;
n,m :longint;

procedure input;
var
f
:text;

14


GREEDY ALGORITHMS
i
:longint;
begin
assign(f,fi); reset(f); readln(f,n);
for i:=1 to n do read(f,jobs[i].p);
for i:=1 to n do read(f,jobs[i].d);
close(f);
for i:=1 to n do jobs[i].name:=i;
end;
procedure swap(var j1,j2:TJob);
var tmp :TJob;
begin
tmp:=j1; j1:= j2; j2:=tmp;
end;
function check(var Js:TArrJobs; nJob:longint):boolean;
var
i,j,t :longint;
begin
for i:=1 to nJob-1 do
for j:=i+1 to nJob do
if Js[i].d>Js[j].d then swap(Js[i],Js[j]);

t:=0;
for i:=1 to nJob do begin
if t+Js[i].p>Js[i].d then exit(false);
t:=t+Js[i].p;
end;
exit(true);
end;
procedure Greedy;
var
i,j :longint;
Js2 :TArrJobs;
begin
for i:=1 to n-1 do
for j:=i+1 to n do
if jobs[i].p > jobs[j].p then
swap(jobs[i],jobs[j]);
fillchar(d,sizeof(d),0); m:=0;
for i:=1 to n do begin
Js2:=Js;
Js2[m+1] :=jobs [i];
if check(Js2,m+1) then begin
m:=m+1;
Js:=Js2;
d[i]:=1;
end;
end;
for i:=1 to n do
if d[i]=0 then begin
m:=m+1;
Js[m]:=jobs[i];

end;

15


GREEDY ALGORITHMS
end;
procedure printResult;
var
f :text;
i :longint;
begin
assign(f,fo); rewrite(f);
for i:=1 to n do write(f,Js[i].name,' ’); close(f);
end;
BEGIN
input;
Greedy;
printResult ;
END.
Nhận xét: Thuật toán tham lam trình bày trên luôn cho ra phương án tối ưu.

3. ỨNG DỤNG
3.1. Thuật toán Dijkstra (Thuật toán tìm đường đi ngắn nhất trên đồ thị)
Thuật toán Dijkstra, mang tên của nhà khoa học máy tính người Hà Lan Edsger
Dijkstra vào năm 1956 và ấn bản năm 1959, là một thuật toán giải quyết bài toán
đường đi ngắn nhất nguồn đơn trong một đồ thị có hướng không có cạnh mang
trọng số âm. Thuật toán thường được sử dụng trong định tuyến với một chương
trình con trong các thuật toán đồ thị hay trong công nghệ Hệ thống định vị toàn
cầu (GPS).

Bài toán: Cho đồ thị G = (V, E), mỗi cạnh của đồ thị được gán tương ứng cho một số
không âm gọi là trọng số của cạnh. Độ dài đường đi được tính bằng tổng trọng
số của các cạnh đi qua. Hãy tìm một đường đi ngẳn nhất từ đỉnh xuất phát s ∈ V
đến đình f ∈ V
Input: trong file “MINPATH.INP” có dạng:
- Dòng đầu là số đỉnh n (n<1000), số cung m, đỉnh xuất phát s, đình đích f cánh
nhau ít nhất một dấu cách
- m dòng tiếp theo, mỗi dòng chứa 3 số u, v, C[u,v] (C[u,v]<=1000) cách nhau ít
nhất một dấu cách, thể hiện trọng số của cung (u,v)
Output: file “MINPATH.OUT” có dạng:
- Dòng đầu là độ dài đường đi ngắn nhất từ đỉnh s đến đỉnh f
- Dòng thứ hai chứa đường đi ngắn nhất từ đỉnh s đến đỉnh f
TEST

16


GREEDY ALGORITHMS
6
1
1
2
3
3
5
6

7
2
6

3
6
4
4
5

MINPATH.INP
1 4
1
20
2
3
20
5
4

MINPATH.OUT
15
4 5 6 3 2 1

Hướng dẫn: Thuật toán Dijkstra được miêu tả như sau:
Bước 1: Khởi tạo
Với đình v ∈ V, gọi nhãn d[v] lả độ dài đường đi ngắn nhất từ s tới v. Ban đầu
khởi gán d[s] = 0 và d[v] = ∞ với ∀ v ≠ s. Nhãn của mỗi đỉnh có hai trạng thái tự
do hay cố định, nhãn tự do có nghĩa là có thể còn tối ưu hơn được nữa và nhãn cố
định tức là d[v] đã bằng độ dài đường đi ngắn nhất từ s tới v nên không thể tối ưu
thêm. Để làm điều này ta có thể sử dụng kỹ thuật đánh dấu: Free[v] = TRUE hay
FALSE tuỳ theo d[v] tự do hay cố định. Ban đầu các nhãn đều tự do.
Bước 2: Lặp
Bước lặp gồm có hai thao tác:

+ Cố định nhãn: Chọn trong các đỉnh có nhãn tự do, lấy ra đỉnh u lả đỉnh có
d[u] nhỏ nhất, và cố định nhãn đỉnh u.
+ Sữa nhãn: Dùng đỉnh u, xét tất cả những đỉnh v và sửa lại các d[v] theo
công thức: d[v] := min (d[v], d[u]+c[u,v])
Bước lặp sẽ kết thúc khi mà đỉnh đích f được cố định nhãn (tìm được đường đi
ngẳn nhất từ s tới f); hoặc tại thao tác cố định nhãn, tất cả các đỉnh tự do đều có
nhãn là ∞ (không tồn tại đường đi).
Có thể đặt câu hỏi, ở thao tác 1, tại sao đỉnh u như vậy được cố định nhãn, giả sử
d[u] còn có thể tối ưu thêm được nữa thì tất phải có một đỉnh t mang nhãn tự do
sao cho d[u] > d[t] + c[t, u]. Do trọng số c[t, u] không âm nên d[u] > d[t], trái với
cách chọn d[u] là nhò nhất. Tất nhiên trong lần lặp đầu tiên thì s là đỉnh được cố
định nhãn do d[s] = 0.
Bước 3:

17


GREEDY ALGORITHMS
Kết hợp với việc lưu vết đường đi trên từng bước sửa nhãn, thông báo đường đi
ngắn nhất tìm được hoặc cho biết không tồn tại đường đi (d[f] = +∞).
Code:
const

InputFile = 'MINPATH.INP';
OutputFile = 'MINPATH.OUT';
max = 1000;
maxEC = 1000;
maxC = max * maxEC;
var c: array[0..max, 0..max] of Longint;
d,trace: array[0..max]

of Longint;
Free: array [0..max]
of Boolean;
n, s, f
: Longint;
procedure LoadGraph; //Doc do thi
var i, m, u, v: Longint;
fi: Text;
begin
Assign(fi, InputFile); Reset(fi);
readLn(fi, n, m, s, f);
for u := 1 to n do
for v:= 1 to n do
if u = v then c[u, v] := 0
else c[u, v] := maxC;
for i := 1 to m do ReadLn(fi, u, v, c[u, v]);
Close(fi);
end;
procedure Init; // Khoi tao nhan, danh dau tu do
var i: Longint;
begin
for i := 1 to n do d[i] := MaxC;
d[s] := 0;
FillChar(Free, SizeOf(Free), True);
end;
procedure Dijkstra;
var
i, u, v: Longint;
min : Longint;
begin

repeat
u := 0;
min := maxC;
for i := 1 to n do
if (Free[i]) and (d[i] < min) then begin
min := d[i];
u := i;
end;
if (u = 0) or (u = f) then Break;
Free[u] := False;

18


GREEDY ALGORITHMS
for v := 1 to n do
if (Free[v]) and (d[v] > d[u] + c[u, v]) then
begin
d[v] := d[u] + c[u, v];
Trace[v] := u;
end;
until False;
end;
procedure PrintResult;
var fo: Text;
begin
Assign(fo, OutputFile); Rewrite(fo);
if d[f] = maxC then
WriteLn(fo, 'NO PATH')
else

begin
WriteLn(fo, d[f]);
while f <> s do begin
Write(fo, f,' ');
f := Trace[f];
end;
WriteLn(fo, s);
end;
Close(fo);
end;
begin
LoadGraph;
Init;
Dijkstra;
PrintResult;
end.
Nhận xét: Thuật toán trên với độ phức tạp O(n 2+m) luôn cho ra phương án tối ưu.
Tính chất tham lam ở thuật toán Dijkstra là tại mỗi bước, chọn một đỉnh s i chưa thuộc
tập nghiệm S và kề với đỉnh hiện tại, sao cho có độ dài đường đi giữa hai đình là nhỏ
nhất. Ngoài Dijkstra thường, ta có thể dụng kết hợp với cấu trúc heap, khi đó độ phức
tạp sẽ là O((m + n)log(n)), nếu dùng đống Fibonacci thì độ phức tạp giảm xuống còn
O(m + n\log n). Trong đó m là số cạnh, n là số đỉnh của đồ thị đang xét.

19


GREEDY ALGORITHMS
3.2. Thuật giải Heuristic

Thuật giải Heuristic là một sự mở rộng khái niệm thuật toán. Nó thể hiện cách giải

bài toán với các đặc tính sau:
• Thường tìm được lời giải tốt (nhưng không chắc là lời giải tốt nhất)
• Giải bài toán theo thuật giải Heuristic thường dễ dàng và nhanh chóng
đưa ra kết quả hơn so với giải thuật 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 vì vậy
chi phí thấp hơn.
• Những cách sắp xếp một bộ dữ liệu để đạt đến giới hạn giá trị (lớn nhất /
nhỏ nhất) mong muốn được gọi là"heuristic". Có thể thay đổi một vài
heuristic trong quá trình giải để thích ứng với các loại bộ dữ liệu khác
nhau và giữ lại kết quả tốt hơn.
• Thuật giải Heuristic thường thể hiện khá tự nhiên, gần gũi với cách suy
nghĩ và hành động của con người.
Có nhiều phương pháp để xây dựng một thuật giải Heuristic, trong đó người ta thường
dựa vào nguyên lí tham lam như sau: lấy tiêu chuẩn tối ưu (trên phạm vi toàn cục)

của bài toán để làm tiêu chuẩn chọn lựa hành động cho phạm vi cục bộ của từng
bước (hay từng giai đoạn) trong quá trình tìm kiếm lời giải.


Bài toán phân việc

Bài toán: 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
Input: trong file “PHANVIEC.INP” có dạng:
- Dòng đầu là hai số N và M chỉ số công việc vào số máy
- Dòng thứ hai gồm N số là thời gian thực hiện các công việc
Output: file “PHANVIEC.OUT” có dạng:


20


GREEDY ALGORITHMS
- Dòng đầu là thời gian hoàn thành N công việc
- M dòng tiếp theo: dòng i ghi số hiệu các công việc thực hiện trên máy i
TEST
PHANVIEC.INP
6 3
2 5 8 1 5 1

PHANVIEC.OUT
8
3
2 1 4
5 6

Hướng dẫn: Có thể thực hiện một heuristic sau:
Bước 1: Sắp xếp thứ tự các công việc theo thời gian hoàn thành giảm dần.
Bước 2: Lấy M công việc đầu phân cho M máy, mỗi máy một việc. Gọi MaxT là
thời gian hoàn thành lớn nhất của M công việc này
Bước 3: Thực hiện
+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). Thực hiện tiếp đến khi
không còn máy nào có thời gian thực hiện nhỏ hơn MaxT
+Tìm máy có thời gian đã làm nhỏ nhất và phân công tiếp cho máy này.
Cập nhật lại giá trị MaxT. Thực hiện cho đến khi hết việc
Code:
uses crt;

const maxn=10000;
type m1=array[1..maxn] of longint;
var t,id,p:m1;
n,m: longint;
maxt: longint;
may:m1;
procedure nhap;
var i: longint;
begin
assign(INPUT,'PHANVIEC.INP');
reset(INPUT);
read(n,m);
for i:=1 to n do read(t[i]);
for i:=1 to n do id[i]:=i;
end;

21


GREEDY ALGORITHMS
procedure QuickSort(L,H:longint);
var i,j :longint;
x,tmp :longint;
begin
i:=L;
j:=H;
x:=t[(L+H) div 2];
repeat
while t[i]>x do inc(i);
while t[j]

if i<=j then
begin
tmp:=t[i];
t[i]:=t[j];
t[j]:=tmp;
tmp:=id[i];
id[i]:=id[j];
id[j]:=tmp;
inc(i);
dec(j);
end;
until i>j;
if Lif iend;
function tim_may_min: longint;
var i,j:longint;
begin
j:=1;
for i:=1 to m do
if may[i]exit(j);
end;
procedure writef;
var i,j: longint;
sum: longint;
begin
assign(OUTPUT,'PHANVIEC.OUT');
rewrite(OUTPUT);
writeln(maxt);

for i:=1 to m do begin
for j:=1 to n do
if (p[j]=i) then write(j,' ');
writeln;
end;

22


GREEDY ALGORITHMS
close(OUTPUT);
end;
procedure chia;
var i,j,lj: longint;
ok: boolean;
begin
Fillchar(may,sizeof(may),0);
fillchar(p,sizeof(p),0);
i:=0;
maxt:=-maxint;

end;
Begin

for j:=1 to m do
begin
inc(i);
may[j]:=t[i];
p[i]:=j;
if t[i]>maxt then maxt:=t[i];

end;
inc(i);
while i<=n do
begin
ok:=false;
for j:=1 to m do
begin
if i>n then break;
if may[j]+t[i] <= maxt then
begin
ok:=true;
may[j]:=may[j]+ t[i];
p[i]:=j;
inc(i);
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;
nhap;
quicksort(1,n);

23



GREEDY ALGORITHMS
chia;
writef;
End.
Nhận xét: Qua bài toán trên, ta thấy rằng không thể nói thực hiện phương án kiểu nào
là tốt nhất, song chắc chắn là trong các phương án này phải có một kiểu tốt hơn các
kiểu còn lại khi xét bài toán ở một bộ dữ liệu cụ thể nào đó. Với nhiều bộ dữ liệu khác
nhau, ta nên nghĩ ra một số kiểu heuristic khác nhau cho một bài toám, sau đó trong
chương trình thực hiện đồng thời các kiểu này, rồi chọn ra kết quả tốt nhất trong
chúng.

24


GREEDY ALGORITHMS

KẾT LUẬN
Mục đích của phương pháp tham lam (Greedy) là xây dựng bài toán giải nhiều
lớp bài toán khác nhau, đưa ra quyết định dựa ngay vào thuật toán đang có, và trong
tương lai sẽ không xem xét lại quyết định trong quá khứ.
Do vậy thuật toán tham lam có ưu điểm dễ đề xuất, thời gian tính nhanh và
nhược điểm là đối với nhiều bài toán, giải thuật tham lam hầu như không cho ra lời
giải tối ưu toàn cục (nhưng không phải luôn như vậy), vì chúng thường không chạy
trên tất cả các trường hợp. Chúng có thể bám chặt lấy một số lựa chọn nhất định một
cách quá sớm, điều này dẫn đến hậu quả là trong giai đoạn sau, các thuật toán này
không thể tìm ra các lời giải toàn cục tốt nhất. Tuy nhiên, các thuật toán này vẫn hữu
ích vì chúng dễ thiết kế và cho ra các ước lượng tốt về lời giải tối ưu. Quan trọng là
cách chúng ta cài đặt như thế nào để đạt được kết tối ưu hoặc gần tối ưu nhất.

Tham lam là một phương pháp hay, được vận dụng rất nhiều vào các các bài toán
lý thuyết và thực tế. Qua bài tiểu luận này, mong rằng các bạn đã hiểu và sẽ ứng dụng
chúng một cách dễ dàng vào trong các bài toán có thể cài đặt được. Tiểu luận Phương
pháp tham lam đến đây là kết thúc, thân ái chào tạm biệt và hẹn gặp lại các bạn ở
những tiểu luận tiếp theo.

25


×