1. Giải thuật BFS
Ý tưởng
- Sử dụng giải thuật tìm kiếm theo chiều rộng để tìm kiếm mọi đường đi giữa hai cặp đỉnh, sau đó lấy ra đường đi ngắn nhất
Ưu điểm
- Đơn giản, dễ dàng cài đặt
Nhược điểm
- Sử dụng đệ quy nên tốn nhiều thời gian để xử lý
Code minh họa (C#)
// Mảng lưu dữ liệu đồ thị
int[,] A;
// Mảng lưu đường đi
int[] L;
// Điểm đi, điểm đến
int source, dest;
// Số đỉnh của đồ thị
int n;
// Mảng đánh dấu các đỉnh đã đi qua
int[] Tick;
// Giá trị đường đi ngắn nhất
int minLength = MAXINT;
// Các đỉnh đã đi qua
string minPath = "";
void InitializeBFS()
{
L = new int[n + 1];
Tick = new int[n + 1];
source = Convert.ToInt16(txtSource.Text);
dest = Convert.ToInt16(txtDest.Text);
for (int i = 1; i <= n; i++)
{
Tick[i] = 0;
L[i] = 0;
}
Tick[source] = 1;
L[1] = source;
}
void SaveResult(int edge)
{
string path = source.ToString() + " -> ";
int length = 0;
for (int i = 2; i <= edge; i++)
{
path = path + L[i].ToString() + " -> ";
length = length + A[L[i - 1], L[i]];
}
if (length < minLength)
{
minLength = length;
minPath = path;
}
}
// Hàm đệ quy tìm mọi đường đi ngắn nhất giữa hai đỉnh
void TryBFS(int edge)
{
if (L[edge] == dest)
{
// Mảng L là mảng lưu kết quả đường đi
SaveResult(edge);
}
else
{
for (int i = 1; i <= n; i++)
if (A[L[edge], i] > 0 && Tick[i] == 0)
{
L[edge + 1] = i;
Tick[i] = 1;
TryBFS(edge + 1);
L[edge + 1] = 0;
Tick[i] = 0;
}
}
}
2. Giải thuật Ford-Bellman
Ý tưởng
- Ta thực hiện duyệt n lần, với n là số đỉnh của đồ thị.
- Với mỗi lần duyệt, ta tìm tất cả các cạnh mà đường đi qua cạnh đó sẽ rút ngắn đường đi ngắn nhất từ đỉnh gốc tới một đỉnh khác.
- Ở lần duyệt thứ n, nếu còn bất kỳ cạnh nào có thể rút ngắn đường đi, điều đó chứng tỏ đồ thị có chu trình âm, và ta kết thúc thuật toán.
Ưu điểm
- Đơn giản, dễ dàng cài đặt (chỉ bao gồm 3 vòng lặp lồng nhau).
- Chạy nhanh hơn so với BFS.
- Ford-Bellman là một thuật toán Dynamic Programming (thuật toán quy hoạch động).
- Có thể áp dụng cho đồ thị có trọng số âm.
Nhược điểm
- Không áp dụng được với đồ thị có chu trình âm.
- Thời gian thực hiện vẫn chưa tối ưu
Video minh họa Ford-Bellman
Code minh họa (C#)
int[] d;
int[] Trace;
void InitializeFord_Bellman()
{
d = new int[n + 1];
Trace = new int[n + 1];
for (int i = 1; i <= n; i++)
{
d[i] = MAXINT;
for (int j = 1; j <= n; j++)
{
if (A[i, j] == 0 && i != j)
A[i, j] = MAXINT;
}
}
d[source] = 0;
}
void Ford_Bellman()
{
bool isStop;
for (int i = 1; i < n; i++)
{
isStop = true;
for (int u = 1; u <= n; u++)
for (int v = 1; v <= n; v++)
{
if (A[u, v] + d[u] < d[v])
{
d[v] = d[u] + A[u, v];
Trace[v] = u;
isStop = false;
}
}
if (isStop) break;
}
}
string PrintResult()
{
string res = "";
if (d[dest] == MAXINT)
{
res = "Không có đường đi từ " + source + "đến " + dest;
}
else
{
int path = d[dest];
while (dest != source)
{
res += dest + "<--";
dest = Trace[dest];
}
res += source + ": " + path;
}
return res;
}
3. Giải thuật Dijkstra
Ý tưởng
- Chọn một điểm bất kỳ chưa được duyệt và có đường đi ngắn nhất từ điểm gốc tới nó là nhỏ nhất.
- Từ điểm đó, loang đường đi ra tất cả các đỉnh kề cận, và cập nhật lại đường đi ngắn nhất tới các đỉnh đó nếu đường đi mới tối ưu hơn.
- Nếu còn điểm chưa được duyệt (hoặc chưa tìm được điểm đích, tùy yêu cầu đề bài), ta trở lại bước 1.
Ưu điểm
- Thời gian thực hiện nhanh.
- Dijkstra là một thuật toán Greedy (thuật toán tham lam).
- Độ phức tạp O(n2 + m), nếu dùng cấu trúc Heap thì độ phức tạp giảm xuống còn O((m+n)*log(n)).
Nhược điểm
- Chỉ áp dụng được cho đồ thị không có trọng số âm
Video minh họa Dijkstra
Code minh họa (C#)
string Dijkstra(int[,] A, int n, int src, int des)
{
int[] DanhDau = new int[n + 1];
int[] Nhan = new int[n + 1];
int[] Truoc = new int[n + 1];
int XP, min, i;
for (i = 1; i <= n; i++)
{
Nhan[i] = MAXINT;
DanhDau[i] = 0;
Truoc[i] = src;
}
Nhan[src] = 0;
DanhDau[src] = 1;
XP = src;
while (XP != des)
{
for (int j = 1; j <= n; j++)
if (A[XP, j] > 0 && Nhan[j] > A[XP, j] + Nhan[XP] && DanhDau[j] == 0)
{
Nhan[j] = A[XP, j] + Nhan[XP];
Truoc[j] = XP;
}
min = MAXINT;
for (int j = 1; j <= n; j++)
if (min > Nhan[j] && DanhDau[j] == 0)
{
min = Nhan[j];
XP = j;
}
DanhDau[XP] = 1;
}
// Truy vết đường đi và in kết quả
string[] temp = new string[n + 1];
temp[0] = des.ToString();
temp[1] = Truoc[des].ToString();
i = Truoc[des];
int count = 2;
while (i != src)
{
i = Truoc[i];
temp[count] = i.ToString();
count++;
}
string res = "";
for (i = count - 1; i >= 1; i--)
res += temp[i] + " --> ";
res += temp[0] + ": " + Nhan[des];
return res;
}
4. Chương trình minh họa
Ở đây, mình có viết một chương trình tìm đường đi ngắn nhất bằng C#, dữ liệu test với đồ thị có 6 đỉnh và dữ liệu thật của một nút giao thông có 5011 đỉnh.
Trong đó, khi mình test với dữ liệu thật của một nút giao thông có 5011 đỉnh thì thời gian thực hiện ở mức chấp nhận được khi chỉ mất 36 mili giây để tìm được đường đi ngắn nhất.
Link project: