You are on page 1of 13

Tìm hiểu và nghiên cứu thuật toán Dijkstra tìm các đường đi ngắn nhất từ một đỉnh đến

các đỉnh còn lại của đồ thị có trọng số và thuật toán Floyd-Warshall tìm các đường đi ngắn
nhất giữa các cặp đỉnh của đồ thị có trọng số và thực hiện các yêu cầu sau:
1. Trình bày thuật toán Dijkstra và thuật toán Floyd-Warshall.
2. So sánh độ phức tạp về thời gian của thuật toán Dijkstra và thuật toán Floyd-Warshall.
3. So sánh khả năng ứng dụng của thuật toán Dijkstra và thuật toán Floyd-Warshall.

Trình bày thuật toán Dijkstra và thuật toán Floyd-Warshall.

Thuật toán Dijkstra dùng để giải quyết bài toán đường đi ngắn nhất một
nguồn (Single-source shortest path), đồ thị trọng số không âm.
Bài toán.

Ý tưởng của thuật toán.


thuật toán Dijkstra cũng tối ưu hóa đường đi bằng cách xét các cạnh (u,v)(u,v), so sánh
hai đường đi S→vS→v sẵn có với đường đi S→u→vS→u→v.
Thuật toán hoạt động bằng cách duy trì một tập hợp các đỉnh trong đó ta đã biết chắc
chắn đường đi ngắn nhất. Mỗi bước, thuật toán sẽ chọn ra một đỉnh uu mà chắc chắn sẽ
không thể tối ưu hơn nữa, sau đó tiến hành tối ưu các đỉnh vv khác dựa trên các
cạnh (u,v)(u,v) đi ra từ đỉnh uu. Sau NN bước, tất cả các đỉnh đều sẽ được chọn, và mọi
đường đi tìm được sẽ là ngắn nhất.
Cụ thể hơn, thuật toán sẽ duy trì đường đi ngắn nhất đến tất cả các đỉnh. Ở mỗi bước,
chọn đường đi S→uS→u có tổng trọng số nhỏ nhất trong tất cả các đường đi đang
được duy trì. Sau đó tiến hành tối ưu các đường đi S→vS→v bằng cách thử kéo dài
thành S→u→vS→u→v như đã mô tả trên.

Minh họa thuật toán


Cài đặt
Ở thuật toán này, ta sẽ lưu đồ thị dưới dạng danh sách kề. Ta định nghĩa như sau:
 D[u]D[u] là đường đi ngắn nhất từ s→us→u. Ban đầu khởi
tạo D[u]=∞D[u]=∞ với mọi uu, riêng D[s]=0D[s]=0.
o Cũng như thuật toán Bellman-Ford, ta có thể định nghĩa thêm
mảng tracetrace để truy vết đường đi nếu cần.
 W[u,v]W[u,v] là trọng số cạnh trên đường đi từ u→vu→v.
 P[u]P[u] là mảng đánh dấu các đỉnh uu đã được xử lí chưa. Ban đầu tất cả các giá
trị đều là false.
Ta sẽ lặp NN lần quá trình sau:
 Tìm đỉnh uu có D[u]D[u] nhỏ nhất và P[u]=falseP[u]=false.
 Sau khi tìm được đỉnh uu, ta xét các đỉnh vv kề với đỉnh uu và tiến hành tối ưu
hóa D[v]D[v]: nếu D[v]>D[u]+W[u,v]D[v]>D[u]+W[u,v] thì D[v]=D[u]
+W[u,v]D[v]=D[u]+W[u,v].
o Nếu việc tối ưu hóa diễn ra, ta sẽ cập nhật trace[v]=utrace[v]=u.
 Đánh dấu P[u]=trueP[u]=true, nghĩa là đỉnh uu đã được xử lí xong
Độ phức tạp thuật toán: Ta có NN lần lặp:
 Bước đầu tiên có độ phức tạp O(N)O(N) mỗi lần lặp.
 Bước thứ hai có tổng độ phức tạp O(M)O(M) qua tất cả các lần lặp
