惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
Jina AI
Jina AI
罗磊的独立博客
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
J
Java Code Geeks
U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog RSS Feed
爱范儿
爱范儿
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
T
The Blog of Author Tim Ferriss
腾讯CDC
Hugging Face - Blog
Hugging Face - Blog
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
I
InfoQ
月光博客
月光博客
博客园_首页
Vercel News
Vercel News
P
Proofpoint News Feed
GbyAI
GbyAI
Y
Y Combinator Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
A Shortest-Path CLI in Rust — Making a Min-Heap from a Ma...
SEN LLC · 2026-06-24 · via DEV Community

SEN LLC

A CLI that finds shortest paths in a weighted graph, in Rust, with Dijkstra's algorithm. Three implementation hinges: (1) Dijkstra needs a min-priority-queue to pull the closest unfinalized node, but Rust's BinaryHeap is a max-heap — so you reverse the Ord, (2) record predecessors to reconstruct the path, and (3) Dijkstra is wrong for negative weights, so reject them at parse time.

📦 GitHub: https://github.com/sen-ltd/dijkstra-cli

(It's a CLI — no live demo. Run with cargo run or Docker.)

Screenshot

Input format

Edge list, one per line: FROM TO WEIGHT. # comments and blanks ignored. --undirected mirrors every edge.

A B 7
A C 9
C F 2
F E 9

Hinge 1: a min-heap from a max-heap

Dijkstra repeatedly takes the node closest to the source — a min-priority-queue. But Rust's std::collections::BinaryHeap is a max-heap (largest on top).

The fix is to reverse the Ord of the State you push, so popping yields the minimum distance:

#[derive(Copy, Clone, Eq, PartialEq)]
struct State { dist: u64, idx: usize }

impl Ord for State {
    fn cmp(&self, other: &Self) -> Ordering {
        // reverse both keys: smallest dist pops first; on a tie, smaller idx
        other.dist.cmp(&self.dist).then_with(|| other.idx.cmp(&self.idx))
    }
}
impl PartialOrd for State {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

other.dist.cmp(&self.dist) flips the comparison, using the max-heap as a min-heap — the canonical Rust idiom (the std-doc Dijkstra example does this too).

Reversing the secondary key (idx) too makes ties deterministic. Without it, equal-distance nodes pop in an arbitrary order and the reconstructed path wobbles between runs. Pinned by a test:

#[test]
fn deterministic_path_on_ties() {
    // A->B->D and A->C->D cost the same; predecessor is the smaller, B
    let sp = dijkstra(&g("A B 1\nA C 1\nB D 1\nC D 1\n"), "A");
    assert_eq!(sp.path_to("D").unwrap(), vec!["A", "B", "D"]);
}

I actually wrote this test first without reversing idx — it returned A->C->D and failed. Testing determinism caught the bug in my own Ord.

Hinge 2: discard stale heap entries

You want "found a shorter distance? update the queue," but BinaryHeap has no decrease-key. The fix: push duplicates and skip stale pops (lazy deletion):

while let Some(State { dist, idx }) = heap.pop() {
    if dist > best[idx] {
        continue; // a shorter distance was already finalized — stale entry
    }
    // ... relax neighbors ...
}

If dist > best[idx], a shorter path was found after this entry was pushed, so ignore it. Simpler than a hand-rolled indexed heap, and still O((V+E) log V).

#[test]
fn stale_heap_entries_ignored() {
    // direct B is 10 but via C it's 2
    let sp = dijkstra(&g("A B 10\nA C 1\nC B 1\n"), "A");
    assert_eq!(sp.dist["B"], 2);
    assert_eq!(sp.path_to("B").unwrap(), vec!["A", "C", "B"]);
}

Hinge 3: path reconstruction & negative weights

You want the path, not just the distance. Each relaxation records "who updated this node" in a prev map; path_to walks it back from the target and reverses.

And Dijkstra is incorrect for negative weights — the assumption that a finalized node can't later be improved breaks. Reject them at parse time:

if w < 0 {
    return Err(GraphError::NegativeWeight(line, w));
}

The error tells you to use Bellman-Ford instead of silently returning a wrong answer:

$ echo "A B -3" | dijkstra A
error: line 1: negative weight -3 — Dijkstra requires non-negative weights

CLI

match &cli.target {
    Some(target) => { /* print the single path */ }
    None => { /* table of shortest distances to all reachable nodes */ }
}

With a target, one path; without, all distances + paths. stdin supported.

$ dijkstra A E --file examples/graph.txt
A -> C -> F -> E  (distance 20)

A test pins the classic Wikipedia Dijkstra graph: A→E is A->C->F->E = 9+2+9 = 20.

Architecture

src/graph.rs — edge-list parse, Dijkstra (BinaryHeap), path reconstruction
src/main.rs  — clap CLI: single path or all-distances table, file/stdin

Single clap dependency; 10 inline tests in graph.rs. Docker: rust:1.85-alpinealpine, non-root. House style (committed Cargo.lock, opt-level="z" + LTO).

Run it

cargo test
cargo run -- A E --file examples/graph.txt
docker build -t dijkstra . && docker run --rm -i dijkstra A E < examples/graph.txt

Takeaways

  • Rust's BinaryHeap is a max-heap; reverse State's Ord to use it as the min-heap Dijkstra needs.
  • Reverse the secondary key too for deterministic ties — stable path reconstruction (a test caught this).
  • Skip stale entries on pop instead of decrease-key (lazy deletion); still O((V+E) log V).
  • Reconstruct the path from a prev predecessor map.
  • Reject negative weights — Dijkstra's invariant breaks, so error instead of returning garbage.

This is OSS portfolio #272 from SEN LLC (Tokyo). https://sen.ltd/portfolio/