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

BÀI TẬP LỚN MÔN Cấu trúc dữ liệu và giải thuật ĐƯỜNG ĐI NGẮN NHẤT

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 (137.42 KB, 11 trang )

Bài tập lớn môn

Cấu trúc dữ liệu và giải thuật

Đờng đi ngắn nhất
I. Bài toán đờng đi ngắn nhất.
1. Phát biểu bài toán.
Trong các ứng dụng thực tế bài toán tìm đờng đi ngắn nhất giữa hai đỉnh của một đồ
thị có ý nghĩa to lớn. Có thể dẫn về bài toán nh vậy nhiều bài toán thực tế quan trọng.
Ví dụ:
Bài toán chọn một hành trình tiết kiệm nhất (theo tiêu chuẩn khoảng cách hoặc
thời gian, chi phí ...) trên một bản đồ đờng giao thông.
Bài toán chọn một phơng pháp tiết kiệm nhất để đa một hệ động lực từ trạng thái
này sang trạng thái khác.
Đặc biệt, bài toán tìm hành trình trong nhiều công đoạn nào đó thuộc một qui
trình công nghệ sản xuất tự động. Ví dụ trong công đoạn khoan mạch in, hàn chân các
linh kiện điện tử trong các nhà máy sản xuất, lắp ráp mạch điện tử, hành trình làm việc
của các máy khoan tự động, robot hàn thờng gồm rất nhiều đỉnh: hàng nghìn, hàng
chục nghìn đỉnh. Vậy nênviệc thực hiện đợc hành trình ngắn nhất cho các robot đem
lại lợi ích to lớn: tăng năng suất, giảm chi phí... (có thể trong trờng hợp này bài toán
biến đổi đi một chút, ví dụ cần phải tìm hành trình ngắn nhất qua tất cả các đỉnh, mỗi
đỉnh đúng một lần...)
Và nhiều bài toán thực tế khác ...
Hiện nay có rất nhiều phơng pháp để giải các bài toán nh vậy. Thế nhng thông thờng các phơng pháp dựa trên lý thuyết đồ thị tỏ ra là các phơng pháp có hiêụ quả cao
nhất.
Bài toán tìm đờng đi ngắn nhất dới dạng tổng quát có thể phát biểu nh sau:
Cho một đồ thị G(V, E), một hàm trọng số w(e) cho các cung e của G. Bài toán đặt
ra là cần tìm một đờng đi ngắn nhất từ một đỉnh xuất phát s G đến đỉnh cuối d G.
Các trọng số w(e) có thể là dơng, âm hoặc bằng 0. Một điều duy nhất là G không chứa
chu trình với tổng trọng số âm. Vì rằng nếu nh G có một chu trình H nh vậy thì xuất
phát từ S, ta đi đến v H và sau đó đi vòng quanh chu trình H một số đủ lớn lần rồi đến


d ta sẽ thu đợc một đờng đi có trọng lợng đủ nhỏ . Vì vậy trong trờng hợp này đờng đi ngắn nhất là không tồn tại.
Tuy nhiên, việc giải bài toán tổng quát trên là nằm ngoài khuôn khổ bài tập lớn này,
vì vậy chúng ta chỉ giải bài toán đặt ra trong trờng hợp các trọng số w(e) 0, G là đồ
thị định hớng. Ta xét một thuật toán đơn giản, hiệu quả để giải bài toán trong trờng hợp
này.
2. Giải bài toán với trọng số không âm thuật toán Dijkstra.