Như vậy độ phức tạp của cách cài đặt cơ bản sẽ là O(N2+M)O(N2+M).
Code:
const long long INF = 2000000000000000000LL;
struct Edge{
int v;
long long w;
};
void dijkstra(int n, int S, vector<vector<Edge>> E, vector<long long> &D,
vector<int> &trace) {
D.resize(n, INF);
trace.resize(n, -1);

vector<bool> P(n, 0);


D[S] = 0;

for (int i = 0; i < n; i++) {


int uBest; // tìm đỉnh u chưa dùng, có khoảng cách nhỏ nhất
long long Max = INF;
for (int u = 0; u < n; u++) {
if(D[u] < Max && P[u] == false) {
uBest = u;
Max = D[u];
}
}

// cải tiến các đường đi qua u


int u = uBest;
P[u] = true;
for(auto x : E[u]) {
int v = x.v;
long long w = x.w;
if(D[v] > D[u] + w) {
D[v] = D[u] + w;
trace[v] = u;
}
}
}
}

Cải tiến đối với đồ thị thưa


 Nhận xét rằng bước đầu tiên: "Tìm đỉnh uu có DuDu nhỏ nhất
và Pu=falsePu=false", có thể được cải tiến. Ta có thể sử dụng cấu trúc dữ
liệu Heap (cụ thể là Min Heap) hoặc cây nhị phân tìm kiếm để cải tiến bước này.
o Mỗi lần tối ưu hóa DvDv, ta đẩy cặp Dv,vDv,v vào trong Heap.
o Để tìm đỉnh có DuDu nhỏ nhất, ta chỉ cần liên tục lấy phần tử trên cùng
trong Heap ra, cho đến khi gặp đỉnh uu thỏa mãn Pu=falsePu=false.
 Mỗi lần tối ưu DvDv, ta phải push vào Heap một lần. Với mỗi lần push vào trong
Heap, ta đều phải pop ra lại một lần. Do có tối đa O(M)O(M) lần tối ưu, độ phức
tạp của thuật toán là O(MlogN)O(MlogN).
Độ phức tạp sau khi cải tiến: O(MlogN)O(MlogN). Lưu ý rằng với MM lớn thì độ phức
tạp này không tốt hơn cài đặt cơ bản. Chứng minh như sau:
 Ta có độ phức tạp của hai cách cài đặt:
o Cách cài đặt cơ bản: O(N2+M)O(N2+M).
o Cách cài đặt cải tiến: O(MlogN)O(MlogN).
 Số lượng cạnh MM trong đơn đồ thị không vượt quá N2N2, như vậy độ phức tạp
của cách cơ bản có thể được viết đơn giản thành O(N2)O(N2).
 Để cách cài đặt cải tiến tốt hơn, ta cần MlogN<N2MlogN<N2 suy
ra M<N2/logNM<N2/logN.
o Ví dụ: đối với N=105N=105, ta cần M>6⋅108M>6⋅108 để cách cài đặt cơ
bản tốt hơn cách cài đặt cải tiến. Thực tế trong lập trình thi đấu khó có đồ
thị nào có số cạnh lớn như vậy. Vì thế nhìn chung khi NN lớn thì
thuật O(MlogN)O(MlogN) luôn tốt hơn.
Code:
const long long INF = 2000000000000000000LL;
struct Edge{// kiểu dữ liệu tự tạo để lưu thông số của một cạnh.
int v;
long long w;
};
struct Node{// kiểu dữ liệu để lưu đỉnh u và độ dài của đường đi ngắn nhất từ s
đến u.
int u;
long long Dist_u;
};
struct cmp{
bool operator() (Node a, Node b) {
return a.Dist_u > b.Dist_u;
}
};
void dijkstraSparse(int n, int s, vector<vector<Edge>> &E, vector<long long> &D,
vector<int> &trace) {
D.resize(n, INF);
trace.resize(n, -1);
vector<bool> P(n, 0);

D[s] = 0;
priority_queue<Node, vector<Node>, cmp> h; // hàng đợi ưu tiên, sắp xếp theo
dist[u] nhỏ nhất trước
h.push({s, D[s]});

while(!h.empty()) {
Node x = h.top();
h.pop();

int u = x.u;
if(P[u] == true) // Đỉnh u đã được chọn trước đó, bỏ qua
continue;

P[u] = true; // Đánh dấu đỉnh u đã được chọn


for(auto e : E[u]) {
int v = e.v;
long long w = e.w;

if(D[v] > D[u] + w) {


D[v] = D[u] + w;
h.push({v, D[v]});
trace[v] = u;
}
}
}
}
Tìm lại đường đi ngắn nhất
Cũng giống như thuật toán Bellman-Ford, để tìm lại đường đi ngắn nhất từ SS về uu, ta
sẽ bắt đầu từ đỉnh uu, sau đó truy vết theo mảng tracetrace ngược về SS.
vector<int> trace_path(vector<int> &trace, int S, int u) {
if (u != S && trace[u] == -1) return vector<int>(0); // không có đường đi

vector<int> path;
while (u != -1) { // truy vết ngược từ u về S
path.push_back(u);
u = trace[u];
}
reverse(path.begin(), path.end()); // cần reverse vì đường đi lúc này là từ u
về S

return path;
}

