Thuật toán Dijkstra với những cải tiến
Thuật toán Dijkstra là một thuật toán tốt và hiệu quả trong việc tìm đường đi ngắn nhất
trên đồ thị trong trường hợp trọng số trên các cung không âm. Quả thực thuật toán đã được
đề cập rất nhiều, nhưng ở đây mình xin giới thiệu một cải tiến mới.
Xét bài toán 1: cho đồ thị G=(V,E), hãy tìm đường đi ngắn nhất từ đỉnh xuất phát s tới đỉnh
đích f.
Lời giải: đây là một bài toán kinh điển của thuật toán Dijkstra:
d[i] = + ∞ với mọi I thuộc [1..n]
Đánh dấu đỉnh s.
d[s]=0.
repeat
{tìm 1 đỉnh k còn tự do và có trọng số d[k] nhỏ nhất.}
{nếu k=f thì dừng}
{Đánh dấu đỉnh k.}
với mọi đỉnh I còn tự do thì
d[i] = min {d[i], d[k] +l[k,i]}
until {mọi đỉnh đều bị đánh dấu };
Bây giờ ta sẽ xét ví dụ 1:
Cho 1 hình chữ nhật (NxM) mỗi ô là 1 số nguyên. Từ 1 ô (x,y) hãy tìm đường đi đến ô
(u,v) sao cho tổng các số ghi trên các ô là bé nhất (qui tắc đi: từ 1 ô chỉ đi được đến các ô
có chung cạnh).
Lời giải: Đây là 1 bài toán quen thuộc và có thể dùng nhiều cách giải khác nhau, nhưng ở
đây chúng ta chỉ xét cách giải bằng thuật toán Dijkstra:
Gọi a[I,j] là trọng số nhỏ nhất khi đi từ ô (x,y) đến ô (I,j).
Khi đó cài đặt thuật toán trên ta sẽ tìm ra a[u,v] là trọng số nhỏ nhất của đường đi từ (x,y)
đến (u,v).
Vấn đề ở đây là khi tìm 1 đỉnh k tự do và có trọng số nhỏ nhất ta phải dùng 1 vòng for lồng
nhau để duyệt hết bảng, trong khi có rất nhiều ô tự do có giá trị bằng + ∞ và nhiều ô đã
được cố định nhãn.Vì vậy ở đây ta có thể cải tiến dùng thêm 2 mảng 1 chiều để giữ lại các
ô tự do mà có giá trị a[I,j] + ∞. Việc này có ý nghĩa rất lớn trong việc tìm kiếm ô tự do có
giá trị min.
Ví dụ: xuất phát từ ô (1,3) đến ô (3,1).
Tổng chi phí của đường đi ngắn nhất là 20. Nếu dùng Dijkstra bình thường thì mỗi lần tìm
min ta phải duyệt hết, nhưng nếu ta chỉ tìm những ô đang tự do mà có giá trị khác + ∞ thời
gian chạy chương trình sẽ nhanh hơn.
Quá trình chạy theo bảng sau:
Những ô màu xanh và đỏ là những ô thuộc vùng tìm kiếm giá trị min,ô màu đỏ là ô có giá
trị min trong bước chạy đó, những ô màu đen là những ô không thuộc vùng tìm kiếm giá trị
min. Ta có thể thấy số ô trong vùng tìm kiếm (ô màu xanh và đỏ) ít hơn rất nhiều so với ô
màu đen.
Chương trình dưới đây dùng mảng q:array[1..2,1..1000] of byte để giữ lại chỉ số các ô
trong vùng tìm kiếm.
uses crt;
const fi='vd1.inp';
fo='vd1.out';
max=100;
maxa=10000;
interval=1193180/65536;
dx:array[1..4]of integer=(1,0,-1,0);
dy:array[1..4]of integer=(0,-1,0,1);
var a,c:array[1..max,1..max]of word;
trace:array[1..max,1..max]of byte;
free:array[0..max,0..max]of boolean;
q:array[1..2,1..2000]of byte;
m,n,x,y,u,v,top:integer;
sttime:longint;
time: longint absolute 0:$46c;
procedure nhap;
var f:text;
i,j:integer;
begin
assign(f,fi);
reset(f);
readln(f,n,m,x,y,u,v);
for i:=1 to n do
begin
for j:=1 to m do read(f,c[i,j]);
readln(f);
end;
close(f);
sttime:=time;
end;
procedure init;
var i,j:integer;
begin
fillchar(free,sizeof(free),false);
for i:=1 to n do
for j:=1 to m do
begin
a[i,j]:=maxa;
free[i,j]:=true;
end;
end;
procedure ijk;
var i,j,min,k,h:integer;
begin
a[x,y]:=c[x,y];
top:=1;
q[1,top]:=x;
q[2,top]:=y;
repeat
min:=maxa;
for i:=1 to top do
if(a[q[1,i],q[2,i]]a[k,h]+c[k+dx[i],h+dy[i]]) then
begin
a[k+dx[i],h+dy[i]]:=a[k,h]+c[k+dx[i],h+dy[i]];
trace[k+dx[i],h+dy[i]]:=i;
inc(top);
q[1,top]:=k+dx[i];
q[2,top]:=h+dy[i];
end;
until false;
end;
procedure xuat;
var i,j,k:integer;
f:text;
begin
assign(f,fo);
rewrite(f);
writeln(f,a[u,v]);
i:=u;
j:=v;
while (ix)or(jy)do
begin
writeln(f,'(',i,',',j,')');
k:=trace[i,j];
i:=i-dx[k];
j:=j-dy[k];
end;
writeln(f,'(',i,',',j,')');
write(f,(time-sttime)/interval:1:10);
close(f);
end;
procedure test;
var i,j:integer;
f:text;
begin
assign(f,fi);
rewrite(f);
writeln(f,'100 100 1 1 100 100');
randomize;
for i:=1 to 100 do
begin
for j:=1 to 100 do
write(f,random(20),' ');
writeln(f);
end;
close(f);
end;
begin
nhap;
init;
ijk;
xuat;
end.
Nếu ta tính thời gian chạy thì chương trình trên chạy bộ test cỡ n=100, m=100, x=1, y=1,
u=100, v=100 thì nhanh hơn chương trình Dijkstra rất nhiều (theo mình thử thì mất khoảng
0,055s, trong khi Dijkstra bình thường mất khoảng 2 s). Đối với các bài toán có áp dụng
Dijkstra (như bài toán 1) thì ta có thể áp dụng cái tiến trên để chương trình có thể chạy
nhanh hơn. Các bạn hãy thử sức với bài toán sau:
Cho một ma trận nhị phân kích thước 200X200. Hai ô chứa số 0 và có chung cạnh gọi là 2
ô kề nhau. Hai ô đến được nhau nếu chúng có thể đến được nhau qua 1 số ô kề nhau, số
bước dịch chuyển là độ dài của đường đi.Từ 1 ô (x,y) hãy tìm đường đi ngắn nhất đến một
ô bất kì nằm sát biên của ma trận