Dijkstra là ngời đầu tiên đề nghị thuật toán hữu hiệu giải bài toán tìm đờng đi ngắn
nhất từ s tới d. Phơng pháp này đơc xây dựng dựa trên cơ sở dán các nhãn tạm thời cho
các đỉnh. Nhãn của mỗi đỉnh cho biết cận trên của độ dài đờng đi ngắn nhất từ s đến
nó. Các nhãn này đợc biến đổi theo các thủ tục lặp, mà ở mỗi bớc lặp lại có một nhãn
tạm thời trở thành nhãn cố định. Nếu nhãn của một đỉnh nào đó trở thành cố định thì
nó cho ta không phải là cận trên mà là độ dài của đờng đi ngắn nhất từ s đến nó. Ta sẽ
mô tả cụ thể thuật toán này.
Thuật toán Dijkstra (w(e) 0):
Ký hiệu val(i), val(s) là nhãn của đỉnh v(i), s.
Bớc 1. Gán nhãn tạm thời cho tất cả các đỉnh. Tất cả val của các đỉnh bằng + trừ
val(s) = 0.
Bớc 2. Trong tất cả các đỉnh có nhãn tạm thời và khác + , tìm đỉnh có val nhỏ
nhất. Nếu tìm thấy, gọi đỉnh đó là vmin. Nếu không tìm thấy thì không có đờng đi từ s
đến d, thoát.
Thay đổi nhãn của vmin thành cố định. Nếu vmin chính là d thì đã tìm đợc đờng đi
ngắn nhất, độ dài đờng đi này chính là val(vmin), thoát.
Bớc 3. Với các đỉnh v(i) có nhãn tạm thời và kề với vmin (có cạnh hớng từ vmin
sang v(i), nói cách khác là w(vmin, v(i)) < + ) ta thay đổi val(i) theo qui tắc sau:
val(i) := min{val(i), val(vmin) + w(vmin, v(i))}.
Quay lại bớc 2.
Phần chứng minh thuật toán này có thể thấy trong rất nhiều tài liệu nên để khỏi dài
dòng, ta không cần nêu ra ở đây.

II. Chơng trình viết bằng ngôn ngữ Turbo Pascal.
1. Cách tổ chức chơng trình.
a. Cấu trúc dữ liệu.
Trong chơng trình này những biến dùng để lu các trọng số của các cạnh chiếm rất
nhiều bộ nhớ. Ta sẽ dùng các biến kiểu real (6 bytes) để lu các trọng số. Nếu ta lu trữ
các trọng số vào ma trận kề thì chơng trình chỉ có thể xử lý cho số lợng đỉnh tối đa là
xấp xỉ 100 đỉnh, một số lợng khá ít cho các bài toán thực tế. Vì vậy việc chọn cấu trúc
dữ liệu thích hợp cho chơng trình là hết sức quan trọng.
Ta sẽ dùng cấu trúc danh sách kề để biểu diễn đồ thị. Ta dùng một mảng một chiều
mà mỗi phần tử mảng là một danh sách móc nối. Mỗi đỉnh của G có một danh sách tơng ứng. Các nút trong danh sách i biểu diễn các đỉnh lân cận của đỉnh v(i). Mỗi nút
của danh sách thứ i gồm 3 trờng: index, long và link.
Trờng index cho biết chỉ số đỉnh kề với đỉnh v(i).
Trờng long cho biết trọng số của cạnh nối từ nút v(i) đến nút có chỉ số index.
Trờng link trỏ tới nút tiếp theo trong danh sách hoặc bằng nil nếu đó là nút cuối
cùng của danh sách.
Ta thấy dùng cách biểu diễn đồ thị này tuy có phức tạp hơn dùng ma trận kề song
chơng trình có thể dễ dàng giải quyết bài toán có số đỉnh số cạnh cỡ nghìn, đủ lớn cho
nhiều ứng dụng thực tế.
Ta khai báo trong chơng trình nh sau:
const
nmax=1001;


type
nn=1..nmax;
vertexPtr=^vertexNode;
vertexNode=record
index: word;
long: real;
link: vertexPtr;

end;
var
adj: array[nn] of vertexPtr;

Nmax là số đỉnh tối đa mà chơng trình có thể xử lý.
Adj chính là mảng các danh sách kề.
b. Cấu trúc chơng trình.
Ta chia nhỏ chơng trình thành các module, mỗi module có chức năng riêng và do 1
hoặc 2 thành viên trong nhóm thiết kế. Sau đây là cấu trúc chơng trình.