Thuật toán Floyd-Warshall


Ý tưởng của thuật toán.
Ý tưởng của thuật toán này là: "Liệu chúng ta có thể chèn một đỉnh kk vào đường đi
ngắn nhất giữa 2 đỉnh uu và vv?".
 Ví dụ như có một đường đi ngắn nhất từ 0→40→4 như
sau: 0→1→2→3→40→1→2→3→4. Vậy việc tính đường đi ngắn nhất
từ 0→40→4 hoàn toàn có thể được chia thành tính đường đi ngắn nhất
từ 0→20→2 sau đó cộng với đường đi ngắn nhất từ 2→42→4. Tương tự thế
đường đi ngắn nhất từ 0→20→2 và 2→42→4 lại tiếp tực được phân hoạch thành
những đường đi ngắn nhất khác đơn giản hơn và tối ưu hơn.

Ta nhận thấy có một cấu trúc đệ quy, chia nhỏ bài toán ở đây. Ý tưởng này cho phép
chúng ta thực hiện một thuật toán mang hương vị quy hoạch động như sau:

 Gọi D(u,v,k)D(u,v,k) là đường đi ngắn nhất, trong đó ta chỉ được đi qua kk đỉnh


đầu tiên (có số thứ tự từ 00 đến k−1k−1), ngoại trừ chính uu và vv. Ta có công
thức truy hồi:
o D(u,v,0)=Wu,vD(u,v,0)=Wu,v (không được dùng đỉnh nào ngoài
chính u,vu,v).
o D(u,u,k)=0D(u,u,k)=0
o D(u,v,k)=D(u,v,k)= min của 2 trường hợp:
 D(u,v,k−1)D(u,v,k−1): ta không dùng đỉnh kk làm trung gian, giữ
nguyên đường đi cũ.
 D(u,k,k−1)+D(k,v,k−1)D(u,k,k−1)+D(k,v,k−1): ta dùng
đỉnh kk làm trung gian, từ đường đi u→vu→v thành đường
đi u→k→vu→k→v.
Đến đây ta có thể sử dụng trực tiếp công thức quy hoạch động để cài đặt thuật toán.
Tuy nhiên, để đảm bảo bộ nhớ, ta có thể tính các D(u,v,k)D(u,v,k) với kk lần lượt
từ 11 đến NN, và khi cài đặt chỉ cần lưu lại D(u,v)D(u,v).

Cài đặt
 Định nghĩa:

o W[u,v]W[u,v] là giá trị đường đi trực tiếp từ u→vu→v.


o D[u,v]D[u,v] là giá trị đường đi ngắn nhất từ u→vu→v.
o trace[u,v]trace[u,v] là mảng truy vết đường đi ngắn nhất từ u→vu→v
 Đồ thị sẽ được lưu dưới dạng ma trận kề. Ban đầu sẽ khởi tạo
mọi D[u,v]=W[u,v]D[u,v]=W[u,v] vì khi chưa tối ưu gì thì đường đi trực tiếp luôn
là đường đi ngắn nhất.
o trace[u,v]trace[u,v] sẽ khởi tạo bằng uu với mọi cặp u,vu,v.
o Nếu không có cạnh nối giữa uu và vv, coi như W[u,v]=∞W[u,v]=∞ .
 Thuật toán chỉ cần một vòng lặp xét mọi đỉnh kk như một đỉnh trung gian. Tiếp
