DSA DSA In Depth
0%
lesson astarheuristicspathfinding

A* Search & Bidirectional A*: Admissibility, Consistency (Triangle Inequality) & Optimality Proof

7 min read Module 5.2: Disjoint Sets, Minimum Spanning Trees & Shortest Paths Updated Oct 23, 2026
📄 Cheat Sheet
A* (A Star) Search Algorithm — Admissible Heuristics
Computerphile A* (A Star) Search Algorithm — Admissible Heuristics Watch on YouTube ↗

Demonstrates how heuristic potential functions focus Dijkstra's uniform wave into a directed beam toward the target.

0:00 / --:--

Admissibility vs. Consistency (Monotonicity)

Let $\delta(u, t)$ denote the true shortest-path distance from vertex $u$ to target $t$. The behavior and complexity of A* depend on two hierarchy conditions on the heuristic function $h: V \to \mathbb{R}_{\ge 0}$ (with $h(t) = 0$):

Formal Definitions & Equivalence to Potential Reweighting
  1. Admissibility (Never Overestimates): $h$ is admissible if for all $u \in V$: $$0 \le h(u) \le \delta(u, t)$$ Admissibility guarantees that when target $t$ is popped from the priority queue, $g(t) = \delta(s, t)$ is optimal (since $f(t) = g(t) + 0$, and any alternative path through an open node $x$ has $f(x) = g(x) + h(x) \le g(x) + \delta(x, t)$).
  2. Consistency / Monotonicity (Triangle Inequality): $h$ is consistent if $h(t) = 0$ and for every directed edge $(u, v) \in E$: $$h(u) \le w(u, v) + h(v) \iff w_h(u, v) := w(u, v) - h(u) + h(v) \ge 0$$ Every consistent heuristic is automatically admissible (by telescoping along any shortest path $u \leadsto t$). Crucially, under consistency, any path $P: s \leadsto u$ has reduced weight $w_h(P) = w(P) - h(s) + h(u) = f(u) - h(s)$. Since $w_h(u, v) \ge 0$, A* is literally Dijkstra's algorithm on $w_h$, guaranteeing every vertex is popped and expanded at most once!

Canonical Grid & Geometric Heuristics

  • 4-Directional Grid (Up/Down/Left/Right, unit cost): Manhattan distance $h(u) = |x_u - x_t| + |y_u - y_t|$ is both admissible and consistent.
  • 8-Directional Grid (King moves cost $1$): Chebyshev distance $h(u) = \max(|x_u - x_t|, |y_u - y_t|)$. (If diagonal moves cost $\sqrt{2}$, use Octile distance: $\Delta_{\max} + (\sqrt{2} - 1)\Delta_{\min}$).
  • Continuous 2D/3D Euclidean Space or Road Networks: Euclidean distance $h(u) = \|p_u - p_t\|_2 / v_{\max}$ (straight-line distance divided by the maximum legal highway speed).
Dijkstra (h = 0): Isotropic Circle A* (Consistent h): Focused Ellipsoid s t s t Only nodes with g(u) + h(u) ≤ δ(s, t) are expanded
flowchart LR Heuristic["Heuristic h(u)"] --> CheckCons{"h(u) <= w(u,v) + h(v)?"} CheckCons -- "Yes (Consistent)" --> Once["Reduced weights w_h >= 0: Each node expanded <= 1 time!"] CheckCons -- "No, only h(u) <= dist(u,t)" --> Exp["Admissible but Inconsistent: Nodes may re-open O(2^V) times!"] CheckCons -- "h(u) > dist(u,t)" --> Weighted["Inadmissible (Weighted A*): Fast, bounded suboptimal"]
Shortest Paths: Dijkstra, A* Consistent Potentials & OSRM Contraction Hierarchies
O((V+E) log V) · A* f=g+h · CH Shortcuts

Compare Dijkstra’s uniform radial frontier against A* directional pruning f(u) = g(u) + h(u) and OSRM Contraction Hierarchies (shortcut insertion w(u,w)=w(u,v)+w(v,w) + upward rank-monotonic bidirectional search).

Step 1 / 8
Custom Input:
Timeline

Invariant & State

Pop S (g=0, h=6): relax B (g=2, h=4 ⇒ f=6) and C (g=5, h=4 ⇒ f=9). Admissible heuristic h(u) ≤ d(u,T) pulls B ahead of C!
w=2 w=5 w=2 w=2 w=4 S g=0,f=6 B g=2,f=6 C g=5,f=9 D g=∞ T g=∞
Priority Queue
[(B, f=6), (C, f=9)]
Pruned Detour
Vertex C (f=9 > d*(T)=6)
Consistency
w_h(u,v) = w(u,v) + h(v) - h(u) ≥ 0
[Step 1] Pop S (g=0, h=6): relax B (g=2, h=4 ⇒ f=6) and C (g=5, h=4 ⇒ f=9). Admissible heuristic h(u) ≤ d(u,T) pulls B ahead of C!
Hardware Reality — Avoiding std::hypot / sqrt Latency in Inner Loops