program

Find_The_Shortest_Way;
uses crt;
const declaration;
type declaration;
var declaration;
Procedure insert(i1, i2: integer; val: real);
Function check(a, b, n: integer): boolean;
Procedure inputFromFile;
Call insert, check;
Procedure showData;
Procedure inputFromKeyboard;
Call check;
Procedure dijkstra;
Procedure output;
Begin
Call inputFromFile, showData,
inputFromKeyBoard, Dijkstra, output, check;
end.


2. Chức năng của từng module.
Procedure insert(i1, i2: integer; val: real)
Thêm đỉnh v(i2) vào danh sách các đỉnh kề của v(i1), val là trọng số của cạnh
(v(i1), v(i2)).
Function check(a, b, n: integer): boolean;
Nếu 1 a, b n thì check trả về giá trị true, nếu không thì trả về false.


Procedure inputFromFile;
Thủ tục này nhập các số liệu về đồ thị từ file text dijkstra.inp. Ta đề cập luôn cấu
trúc của file này. Tất cả các số liệu về G, trừ s và d đợc nhập từ bàn phím, đều chỉ đợc
ghi trong file trên.
Đầu tiên thủ tục đọc số đỉnh của G vào biến toàn cục n có kiểu integer. Trong file
dữ liệu, n đợc ghi ngay sau dấu :. Xuống dòng, một dòng hớng dẫn. Sau đó đến n
dòng ghi chỉ số, tên của đỉnh ứng với chỉ số đó. Các dòng tên đỉnh có thể ghi theo thứ
tự bất kỳ. Biến mảng name toàn cục chứa tên của các đỉnh, mỗi tên có không quá 15 ký
tự. Dành tiếp một dòng cho hớng dẫn. Mỗi dòng tiếp theo cho thông tin về một cạnh
của đồ thị. Mỗi dòng gồm 2 chỉ số đỉnh i1, i2 và trọng số của cạnh hớng từ v(i1) đến
v(i2). Trọng số là số thực để có thể áp dụng tốt hơn số nguyên cho các bài toán thực tế.
Kết thúc file.
Procedure showData;
Thủ tục này hiển thị một phần thông tin về G ra màn hình nhằm giúp ngời sử dụng
kiểm tra thêm về tính đúng đắn của dữ liệu (chỉ dùng cho những đồ thị bé). Các thông
tin đợc hiển thị bao gồm: số đỉnh, số cạnh, tên các đỉnh, danh sách kề (không có thông
tin về độ lớn của các cạnh).
Procedure inputFromKeyboard;
Thủ tục này lấy các chỉ số của đỉnh nguồn, đỉnh đích do ngời sử dụng nhập từ bàn
phím đa vào 2 biến toàn cục nguyên: source và dest. Nó đơc chơng trình chính sử dụng
kết hợp với hàm check tạo nên vòng lặp sử dụng chơng trình. Khi ngời dùng nhập vào