theo đến là 2 vòng lặp uu, vv, có ý nghĩa thử chèn đỉnh kk vào giữa đường đi
từ uu đến vv.
o Nếu như D[u,v]D[u,v] được tối ưu bằng đỉnh kk, ta cập nhật
thêm trace[u,v]=trace[k,v]trace[u,v]=trace[k,v]
 Độ phức tạp của thuật toán là O(N3)O(N3).
Code: Thuật toán có thể được cài đặt rất dễ dàng chỉ với 3 vòng lặp:
void init_trace(vector<vector<int>> &trace) {
int n = trace.size();
for (int u = 0; u < n; u++) {
for (int v = 0; v < n; v++) {
trace[u][v] = u;
}
}
}

void floydWarshall(int n, vector<vector<long long>> &w, vector<vector<long long>>


&D, vector<vector<int>> &trace) {
D = w;
init_trace(trace); // nếu cần dò đường đi

for (int k = 0; k < n; k++) {


for (int u = 0; u < n; u++) {
for (int v = 0; v < n; v++) {
if (D[u][v] > D[u][k] + D[k][v]) {
D[u][v] = D[u][k] + D[k][v];
trace[u][v] = trace[k][v];
}
}
}
}
}

Tìm lại đường đi ngắn nhất


Giống như hai thuật toán Bellman-Ford và Dijkstra, để tìm đường đi từ uu đến vv, ta sẽ
bắt đầu từ vv, truy ngược về uu theo mảng trace đã tìm được.
vector<int> trace_path(vector<vector<int>> &trace, int u, int v) {
vector<int> path;
while (v != u) { // truy vết ngược từ v về u
path.push_back(v);
v = trace[u][v];
}
path.push_back(u);

reverse(path.begin(), path.end()); // cần reverse vì đường đi từ v ngược về u

return path;
}

2.Bảng so sánh các thuật toán được đề cập:


Dijkstra (trên đồ thị
  Dijkstra (cơ bản) Floyd-Warshall
thưa)

Bài toán giải Đường đi ngắn Đường đi ngắn nhất một Đường đi ngắn
quyết nhất một nguồn nguồn nhất mọi cặp đỉnh

Độ phức tạp O(N2+M)O(N2+M) O(MlogN)O(MlogN) O(N3)O(N3)


Sử dụng được
Không Không Có
cho trọng số âm

Tìm được chu


Không Không Không
trình âm
 Trong trường hợp có chu trình âm, thuật toán Floyd-Warshall có thể phải tính
toán đến những giá trị rất nhỏ (về phía số âm), đủ để gây ra hiện tượng tràn số
(thậm chí với NN tương đối nhỏ). Cần phải chú ý đặc biệt đến trường hợp này khi
cài đặt.
o Một cách thường dùng để giải quyết trường hợp này là gán  D[u][v] =
max(D[u][v], -INF)  ngay sau mỗi lần tối ưu, chặn không
cho D[u,v]D[u,v] xuống dưới hằng số âm vô tận.
 Thuật toán Floyd-Warshall có thứ tự 3 vòng lặp là k→u→vk→u→v thay
vì u→v→ku→v→k (đỉnh trung gian phải được đặt ở vòng lặp ngoài cùng), đây là
một nhầm lẫn tương đối phổ biến khi cài đặt.
 Heap không phải là cấu trúc dữ liệu duy nhất có thể sử dụng khi cài đặt Dijkstra
dành cho đồ thị thưa. Ta có thể sử dụng bất cứ cấu trúc dữ liệu nào hỗ trợ các
thao tác "xóa khỏi tập hợp", "cập nhật phần tử trong tập hợp", "tìm phần tử nhỏ
nhất trong tập hợp". Thực tế cây nhị phân tìm kiếm ( std::set  trong C++) cũng là
một lựa chọn phổ biến khi cài đặt thuật toán này.

 Với đồ thị thưa, không có trọng số âm, thay vì sử dụng thuật toán Floyd, ta có thể
