Below is a small map of towns connected by roads, each road labelled with its length. We want a route from A to F. Press Shortest path and you get the answer instantly. Then press Longest simple path — the longest route from A to F that never repeats a town.
<p class="hint">A map of 6 towns (A–F) joined by roads; each label is the road’s length. We want a route from <b>A</b> to <b>F</b>. <b>Shortest</b> is instant. <b>Longest simple</b> (no town twice) has no shortcut — it must search.</p>
<svg id="map" viewBox="0 0 320 200" aria-label="map of towns"></svg>
<div class="status" id="status">Pick a button below.</div>
<div class="btns">
<button id="short" type="button">Shortest path</button>
<button id="long" type="button">Longest simple path</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
#map { width: 100%; max-width: 420px; height: auto; display: block; margin: .3rem 0; }
.edge { stroke: #adb5bd; stroke-width: 2; }
.edge.on { stroke: #1d3557; stroke-width: 4; }
.elabel { font: 600 11px ui-monospace, monospace; fill: #6c757d; }
.node circle { fill: #e8eef3; stroke: #1d3557; stroke-width: 2; }
.node.on circle { fill: #1d3557; }
.node text { font: 700 13px system-ui; fill: #1d3557; text-anchor: middle; dominant-baseline: central; }
.node.on text { fill: #fff; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.short { color: #0a7d33; }
.status.long { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Same weighted graph, two opposite questions from A to F.
// Shortest path -> Dijkstra (greedy, never explores most routes): in P.
// Longest SIMPLE path (no repeated town) -> exhaustive DFS over all simple paths: NP-hard (it contains the Hamiltonian-path problem). Negating weights does NOT help because it creates negative cycles.
const NODES = ['A','B','C','D','E','F'];
const POS = { A:[30,100], B:[110,40], C:[110,160], D:[210,40], E:[210,160], F:[290,100] };
// undirected edges with lengths
const EDGES = [
['A','B',4],['A','C',2],['B','C',1],['B','D',5],['C','D',8],
['C','E',10],['D','E',2],['D','F',6],['E','F',3]
];
const adj = {}; NODES.forEach(n => adj[n] = []);
EDGES.forEach(([u,v,w]) => { adj[u].push([v,w]); adj[v].push([u,w]); });
const edgeKey = (u,v) => [u,v].sort().join('-');
function dijkstra(src, dst){
const dist = {}, prev = {};
NODES.forEach(n => dist[n] = Infinity); dist[src] = 0;
const seen = new Set();
while (seen.size < NODES.length){
let u = null, best = Infinity;
for (const n of NODES) if (!seen.has(n) && dist[n] < best){ best = dist[n]; u = n; }
if (u === null) break;
seen.add(u);
for (const [v,w] of adj[u]) if (dist[u]+w < dist[v]){ dist[v] = dist[u]+w; prev[v] = u; }
}
if (dist[dst] === Infinity) return null;
const path = []; let c = dst; while (c !== undefined){ path.unshift(c); c = prev[c]; }
return { path, cost: dist[dst] };
}
function longestSimple(src, dst){
let best = null, explored = 0;
const visited = new Set();
(function dfs(u, cost, path){
if (u === dst){ explored++; if (!best || cost > best.cost) best = { path: path.slice(), cost }; return; }
for (const [v,w] of adj[u]) if (!visited.has(v)){
visited.add(v); path.push(v);
dfs(v, cost+w, path);
path.pop(); visited.delete(v);
}
})(src, 0, [src]);
visited.add(src);
return best ? { ...best, explored } : null;
}
const svg = document.getElementById('map');
const SVGNS = 'http://www.w3.org/2000/svg';
let onEdges = new Set(), onNodes = new Set();
function el(tag, attrs){ const e = document.createElementNS(SVGNS, tag);
for (const k in attrs) e.setAttribute(k, attrs[k]); return e; }
function draw(){
svg.innerHTML = '';
EDGES.forEach(([u,v,w]) => {
const [x1,y1] = POS[u], [x2,y2] = POS[v];
svg.appendChild(el('line', { class:'edge'+(onEdges.has(edgeKey(u,v))?' on':''), x1,y1,x2,y2 }));
const lab = el('text', { class:'elabel', x:(x1+x2)/2, y:(y1+y2)/2-3 });
lab.textContent = w; svg.appendChild(lab);
});
NODES.forEach(n => {
const [x,y] = POS[n];
const g = el('g', { class:'node'+(onNodes.has(n)?' on':'') });
g.appendChild(el('circle', { cx:x, cy:y, r:13 }));
const t = el('text', { x, y }); t.textContent = n; g.appendChild(t);
svg.appendChild(g);
});
}
function highlight(path){
onEdges = new Set(); onNodes = new Set(path);
for (let i=0;i<path.length-1;i++) onEdges.add(edgeKey(path[i], path[i+1]));
draw();
}
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status '+cls; }
document.getElementById('short').addEventListener('click', () => {
const r = dijkstra('A','F');
highlight(r.path);
setStatus(fmt('Shortest A→F: %path% (length %cost%). Dijkstra found it without trying every route.', { path: r.path.join('→'), cost: r.cost }), 'short');
console.log('shortest', r.path.join('-'), r.cost);
});
document.getElementById('long').addEventListener('click', () => {
const r = longestSimple('A','F');
highlight(r.path);
setStatus(fmt('Longest simple A→F: %path% (length %cost%). It had to explore %explored% full routes.', { path: r.path.join('→'), cost: r.cost, explored: r.explored }), 'long');
console.log('longest', r.path.join('-'), r.cost, 'explored', r.explored);
});
document.getElementById('reset').addEventListener('click', () => {
onEdges = new Set(); onNodes = new Set(); draw(); setStatus('Pick a button below.', '');
});
draw();
<p class="hint">A map of 6 towns (A–F) joined by roads; each label is the road’s length. We want a route from <b>A</b> to <b>F</b>. <b>Shortest</b> is instant. <b>Longest simple</b> (no town twice) has no shortcut — it must search.</p>
<svg id="map" viewBox="0 0 320 200" aria-label="map of towns"></svg>
<div class="status" id="status">Pick a button below.</div>
<div class="btns">
<button id="short" type="button">Shortest path</button>
<button id="long" type="button">Longest simple path</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .6rem; line-height: 1.45; }
#map { width: 100%; max-width: 420px; height: auto; display: block; margin: .3rem 0; }
.edge { stroke: #adb5bd; stroke-width: 2; }
.edge.on { stroke: #1d3557; stroke-width: 4; }
.elabel { font: 600 11px ui-monospace, monospace; fill: #6c757d; }
.node circle { fill: #e8eef3; stroke: #1d3557; stroke-width: 2; }
.node.on circle { fill: #1d3557; }
.node text { font: 700 13px system-ui; fill: #1d3557; text-anchor: middle; dominant-baseline: central; }
.node.on text { fill: #fff; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.short { color: #0a7d33; }
.status.long { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Same weighted graph, two opposite questions from A to F.
// Shortest path -> Dijkstra (greedy, never explores most routes): in P.
// Longest SIMPLE path (no repeated town) -> exhaustive DFS over all simple paths: NP-hard (it contains the Hamiltonian-path problem). Negating weights does NOT help because it creates negative cycles.
const NODES = ['A','B','C','D','E','F'];
const POS = { A:[30,100], B:[110,40], C:[110,160], D:[210,40], E:[210,160], F:[290,100] };
// undirected edges with lengths
const EDGES = [
['A','B',4],['A','C',2],['B','C',1],['B','D',5],['C','D',8],
['C','E',10],['D','E',2],['D','F',6],['E','F',3]
];
const adj = {}; NODES.forEach(n => adj[n] = []);
EDGES.forEach(([u,v,w]) => { adj[u].push([v,w]); adj[v].push([u,w]); });
const edgeKey = (u,v) => [u,v].sort().join('-');
function dijkstra(src, dst){
const dist = {}, prev = {};
NODES.forEach(n => dist[n] = Infinity); dist[src] = 0;
const seen = new Set();
while (seen.size < NODES.length){
let u = null, best = Infinity;
for (const n of NODES) if (!seen.has(n) && dist[n] < best){ best = dist[n]; u = n; }
if (u === null) break;
seen.add(u);
for (const [v,w] of adj[u]) if (dist[u]+w < dist[v]){ dist[v] = dist[u]+w; prev[v] = u; }
}
if (dist[dst] === Infinity) return null;
const path = []; let c = dst; while (c !== undefined){ path.unshift(c); c = prev[c]; }
return { path, cost: dist[dst] };
}
function longestSimple(src, dst){
let best = null, explored = 0;
const visited = new Set();
(function dfs(u, cost, path){
if (u === dst){ explored++; if (!best || cost > best.cost) best = { path: path.slice(), cost }; return; }
for (const [v,w] of adj[u]) if (!visited.has(v)){
visited.add(v); path.push(v);
dfs(v, cost+w, path);
path.pop(); visited.delete(v);
}
})(src, 0, [src]);
visited.add(src);
return best ? { ...best, explored } : null;
}
const svg = document.getElementById('map');
const SVGNS = 'http://www.w3.org/2000/svg';
let onEdges = new Set(), onNodes = new Set();
function el(tag, attrs){ const e = document.createElementNS(SVGNS, tag);
for (const k in attrs) e.setAttribute(k, attrs[k]); return e; }
function draw(){
svg.innerHTML = '';
EDGES.forEach(([u,v,w]) => {
const [x1,y1] = POS[u], [x2,y2] = POS[v];
svg.appendChild(el('line', { class:'edge'+(onEdges.has(edgeKey(u,v))?' on':''), x1,y1,x2,y2 }));
const lab = el('text', { class:'elabel', x:(x1+x2)/2, y:(y1+y2)/2-3 });
lab.textContent = w; svg.appendChild(lab);
});
NODES.forEach(n => {
const [x,y] = POS[n];
const g = el('g', { class:'node'+(onNodes.has(n)?' on':'') });
g.appendChild(el('circle', { cx:x, cy:y, r:13 }));
const t = el('text', { x, y }); t.textContent = n; g.appendChild(t);
svg.appendChild(g);
});
}
function highlight(path){
onEdges = new Set(); onNodes = new Set(path);
for (let i=0;i<path.length-1;i++) onEdges.add(edgeKey(path[i], path[i+1]));
draw();
}
function setStatus(t, cls){ const s = document.getElementById('status'); s.textContent = t; s.className = 'status '+cls; }
document.getElementById('short').addEventListener('click', () => {
const r = dijkstra('A','F');
highlight(r.path);
setStatus(fmt('Shortest A→F: %path% (length %cost%). Dijkstra found it without trying every route.', { path: r.path.join('→'), cost: r.cost }), 'short');
console.log('shortest', r.path.join('-'), r.cost);
});
document.getElementById('long').addEventListener('click', () => {
const r = longestSimple('A','F');
highlight(r.path);
setStatus(fmt('Longest simple A→F: %path% (length %cost%). It had to explore %explored% full routes.', { path: r.path.join('→'), cost: r.cost, explored: r.explored }), 'long');
console.log('longest', r.path.join('-'), r.cost, 'explored', r.explored);
});
document.getElementById('reset').addEventListener('click', () => {
onEdges = new Set(); onNodes = new Set(); draw(); setStatus('Pick a button below.', '');
});
draw();
Comments
Loading comments...