một số không nằm trong [1, n] thì chơng trình kết thúc ngay.
Procedure dijkstra;
Đây là thủ tục chính, hạt nhân của chơng trình. Chúng ta sẽ mô tả kỹ hơn về thủ tục
này.
Thủ tục dùng các biến mảng val, dad, mark, adj. Chúng ta đã biết adj là mảng danh
sách kề lu trữ thông tin về các cạnh của đồ thị có hớng G, còn val (viết tắt của value) lu
nhãn của các đỉnh. Ta định nghĩa hằng maxreal = 1e38 đủ lớn để đại diện cho đại lợng
+ . Mảng mark cho biết trạng thái nhãn của các đỉnh. Mark[i] bằng 0 hoặc 1 cho biết
nhãn đỉnh v(i) là tạm thời hay cố định. Còn mảng dad dùng để lu trữ thông tin về đờng
đi để sau khi tìm đợc đờng đi ngắn nhất, ta sẽ dùng nó để tìm lại đờng đi từ s đến d.
Trong các bớc lặp, nếu val[i] đợc thay đổi, dad[i] sẽ chứa đỉnh đứng ngay trớc v(i)
trong đờng đi đã tìm đợc ở thời điểm đó. Nếu nhãn của v(i) là cố định, dad[i] chính là
chỉ số của đỉnh đứng ngay trớc đỉnh i trong đờng đi ngắn nhất từ s đến v(i).
Biến imin, vmin là chỉ số và giá trị nhãn của đỉnh vmin ta đã đề cập trong bớc 2 của
thuật toán. Biến toàn cục found kiểu boolean cho biết đã gặp đỉnh đích và gán nhãn cố
định cho nó hay cha.
Thực tế là có nhiều cách tổ chức lu trữ cho các biến adj, name, dad, mark, val để tiết
kiệm bộ nhớ hơn nữa, song với chơng trình bài tập lớn này ta thấy đó là điều không cần
thiết lắm vì nó làm tăng độ phức tạp của chơng trình lên mà hiệu quả không tăng nhiều.
Do vậy ta vẫn chọn kiểu mảng một chiều đơn giản cho các biến trên.
Vì thủ tục đơc thiết kế rất trung thành với ý tởng thuật toán đề ra và đoạn mã cũng
ngắn, dễ hiểu nên để cho hiệu quả, nhanh chóng chúng ta sẽ bỏ qua phần thiết kế thủ
tục bằng ngôn ngữ tựa Pascal.


Procedure output;
Thủ tục này xuất kết quả ra file text dijkstra.out, đồng thời đa kết quả ra màn hình
để ngời sử dụng dễ theo dõi đối với các đồ thị cỡ nhỏ. Nếu nh tìm đợc đờng đi, thủ tục
này cũng kiêm luôn chức năng dò lại đờng đi, lu vào mảng way.
3. Văn bản chơng trình.

Xin xem chơng trình dijkstra.pas trong đĩa kèm theo.
program Find_The_Shortest_Way;
uses crt;
const
nmax=1001;
maxreal=1e38;
fi='dijkstra.inp';
fo='dijkstra.out';
type
nn=1..nmax;
vertexPtr=^vertexNode;
vertexNode=record
index: word;
long: real;
link: vertexPtr;
end;
var
f: text;
n, e, start, dest: integer;
name: array[nn] of string[15];
val: array[nn] of real;
dad, way: array[nn] of word;
mark: array[nn] of byte;
adj: array[nn] of vertexPtr;
found: boolean;
Procedure insert(i1, i2: integer; val: real);
var
v: vertexPtr;
begin
new(v);

v^.index:=i2;
v^.long:=val;
v^.link:=adj[i1];
adj[i1]:=v;
end;


Function check(a, b, n: integer): boolean;
begin
check:=(a>=1)and(a<=n)and(b>=1)and(b<=n);
end;
Procedure inputFromFile;
var
s: string;
ch: char;
i, j, k, i1, i2: integer;
val: real;
begin
assign(f, fi); reset(f);
ch:=#0;
repeat
read(f, ch);
until eof(f) or (ch=':');
readln(f, n);
if eof(f) or not check(n, 1, nmax) then halt;
readln(f);
for i:=1 to n do
begin
read(f, k);
readln(f, s);

while (s[1]=' ') and (s<>'') do delete(s, 1, 1);
name[k]:=s;
adj[i]:=nil;
end;
readln(f);
e:=0;
while not eof(f) do
begin
readln(f, i1, i2, val);
if (i1>=1)and(i1<=n)and(i2>=1)and(i2<=n) then
begin
insert(i1, i2, val);
inc(e);
end;
end;
close(f);
end;
Procedure showData;
var
s: string;
i: integer;
v: vertexPtr;
begin
writeln(n, ' vertices, ', e, ' edges.');
for i:=1 to n do


begin
s:=name[i];
while length(s)<15 do s:=s+' ';