chạy thuật toán Dijkstra cải tiến NN lần với NN đỉnh nguồn để tìm đường đi ngắn
nhất giữa mọi cặp đỉnh, với độ phức tạp tốt hơn thuật toán Floyd:
o Ta có độ phức tạp của hai thuật toán:
 Dijkstra cải tiến, NN lần: O(N∗MlogN)O(N∗MlogN)
 Floyd-Warshall: O(N3)O(N3)
o Như vậy, để Dijkstra NN lần tốt hơn, ta cần
có N∗MlogN<N3N∗MlogN<N3 suy ra M<N2/logNM<N2/logN (tương
tự như so sánh giữa hai cách cài đặt thuật Dijkstra).
Ứng dụng:
Thuật toán Dijkstra dùng để tìm đường đi ngắn nhất từ đỉnh s đến các đỉnh còn lại trong đồ
thị . Được sử dụng cho đồ thị không có cung trọng số âm.
Thuật toán Floyd dùng để tìm đường đi ngắn nhất giữa tất cả các cặp đỉnh trong đồ thị. Sử
dụng được cho các đồ thị có cung trọng số âm.
Qua đó ta thấy khả năng áp dụng của thuật toán Floyd cao hơn Dijkstra.
 Một yếu tố khác biệt quan trọng khác giữa các thuật toán là hoạt
động của chúng đối với các hệ thống phân tán. Không giống như
thuật toán của Dijkstra, Floyd Warshall có thể được thực hiện trong
một hệ thống phân tán, làm cho nó phù hợp với các cấu trúc dữ
liệu như Đồ thị của Đồ thị (Được sử dụng trong Bản đồ).
 Cuối cùng, Floyd Warshall làm việc cho cạnh âm nhưng không
có chu kỳ âm , trong khi thuật toán Dijkstra không hoạt động cho
các cạnh âm.

A. Dijkstra
- Bài toán tìm đường đi ngắn nhất đến một địa điểm trên bản đồ.
- định tuyến đường đi của một hệ thống mạng - loại bài toán cơ
bản mà các kỹ sư mạng cần phải biết đến:
1) Nó được sử dụng trong Google Maps
- 2) Nó được sử dụng để tìm đường đi ngắn nhất.
- 3) Nó được sử dụng trong Bản đồ địa lý
- 4) Để tìm các vị trí của Bản đồ tham chiếu đến các đỉnh của đồ
thị.
- 5) Khoảng cách giữa các vị trí đề cập đến các cạnh.
- 6) Nó được sử dụng trong định tuyến IP để tìm Mở đường dẫn
ngắn nhất Đầu tiên.
- 7) Nó được sử dụng trong mạng điện thoại.
- Áp dụng trong logictis tìm tuyến đường vận chuyển hàng hóa ít tốt
chi phí nhất

Các ứng dụng của thuật toán Dijkstra


 .Ví dụ, nếu một người muốn đi từ thành phố A đến thành phố B, nơi cả hai
thành phố được kết nối với nhiều tuyến đường khác nhau. Anh ấy / cô ấy
nên chọn con đường nào?
Không còn nghi ngờ gì nữa, chúng tôi sẽ áp dụng tuyến đường mà chúng tôi
có thể đến đích với thời gian, khoảng cách và thậm chí là chi phí ít nhất có
thể. 
 
Hơn nữa, với cuộc thảo luận, nó có nhiều trường hợp sử dụng trong thế giới
thực khác nhau, một số ứng dụng như sau:
 
 Đối với các ứng dụng bản đồ  , nó được triển khai rộng rãi trong việc
đo khoảng cách ít nhất có thể và kiểm tra hướng giữa hai khu vực địa
lý như Google Maps, khám phá các vị trí bản đồ trỏ đến các đỉnh của
biểu đồ, tính toán lưu lượng truy cập và thời gian trễ, v.v.
 Đối với mạng điện thoại  , điều này cũng được thực hiện rộng rãi trong
việc truyền dữ liệu trong các lĩnh vực mạng và viễn thông để giảm trở
ngại xảy ra đối với việc truyền dẫn.
 Bất cứ nơi nào giải quyết nhu cầu về các phép thử đường đi ngắn nhất