On x86-64/ARM64, 64-bit floating-point sqrt has 12–20 cycles throughput latency and converts integer distances into floats. On 4-connected or 8-connected grids, always use exact integer heuristics (Manhattan $|dx| + |dy|$ or scaled Octile $1000(\Delta_{\max}) + 414(\Delta_{\min})$) so priority queue keys remain 64-bit integers compared in a single ALU cycle.

Complexity Matrix

Heuristic Class Guarantees Optimal Path? Max Expansions per Vertex Worst-Case Time
Zero Heuristic ($h(u) = 0$) Yes (Standard Dijkstra) 1 O((V + E) log V)
Consistent ($h(u) \le w(u,v) + h(v)$) Yes 1 O((V + E) log V)
Admissible Only ($h(u) \le \delta(u, t)$) Yes (if re-expansion allowed) O(2^V) Exponential (if closed set re-opened)
Weighted A* ($f = g + \epsilon \cdot h, \epsilon > 1$) $\epsilon$-Approximation ($\le \epsilon \cdot \text{OPT}$) 1 (without re-opening) O((V + E) log V)

Production Implementation: A* Search on a Weighted Graph

#include <vector>
#include <queue>
#include <limits>
#include <functional>

struct AStarEdge {
    int to;
    long long weight;
};

long long astarShortestPath(int n, int start, int target,
                            const std::vector<std::vector<AStarEdge>>& adj,
                            const std::function<long long(int)>& heuristic) {
    constexpr long long INF = std::numeric_limits<long long>::max() / 4;
    std::vector<long long> gScore(n, INF);
    std::vector<uint8_t> closed(n, 0);

    using State = std::pair<long long, int>; // (fScore = g + h, u)
    std::priority_queue<State, std::vector<State>, std::greater<State>> openSet;

    gScore[start] = 0;
    openSet.emplace(heuristic(start), start);

    while (!openSet.empty()) {
        auto [f, u] = openSet.top();
        openSet.pop();

        if (u == target) return gScore[target];
        if (closed[u]) continue; // Sound and optimal when heuristic is consistent!
        closed[u] = 1;

        for (const auto& e : adj[u]) {
            long long tentativeG = gScore[u] + e.weight;
            if (tentativeG < gScore[e.to]) {
                gScore[e.to] = tentativeG;
                openSet.emplace(tentativeG + heuristic(e.to), e.to);
            }
        }
    }
    return -1; // Target unreachable
}
Failure Mode — Using Manhattan Distance on an 8-Directional Grid

On a grid where diagonal movement is allowed with cost $1$ (or $\sqrt{2} \approx 1.414$), moving from $(0, 0)$ to $(1, 1)$ has true distance $1$ (or $1.414$), but Manhattan distance gives $h = |1 - 0| + |1 - 0| = 2 > \delta$. Because Manhattan distance overestimates diagonal shortcuts, it is inadmissible on 8-way grids and can return suboptimal paths! Always match the norm to the allowed move set ($L_1$ for 4-way, $L_\infty$ / Octile for 8-way, $L_2$ for any-angle).

Check yourself Question 1 of 4
Concept Check Defining Heuristic Consistency (Monotonicity) recall
Section: Admissibility vs Consistency

In $A^*$ search toward target $t$, what inequality defines a **consistent** (monotonic) heuristic $h(u)$?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

In Bidirectional Dijkstra from $s$ and $t$, suppose vertex $m$ is the first vertex popped from both the forward priority queue `pqF` and backward queue `pqB`. Why is $d_F[m] + d_B[m]$ **not** guaranteed to be the shortest $s$-$t$ path?

Key Takeaways

A heuristic h(u) is admissible if 0 <= h(u) <= dist(u, t), and consistent if h(u) <= w(u, v) + h(v) for every edge (u, v).
Consistency is mathematically equivalent to Johnson's potential reweighting w_h(u, v) = w(u, v) - h(u) + h(v) >= 0, proving that A* with a consistent heuristic never needs to re-expand a closed vertex.
Every vertex u with g(u) + h(u) < dist(s, t) must be expanded by A*, whereas vertices with g(u) + h(u) > dist(s, t) are pruned completely.
Never use L1 Manhattan distance on an 8-directional grid where diagonal moves cost less than 2, as it violates admissibility.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue