Thuật toán Cốt lõi — Tìm kiếm, Sắp xếp & Đồ thị/Dijkstra — Shortest path trên weighted graph với min-heap
29/36
Bài 29 / 36~25 phútĐường đi & quan hệ — Graph algorithmsMiễn phí lượt xem

Dijkstra — Shortest path trên weighted graph với min-heap

Dijkstra tìm shortest path weighted graph: relaxation, min-heap lazy deletion O((V+E) log V), fail với edge âm — pattern Google Maps và OSPF routing áp dụng.

TL;DR: Dijkstra tìm shortest path trên weighted graph (mọi cạnh có weight không âm) bằng cách luôn mở rộng đỉnh có tổng cost nhỏ nhất chưa finalized — một quyết định tham lam (greedy) đúng đắn nhờ không có cạnh âm. Cấu trúc dữ liệu cốt lõi là min-heap (priority queue); mỗi lần cải thiện dist của đỉnh v, push entry mới vào heap và bỏ qua entry cũ khi pop (lazy deletion). Complexity O((V+E) log V). Pitfall lớn nhất: Dijkstra FAIL khi có bất kỳ cạnh âm nào — kể cả không có negative cycle — vì greedy invariant bị phá vỡ; dùng Bellman-Ford thay thế.

Bạn đã biết BFS tìm shortest path trên unweighted graph (lesson 02) — mỗi cạnh có weight bằng 1, BFS đảm bảo path ít cạnh nhất. Nhưng Google Maps không đếm số ngã tư — nó tính thời gian di chuyển. Đường cao tốc từ Hà Nội đến Bắc Ninh có thể nhanh hơn đường làng dù nhiều "ngã rẽ" hơn. Mỗi cạnh trong road network có weight riêng: travel time, distance, hoặc cost.

BFS trên weighted graph cho kết quả sai hoàn toàn — nó tối ưu theo số cạnh, không theo tổng weight. Cần một algorithm khác.

Bài này dạy Dijkstra: relaxation principle, min-heap implementation O((V+E) log V), vì sao FAIL với edge âm, và pattern Google Maps / OSPF routing dùng.

1. Analogy — Lan tỏa wave với tốc độ khác nhau

BFS giống sóng nước trên mặt hồ phẳng — sóng lan đều mọi hướng, mỗi "bước" là một cạnh có cost bằng nhau.

Dijkstra giống sóng lan trên địa hình gồ ghề: đường rộng (weight nhỏ) sóng lan nhanh hơn, đường núi (weight lớn) sóng lan chậm hơn. Đỉnh nào bị sóng chạm trước thì tổng cost từ source đến đó là nhỏ nhất.

Greedy core: tại mỗi bước, Dijkstra luôn "expand" đỉnh gần source nhất (theo tổng cost) chưa được finalize — đây là quyết định locally optimal, và với graph không có edge âm, nó cho kết quả globally optimal.

Sóng trên địa hìnhDijkstra
Điểm xuất phátstart đỉnh
Địa hình bằng phẳng — đường dễCạnh weight nhỏ
Địa hình núi — đường khóCạnh weight lớn
Sóng chạm điểm X sau cost Cdist[X] = C — shortest distance từ start đến X
Điểm bị chạm trước = tổng cost nhỏ nhấtĐỉnh được finalize = shortest path confirmed
Sóng KHÔNG thể đi ngược (weight âm)Dijkstra FAIL khi có edge âm
💡 Cách nhớ

Dijkstra = BFS generalized cho weighted graph. Thay Queue bằng MinHeap (theo dist). Mỗi lần pop = finalize đỉnh gần nhất.

2. Thuật toán — Relaxation principle

2.1 Distance estimate và relaxation

dist[v] là ước tính tốt nhất hiện tại về shortest distance từ start đến v. Ban đầu:

  • dist[start] = 0
  • dist[v] = INF cho mọi v khác

Relaxation: với mỗi cạnh u → v có weight w, kiểm tra:

if dist[u] + w < dist[v]:
    dist[v] <- dist[u] + w    -- cập nhật đường ngắn hơn
    push (v, dist[v]) vào MinHeap

Tên "relaxation" từ ý tưởng: ta đang "nới lỏng" ước tính dist của v xuống giá trị tốt hơn.

2.2 Greedy invariant

Khi pop đỉnh udist[u] nhỏ nhất từ MinHeapdist[u] là final shortest distance từ start đến u.