trong lĩnh vực robot, vận tải, hệ thống nhúng, phòng thí nghiệm hoặc
nhà máy sản xuất, v.v., thuật toán này đều được áp dụng.
 Bên cạnh đó, các ứng dụng khác là điều kiện đường xá, đóng và xây
dựng đường, và định tuyến IP để phát hiện  Đường ngắn nhất Mở đầu
tiên  .

 
Ưu điểm và nhược điểm của thuật toán Dijkstra
1. Một trong những ưu điểm chính của nó là độ phức tạp nhỏ gần như
tuyến tính. 
 
Mặc dù có nhiều ứng dụng và ưu điểm khác nhau, thuật toán Dijkstra cũng
có những nhược điểm, chẳng hạn như;
 
1. Nó thực hiện một cuộc khám phá bị che khuất tiêu tốn nhiều thời gian
trong khi xử lý,
2. Nó không thể xử lý các cạnh tiêu cực,
3. Khi nó hướng đến biểu đồ xoay vòng, vì vậy không thể đạt được
đường đi ngắn nhất chính xác và
4. Ngoài ra, cần phải duy trì theo dõi các đỉnh, đã được thăm.

https://www.analyticssteps.com/blogs/dijkstras-algorithm-shortest-path-algorithm
B. Floyd
Tìm ra ma trận Transitve Closure của đồ thị có hướng

 Thuật toán Floyd Warshall giúp tìm ra sự nghịch đảo của ma trận thực
 Nó giúp kiểm tra xem biểu đồ vô hướng có phải là lưỡng phân hay không
 Nó giúp tìm đường đi ngắn nhất trong biểu đồ có hướng
 Các phiên bản khác nhau của thuật toán Floyd Warshall giúp tìm ra điểm
đóng bắc cầu của một biểu đồ có hướng
 Thuật toán này giúp tìm biểu thức chính quy được các ô tự động hữu hạn
chấp nhận.
 Nó giúp tìm ra sự giống nhau giữa các biểu đồ
 Thuật toán Floyd Warshall giúp tìm ra định tuyến tối ưu tức là luồng tối đa
giữa hai đỉnh

o https://www.adamconrad.dev/blog/shortest-paths/
o Đường đi ngắn nhất tất cả các cặp: Tính toán đường đi ngắn nhất giữa mọi cặp đỉnh
trong đồ thị có hướng.
o Phát hiện chu kỳ trọng số âm trong đồ thị. (Nếu có chu kỳ trọng số âm, khoảng cách
từ nút bắt đầu đến chính nó sẽ âm sau khi chạy thuật toán).
 việc tìm các con đường khả thi để đi bằng ô tô với một thùng nhiên liệu hạn chế và
các trạm dừng nghỉ ở mọi nút.
cố gắng tối đa hóa tải trọng mà một xe tải chở hàng có thể chịu khi các con đường
dọc theo lối đi có thể có giới hạn trọng lượng hoặc cố gắng tìm một đường dẫn định
tuyến mạng đáp ứng yêu cầu băng thông tối thiểu cho một số ứng dụng

o Con đường an toàn nhất: Tương tự trong việc xây dựng Floyd-Warshall đến minimax
và maximin - Cần tối đa hóa tích các xác suất sống sót trên một con đường. Đơn giản
chỉ cần thay đổi giá trị tối đa thành tối thiểu để tìm ra con đường nguy hiểm nhất.
Thuật toán có thể được sử dụng để giải quyết một số vấn đề bao gồm đường đi
ngắn nhất trong đồ thị có hướng , đảo ngược ma trận thực, định tuyến tối ưu
và tính toán nhanh mạng Pathfinder

Thuật toán Floyd-Warshall


Với hầu hết các vấn đề về đồ thị này cho đến nay, các ví dụ của chúng
tôi dẫn chúng ta đến việc chọn các đỉnh ở các đầu bên ngoài của đồ
thị, giống như cách chúng ta bắt đầu với nút gốc của cây. Nhưng nếu
bạn muốn bắt đầu từ giữa chừng thì sao? Điều gì sẽ xảy ra nếu bạn
muốn biết đỉnh có vị trí trung tâm nhất trong biểu đồ? Trên thực tế, ví
dụ đầu tiên tôi có thể nghĩ đến là Sim City.