write(i: 4, s: 16);
end;
writeln; writeln;
writeln('Adj list:');
for i:=1 to n do
begin
write(i: 4, ' ');
v:=adj[i];
while v<>nil do
begin
write(v^.index: 4);
v:=v^.link;
end;
writeln;
end;
writeln;
end;
Procedure inputFromKeyboard;
begin
writeln;
writeln('*******************************');
writeln('Input data please (0 for quit).');
write('Index of source vertex: ');
readln(start);
if not check(start, 1, n) then exit;
write('Index of dest vertex: ');
readln(dest);
writeln;
end;
Procedure dijkstra;

var
i, j, k, imin: integer;
vmin: real;
finish: boolean;
v: vertexPtr;
begin
found:=false;
fillchar(dad, 2*n, 0);
fillchar(mark, n, 0);
for i:=1 to n do val[i]:=maxreal;
val[start]:=0;
while true do
begin
vmin:=maxreal;
for i:=1 to n do


if (val[i]begin
vmin:=val[i];
imin:=i;
end;
if (vmin=maxreal) or (imin=dest) then break;
mark[imin]:=1;
v:=adj[imin];
while v<>nil do
begin
with v^ do
if (mark[index]=0) and (val[index] > val[imin]+long) then
begin

val[index]:=val[imin]+long;
dad[index]:=imin;
end;
v:=v^.link;
end;
end; {while true}
if imin=dest then found:=true;
end;
Procedure output;
var
m, i, k: integer;
begin
assign(f, fo); rewrite(f);
if not found then
begin
writeln('Can not go from ', name[start], ' to ', name[dest]);
writeln(f, 'Can not go from ', name[start], ' to ', name[dest]);
close(f);
exit;
end;
k:=dest;
m:=0;
repeat
inc(m);
way[m]:=k;
k:=dad[k];
until k=0;
writeln('The shortest way from ', name[way[m]], ' to ', name[way[1]], '
is:');
writeln(f, 'The shortest way from ', name[way[m]], ' to ', name[way[1]],

' is:');
for i:=m downto 1 do
begin
writeln(way[i]: 4, ' ', name[way[i]]);
writeln(f, way[i]: 4, ' ', name[way[i]]);
end;
writeln;


writeln(f);
writeln('Total length: ', val[dest]: 10);
writeln(f, 'Total length: ', val[dest]: 10);
close(f);
end;

Begin
clrscr;
inputFromFile;
showData;
repeat
inputFromKeyBoard;
if not check(start, dest, n) then break;
dijkstra;
output;
until false;
end.

4. Kiểm tra chơng trình.
File dijkstra.inp đã chứa sẵn thông tin về đồ thị mẫu sau:


Kết quả trên màn hình khi chạy chơng trình:
7 vertices, 14 edges.
1 Ha Noi
5 Hong Kong
Adj list:
1
6

7

2

2 Paris
6 Sai Gon

3 Tokyo
7 New York

4 London


2
3
4
5
6
7

7
7

7
4
7
6

3
4
5
5

4

*******************************
Input data please (0 for quit).
Index of source vertex:

Chän ®Ønh ®Çu lµ 1, ®Ønh ®Ých lµ 4 ta ®îc:
*******************************
Input data please (0 for quit).
Index of source vertex: 1
Index of dest vertex: 4
The shortest way from Ha Noi to London is:
1 Ha Noi
2 Paris
3 Tokyo
7 New York
6 Sai Gon
5 Hong Kong
4 London
Total length:


6.010E+01

*******************************
Input data please (0 for quit).
Index of source vertex:

Chän ®Ønh ®Çu lµ 5, ®Ønh ®Ých lµ 1 ta ®îc:
*******************************
Input data please (0 for quit).
Index of source vertex: 5
Index of dest vertex: 1
Can not go from Hong Kong to Ha Noi
*******************************
Input data please (0 for quit).
Index of source vertex: 0


(****************************************)
(*******************************************************)



×