Proof intuition: giả sử tồn tại path ngắn hơn đến u. Path đó phải đi qua một đỉnh khác w chưa finalized. Nhưng dist[w] >= dist[u] (ta đang pop min) nên path đó có tổng cost ít nhất là dist[w] >= dist[u] — mâu thuẫn với "ngắn hơn". Điều này chỉ đúng khi tất cả edge weight không âm.

2.3 Lazy deletion pattern

Nhiều ngôn ngữ (Java, Python) không cung cấp min-heap có decrease-key trực tiếp. Thay vào đó dùng lazy deletion:

  • Khi dist của v được cải thiện, push entry mới (v, newDist) vào heap — entry cũ vẫn còn.
  • Khi pop entry (u, d): nếu d > dist[u], entry này là outdated (dist đã được update về giá trị tốt hơn) → skip.
if d > dist[u]: continue   -- entry cũ (lazy deletion), bỏ qua

Lazy deletion đơn giản hơn nhiều so với indexed heap nhưng heap có thể chứa O(E) entries thay O(V). Với sparse graph (E ≈ V log V), không đáng kể.

3. Pseudocode đầy đủ

Dijkstra(G, start):
    dist[v] <- INF cho mọi v
    dist[start] <- 0
    parent[v] <- NIL cho mọi v

    H <- MinHeap rỗng        -- keyed by dist
    H.push((start, 0))

    while H không rỗng:
        (u, d) <- H.pop()    -- lấy đỉnh có dist nhỏ nhất

        if d > dist[u]: continue   -- entry cũ (lazy deletion), bỏ qua

        for each cạnh (u -> v, trọng số w) trong G:
            newDist <- dist[u] + w
            if newDist < dist[v]:
                dist[v] <- newDist
                parent[v] <- u
                H.push((v, newDist))

    return dist, parent
    -- dist[v] = INF nếu v không thể tới từ start

Time: O((V+E) log V) Space: O(V+E)

flowchart TD
    INIT["dist[start]=0, đẩy (start,0) vào heap"]
    POP["Lấy (u, d) từ MinHeap"]
    SKIP{"d > dist[u]?"}
    RELAX["Relax mỗi cạnh u->v:\nnewDist = dist[u] + w\nnếu ngắn hơn: cập nhật dist[v], đẩy (v,newDist)"]
    DONE["Trả về dist[], parent[]"]

    INIT --> POP
    POP --> SKIP
    SKIP -- "có (entry cũ)" --> POP
    SKIP -- "không" --> RELAX
    RELAX --> POP
    POP -- "heap rỗng" --> DONE

3.1 Trace ví dụ — graph 5 đỉnh

Graph directed weighted:

    (2)      (3)
0 ----> 1 ----> 3
|       |       ^
|(4)   (1)     |(1)
v       v      |
2 ----> 4 -----+
    (2)    (1)

Cạnh: 0→1 w2, 0→2 w4, 1→3 w3, 1→4 w1, 2→4 w2, 4→3 w1.

BướcPop (dinh, dist)dist[]Heap sau pop
Init[0, INF, INF, INF, INF][(0,0)]
1(0, 0)[0, 2, 4, INF, INF][(1,2),(2,4)]
2(1, 2)[0, 2, 4, 5, 3][(4,3),(2,4),(3,5)]
3(4, 3)[0, 2, 4, 4, 3][(3,4),(2,4),(3,5)]
4(3, 4) — finalized[0, 2, 4, 4, 3][(2,4),(3,5)]
5(2, 4)[0, 2, 4, 4, 3][(3,5)] — 4 khong cai thien
6(3, 5) — outdated, dist[3]=4skip[]

Kết quả: dist = [0, 2, 4, 4, 3]. Shortest path từ 0 đến 3: cost 4 qua 0→1→4→3.

4. Path reconstruction

Theo dõi thêm mảng parent[] trong quá trình relaxation để reconstruct path thực tế.

reconstruct_path(parent, start, target):
    if dist[target] = INF: return []   -- không thể tới

    path <- []
    cur <- target
    while cur != NIL:
        path.prepend(cur)
        cur <- parent[cur]
    return path   -- thứ tự từ start đến target

Time: O(V) Space: O(V)

Vì sao prepend (hoặc reverse)? Ta đi ngược từ target về start qua chuỗi parent[]. Nếu dùng append và reverse, kết quả giống nhau. Nếu dùng prepend (insert đầu), list đã đúng thứ tự.