Trong Sim City, “mục tiêu” (tôi đặt trong dấu ngoặc kép vì trò chơi
kết thúc mở và không có kết thúc khách quan thực sự) là tạo ra một
thành phố sôi động, hạnh phúc của con người, hay còn gọi là
“sims”. Về cơ bản nó là một mô phỏng cô đọng trong quy hoạch đô
thị. Bạn phải cung cấp cho mọi người nguồn điện cho ngôi nhà của họ,
đường để họ đi đến nơi làm việc (và nơi làm việc), và tất cả các tiện
nghi mà một thành phố địa phương cần như trường học, đồn cảnh sát
và công viên. Nhưng bạn đặt tất cả những thứ này ở đâu để khiến mọi
người hạnh phúc?

Đối với nhiều tòa nhà, như đồn cảnh sát, chúng chỉ có thể hoạt động
trong một bán kính nhất định để ngăn chặn tội phạm một cách hiệu
quả trước khi quá muộn. Một cách hợp lý, nếu bạn đặt một đồn cảnh
sát ở rìa thị trấn và một người nào đó phạm tội ở đầu bên kia, sẽ mất
nhiều thời gian hơn để xe cảnh sát đến hiện trường so với nếu nó được
đặt ở trung tâm.
Và vì các thành phố trong Sim City có thể khá lớn, nên chỉ đặt một
đồn cảnh sát ở giữa bản đồ và hy vọng điều tốt nhất là không đủ. Bạn
sẽ cần một số trạm để bao phủ toàn bộ bản đồ. Và bản đồ của bạn,
giống như thế giới thực, không chỉ đơn giản là một lưới vuông gồm cỏ
và đồng bằng phẳng. Các đặc điểm tự nhiên như sông, đại dương và
núi có thể làm phức tạp cách một trạm có thể cảnh sát khu vực một
cách hiệu quả.

Thật may mắn cho bạn, có một thuật toán được gọi là Floyd-
Warshall có thể tìm ra một cách khách quan vị trí tốt nhất để đặt các
tòa nhà của bạn bằng cách tìm đường đi ngắn nhất cho tất cả các
cặp . Nói cách khác, tại mọi đỉnh, chúng ta có thể bắt đầu từ việc tìm
đường đi ngắn nhất trên đồ thị và xem mất bao lâu để đi đến mọi đỉnh
khác. Mỗi lần chúng ta bắt đầu lại, chúng ta giữ lại số điểm của tổng
số lượt di chuyển cần thiết cho mỗi đỉnh. Khoảng cách trung bình gần
nhất sẽ đến từ đỉnh trung tâm đó, chúng ta có thể tính toán bằng ma
trận kề. Và vì bây giờ chúng tôi đang thêm một lớp kiểm tra mọi đỉnh
khác trên đỉnh của cái về cơ bản là của Dijkstra, nên thuật toán này sẽ
chạy  O(n^3) đúng lúc.
Floyd-Warshall không thực sự tạo ra một giá trị trả về kỳ lạ của vị trí
tối ưu. Thay vào đó, nó trả về ma trận khoảng cách với tất cả các
đường đi tối ưu được vạch ra, thường là đủ cho hầu hết các vấn đề
thuộc phạm vi này. Mặc dù thời gian khối có vẻ chậm, nhưng thực tế
là thuật toán này chạy nhanh trong thực tế, một phần vì nó sử dụng ma
trận kề để xử lý ánh xạ tất cả các giá trị khoảng cách của nó (một trong
những trường hợp hiếm hoi mà chúng tôi đã đề cập ban đầu là ma trận
kề là cấu trúc dữ liệu tốt hơn danh sách kề). Nó cũng giúp thuật toán
cũng dễ triển khai:

https://vnoi.info/wiki/algo/graph-theory/shortest-path.md?
fbclid=IwAR0gtDIkDmXDVjSRnDYt44mTZopGc5R4fizrFykzO0c2ASSKNohJ9vfOSis#2-
thu%E1%BA%ADt-to%C3%A1n-dijkstra

You might also like