5. Complexity

ImplementationTimeSpaceGhi chú
Binary heap MinHeapO((V+E) log V)O(V+E)Standard production choice
Array-based (no heap)O(V²)O(V)Tốt hơn khi dense graph (E ≈ V²)
Fibonacci heapO(E + V log V)O(V)Better asymptotic, constants lớn, hiếm dùng

Binary heap O((V+E) log V) từ đâu?

  • Mỗi đỉnh được pop tối đa một lần: V lần pop, mỗi lần log(heap size) ≈ log V → V log V.
  • Mỗi cạnh có thể trigger một push vào heap: E lần push, mỗi lần log V → E log V.
  • Tổng: O(V log V + E log V) = O((V + E) log V).

Array-based O(V²): không dùng heap — mỗi lần tìm đỉnh unvisited có dist nhỏ nhất bằng linear scan O(V), lặp V lần → O(V²). Với dense graph E = O(V²), array-based thắng vì không overhead heap.

6. Vì sao FAIL với negative edge

6.1 Greedy assumption bị phá vỡ

Dijkstra dựa trên invariant: khi pop đỉnh u (vì dist[u] nhỏ nhất trong heap), dist[u] đã là final — không edge nào sau đó cải thiện được nữa. Invariant này chỉ đúng khi mọi edge không âm: thêm một cạnh chỉ tăng hoặc giữ nguyên cost. Một cạnh âm phá vỡ giả định này — một path "dài hơn" tới u (nhiều cạnh hơn) vẫn có thể rẻ hơn nhờ cạnh âm.

Counter-example (một ví dụ đủ để thấy sai):

        (1)
   A ---------> B
   |            ^
  (3)         (-5)
   |            |
   +----> C ----+

Cạnh: A→B weight 1, A→C weight 3, C→B weight -5. Shortest path thực tới B là A→C→B = 3 + (-5) = -2, không phải đường trực tiếp A→B = 1.

Dijkstra (finalize khi pop): dist = [0, INF, INF]. Pop A (0) → relax B=1, C=3. Heap: [(B,1),(C,3)]. Pop B (dist 1, nhỏ hơn C) → finalize B=1. Pop C (3) → relax B: 3 + (-5) = -2 < 1 — nhưng B đã finalized → bỏ qua. Kết quả báo dist[B] = 1, trong khi đáp án đúng là -2. Sai.

Gốc rễ: Dijkstra finalize B trước khi khám phá đường vòng qua C, vì greedy tin "đã nhỏ nhất thì không thể nhỏ hơn". Với cạnh âm, niềm tin đó sai.

⚠️ Bản lazy-deletion có 'cứu' được không?

Bản Dijkstra dùng lazy deletion trong bài này (if d > dist[u]: continue, không có mảng visited) sẽ re-relax B khi pop C, nên trên ví dụ nhỏ này nó tình cờ ra -2 đúng. Nhưng đó không phải bảo đảm: với cạnh âm, Dijkstra mất bất biến nền tảng — có graph nó vẫn cho kết quả sai, và việc re-relax lặp lại có thể nổ ra số bước mũ. Quy tắc an toàn duy nhất: có cạnh âm → dùng Bellman-Ford, đừng dựa vào may rủi của implementation.

6.2 Negative cycle — không có shortest path

Nếu graph có negative cycle (cycle với tổng weight âm), không tồn tại shortest path — ta có thể đi vòng quanh cycle vô hạn lần, giảm total cost mãi.

Dijkstra không detect được negative cycle (sẽ loop hoặc cho kết quả sai). Dùng Bellman-Ford (lesson 06) để handle cả negative edge và detect negative cycle.

⚠️ Khi nào cần Bellman-Ford thay Dijkstra

Nếu graph có bất kỳ edge âm nào — dù không có negative cycle — Dijkstra có thể cho kết quả sai. Không phải chỉ khi có negative cycle. Luôn dùng Bellman-Ford khi edge weight có thể âm.

7. Pitfall tổng hợp

Pitfall 1 — Nhầm "negative weight" với "negative cycle"

-- BUG assumption: "chỉ có negative cycle mới sai, negative edge thường thì OK"
-- Dijkstra sai ngay cả khi chỉ có 1 negative edge, không có cycle
-- Test case: A->B w1, A->C w3, C->B w(-3)
-- Dijkstra cho dist[B]=1, correct là A->C->B = 3+(-3)=0
-- Nhưng 0 < 1 mà Dijkstra finalize B=1 trước khi expand C
-- => Phải dùng Bellman-Ford

Pitfall 2 — Quên lazy skip pattern

-- BUG: push duplicate vào heap nhưng không skip entry cũ
while H không rỗng:
    (u, d) <- H.pop()
    -- THIẾU: if d > dist[u]: continue

    for each cạnh (u -> v, w):
        newDist <- dist[u] + w
        if newDist < dist[v]:
            dist[v] <- newDist
            H.push((v, newDist))
            -- entry cũ (v, oldDist) vẫn còn trong heap
            -- không skip -> sẽ re-relax neighbor với dist cũ
            -- tốn thêm thời gian do re-relax entry cũ
            -- (kết quả vẫn đúng nếu relax dùng dist[u] từ mảng,
            --  chỉ sai nếu implementation relax bằng d của entry
            --  thay vì dist[u] từ mảng)

Luôn có dòng if d > dist[u]: continue ngay sau H.pop(). Thiếu dòng này không nhất thiết cho kết quả sai (nếu relax dùng dist[u] từ mảng), nhưng gây re-relax không cần thiết, tốn thêm thời gian — và dễ dẫn đến lỗi nếu implementation vô tình dùng d thay vì dist[u] khi relax.

Pitfall 3 — Overflow khi cộng INF với weight

-- BUG: dist[u] = INF (giả sử là MAX_INT), w = 5
-- MAX_INT + 5 tràn số (overflow) -> số âm -> sai hoàn toàn
newDist <- dist[u] + w   -- overflow -> negative -> wrong comparison
if newDist < dist[v]: ...   -- luôn true -> làm hỏng dist array
-- CORRECT: guard trước khi cộng
if dist[u] = INF: continue   -- không thể đi tới u, bỏ qua
newDist <- dist[u] + w

Hoặc dùng kiểu số 64-bit (long) cho dist[] và weight, tránh overflow hoàn toàn.

8. Variants và optimizations

A* (A-star): Dijkstra cộng thêm heuristic h(v) ước tính distance từ v đến goal. Heap key = dist[v] + h(v). Nếu hadmissible (không overestimate) — ví dụ Euclidean distance trên grid — A* tìm optimal path nhanh hơn Dijkstra vì ưu tiên expand theo hướng goal. Dùng trong game pathfinding (NPC), routing có endpoint rõ ràng.

Bidirectional Dijkstra: chạy Dijkstra từ start tiến về phía trước và từ goal tiến ngược lại. Dừng khi hai frontier gặp nhau. Chi phí giảm từ O(b^d) xuống O(b^(d/2)), trong đó b là branching factor. Đây là technique Google Maps dùng cho large road network.

Contraction Hierarchy: pre-process graph bằng cách "contract" đỉnh ít quan trọng, tạo shortcut edge. Query time dưới 10ms cho graph 1 triệu đỉnh. Production standard cho realtime routing.

k-shortest paths (Yen's algorithm): tìm K path ngắn nhất, không chỉ một. Dùng khi muốn backup routes (nếu đường 1 tắc, dùng đường 2, 3...).

9. Quy mô lớn — graph 1 triệu đỉnh

Dijkstra full V × log V operations trong heap. Với V = 1 triệu, E = 5 triệu:

  • Heap operations: (V + E) × log V ≈ 6M × 20 = 120 triệu operations.
  • Mỗi operation khoảng 100ns → khoảng 12 giây. Quá lâu cho realtime routing.

Production solution: pre-compute với Contraction Hierarchy + bidirectional Dijkstra. Query time dưới 10ms cho full road network châu Âu (hàng chục triệu đỉnh). Google Maps, HERE Maps, OpenStreetMap Valhalla đều dùng approach này.

Offline batch routing (tìm shortest path cho 1000 request/s không cần realtime): Dijkstra naive đủ nếu scale ngang (nhiều worker xử lý song song).

10. Ứng dụng thực tế

Google Maps / OpenStreetMap routing: Dijkstra với edge weight = travel time (tính từ speed limit × distance). Bidirectional Dijkstra + Contraction Hierarchy cho realtime. A* với Euclidean heuristic cho grid-based map. Edge weight thay đổi theo giờ (giờ cao điểm) → time-dependent Dijkstra với edge weight là function của thời gian.

OSPF (Open Shortest Path First) — Internet routing: mỗi router chạy Dijkstra trên link-state database (graph topology toàn mạng) để tính shortest path tree từ nó đến mọi router khác. Edge weight = link cost (bandwidth, latency). Khi topology thay đổi (router down, link cost change), router cập nhật database và re-run Dijkstra. Đây là lý do OSPF có tên "Shortest Path First" — Dijkstra là core algorithm.

Game AI pathfinding: A* (Dijkstra + heuristic) cho NPC tìm đường trên grid map. Edge weight = terrain cost (đường, rừng, nước có cost khác nhau). Bidirectional A* cho map lớn (open-world game). Dijkstra baseline trước khi thêm heuristic.

Public transit routing (Citymapper, Google Transit): Dijkstra với edge weight = travel time + transfer penalty. Graph phức tạp hơn road network — đỉnh là (station, time) combination (time-expanded graph). Dijkstra trên time-expanded graph tìm fastest itinerary kể cả transfer time và departure schedule.

Telegram / messaging delivery path: trong distributed system, tìm shortest hop từ sender đến recipient qua network topology. Edge weight = latency. Dijkstra trên network graph cho routing table tối ưu.

11. Deep Dive

📚 Deep Dive — tài liệu tham khảo

Bài báo và sách kinh điển:

  • Dijkstra, E.W. (1959) — "A note on two problems in connexion with graphs": bài báo gốc (2.5 trang) giới thiệu algorithm và shortest spanning subtree. Ngắn và đọc được.
  • Introduction to Algorithms (CLRS), Chapter 24.3 — Dijkstra's Algorithm: formal proof greedy correctness, complexity với binary heap và Fibonacci heap.
  • Goldberg & Harrelson (2005) — "Computing the Shortest Path: A* Search Meets Graph Theory": A* với landmarks + triangle inequality, production routing technique.

Production engineering:

  • "Engineering Route Planning Algorithms" (Delling et al., 2009) — survey Contraction Hierarchy, bidirectional Dijkstra, và các technique Google Maps-class system dùng.
  • OpenStreetMap Valhalla source code — open-source routing engine với Dijkstra + CH implementation thực tế.

Cross-link trong khóa học:

  • Module 3, Lesson 02 — BFS: shortest path trên unweighted graph, nền tảng để hiểu tại sao cần Dijkstra.
  • Module 3, Lesson 06 — Bellman-Ford: handle negative edge, detect negative cycle.
  • Module 3, Lesson 11 — Case study Google Maps + Maven: Dijkstra trong production routing.
  • Thuật toán Căn bản — Module 2, Lesson 06 — Hàng đợi ưu tiên (priority queue): cơ chế binary heap mà Dijkstra phụ thuộc.
  • Module 2, Lesson 05 — Heap và heapsort: heap internals, log V per operation.

12. Tóm tắt

  • Dijkstra tìm shortest path trên weighted graph bằng cách luôn expand đỉnh có dist nhỏ nhất chưa finalized — greedy invariant đúng khi mọi edge weight không âm.
  • Relaxation: với cạnh u → v weight w, nếu dist[u] + w < dist[v] thì update dist[v] và push entry mới vào heap.
  • Lazy deletion pattern: heap thường không có decrease-key — push duplicate, skip outdated với if d > dist[u]: continue.
  • Complexity O((V+E) log V): mỗi đỉnh pop một lần V log V, mỗi cạnh có thể trigger push E log V.
  • FAIL với negative edge: greedy invariant sai — đỉnh đã finalized có thể bị cải thiện bởi edge âm. Dùng Bellman-Ford khi có edge âm.
  • Path reconstruction qua parent[] array — đi ngược từ target về start.
  • Variants: A* thêm heuristic cho goal-directed search, Bidirectional Dijkstra giảm search space, Contraction Hierarchy cho production realtime routing.
  • Ứng dụng: Google Maps routing, OSPF Internet routing, game AI pathfinding, public transit planning.

13. Tự kiểm tra

Tự kiểm tra
Q1
Vì sao Dijkstra FAIL với edge âm? Cho counter-example cụ thể.

Dijkstra dựa trên greedy invariant: khi pop đỉnh u với dist[u] nhỏ nhất, đó là final shortest distance. Invariant này chỉ đúng khi mọi edge có weight không âm — vì không có cách nào "cải thiện" path đến u sau khi finalize (mọi edge tiếp theo chỉ tăng hoặc giữ nguyên cost).

Với edge âm: graph A→B weight 1, A→C weight 3, C→B weight -5. Dijkstra pop A → dist[B]=1, dist[C]=3. Pop B (dist 1, nhỏ hơn C) → finalize B=1. Pop C → relax B: 3+(-5)=-2, nhưng B đã finalized → kết quả sai. Shortest path thực là A→C→B với cost -2, Dijkstra báo 1.

Q2
MinHeap lazy skip — vì sao cần dòng if d > dist[u]: continue?

Heap thường không support decrease-key. Khi dist của đỉnh v được cải thiện, ta push entry mới (v, newDist) vào heap nhưng entry cũ (v, oldDist) vẫn còn trong heap.

Khi entry cũ được pop sau này, d = oldDist > dist[v] = newDist — dist thực của v đã được update tốt hơn. Nếu không skip, ta sẽ re-relax tất cả neighbor của v với dist stale thay vì dist mới — có thể ghi đè dist tốt hơn bằng giá trị tệ hơn, cho kết quả sai. Skip dòng này là bug tinh tế: output có thể đúng trên nhiều input nhưng sai trên một số graph topology cụ thể.

Q3
Complexity O((V+E) log V) — log V đến từ đâu?

log V là chi phí của mỗi heap operation (push/pop) trên MinHeap với kích thước tối đa O(V) hoặc O(E) entries.

Có hai nguồn operations: (1) Mỗi trong V đỉnh được pop tối đa một lần: V × log V. (2) Mỗi trong E cạnh có thể trigger một push mới: E × log V (lazy deletion nên heap có thể chứa tới O(E) entries, heap operation là log(E) = log(V²) = 2 log V = O(log V)). Tổng: O((V+E) log V).

Q4
A* vs Dijkstra — heuristic nào đảm bảo correctness? Khi nào A* không cho optimal path?

A* đảm bảo optimal path khi heuristic h(v)admissible: không được overestimate distance thực từ v đến goal. Ví dụ admissible: Euclidean distance trên 2D grid (đường thẳng luôn ngắn hơn đường thực đi được), Manhattan distance trên grid chỉ có 4 hướng.

A* cho kết quả không optimal khi heuristic overestimate — ví dụ heuristic đơn giản "random noise" hoặc estimate quá cao. Trong game nếu dùng heuristic lớn hơn actual cost để chạy nhanh hơn (greedy best-first search), tìm đường nhanh nhưng không đảm bảo shortest. Trade-off giữa speed và optimality.

Q5
Path reconstruction qua parent[] — vì sao cần reverse (hoặc prepend)? Điều gì xảy ra nếu quên?

Reconstruction đi ngược từ target về start qua chuỗi parent[cur]: target → parent[target] → ... → start. List thu được có thứ tự ngược — target ở đầu, start ở cuối.

Nếu dùng append và quên reverse, trả về path ngược: [target, ..., start] thay vì [start, ..., target]. Navigation app sẽ hướng dẫn người dùng đi từ đích về điểm xuất phát — sai hoàn toàn về chiều. Bug này dễ bỏ qua trong unit test nếu path chỉ có 2 đỉnh (start và target — reverse không đổi gì).

Q6
Graph 1 triệu đỉnh cho realtime routing — Dijkstra naive vs bidirectional Dijkstra + A* về thực tế diff?

Dijkstra naive: worst-case phải explore gần như toàn bộ graph — V log V ≈ 20 triệu heap operations. Với 100ns/op ≈ 2 giây per query. Không đáp ứng yêu cầu realtime (dưới 100ms per query cho navigation app).

Bidirectional Dijkstra + A*: Bidirectional giảm search space từ O(b^d) xuống O(b^(d/2)) — với branching factor 4 và depth 20, giảm từ 4^20 xuống 2×4^10, tức hơn 1000 lần. A* thêm heuristic hướng search về phía goal, giảm thêm. Kết hợp với Contraction Hierarchy (pre-compute shortcut), Google Maps đạt query time dưới 10ms trên graph hàng chục triệu đỉnh — nhanh hơn Dijkstra naive khoảng 100-200 lần trong thực tế.

Bài tiếp theo: Bellman-Ford — handle negative weight

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên