Below is one small graph. Press Euler check and the answer pops out instantly from a single rule: an Euler trail (every edge once) exists exactly when at most two vertices have an odd number of edges. No searching, no trying — just count.
<p class="hint">One graph, two questions. <b>Euler:</b> can you trace every <i>edge</i> exactly once? (Answered instantly by a degree rule.) <b>Hamilton:</b> can you visit every <i>vertex</i> exactly once? (No rule — the computer must search.)</p>
<svg id="graph" viewBox="0 0 320 220" role="img" aria-label="graph"></svg>
<div class="status" id="status">Pick a question below.</div>
<div class="btns">
<button id="euler" type="button">Euler check</button>
<button id="hamilton" type="button">Find Hamilton 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; }
svg { width: 100%; max-width: 340px; height: auto; display: block; margin: .2rem auto; }
.edge { stroke: #adb1b8; stroke-width: 3; }
.edge.lit { stroke: #1d3557; stroke-width: 5; }
.node circle { fill: #e8eef3; stroke: #1d3557; stroke-width: 2; }
.node.lit circle { fill: #1d3557; }
.node text { font: 700 13px ui-monospace, monospace; fill: #1d3557; text-anchor: middle; dominant-baseline: central; }
.node.lit text { fill: #fff; }
.status { font-size: .98rem; font-weight: 600; margin: .5rem 0; min-height: 2.6em; line-height: 1.4; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; 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] ?? ''));
// A small graph. Vertices 0..4 with fixed positions; EDGES are undirected pairs.
// EULER: an Euler trail (every edge once) exists iff the graph is connected and
// the number of odd-degree vertices is 0 or 2 (Euler, 1736) — a one-pass count.
// HAMILTON: a Hamiltonian path (every vertex once) — no shortcut rule is known;
// we brute-force permutations of the vertices (NP-complete, Karp 1972).
const POS = [[60,40],[260,40],[160,110],[60,180],[260,180]];
const LABEL = ['A','B','C','D','E'];
// Edges chosen so: degrees -> A:2 B:2 C:4 D:2 E:2 => 0 odd vertices => Euler circuit exists.
// And a Hamiltonian path A-B-C-D... we will let the search discover one.
const EDGES = [[0,1],[0,2],[1,2],[2,3],[2,4],[3,4]];
const N = 5;
function degrees(){ const d=Array(N).fill(0); for(const [a,b] of EDGES){ d[a]++; d[b]++; } return d; }
function connected(){
const seen=new Set([0]); const st=[0];
while(st.length){ const v=st.pop();
for(const [a,b] of EDGES){ if(a===v&&!seen.has(b)){seen.add(b);st.push(b);} if(b===v&&!seen.has(a)){seen.add(a);st.push(a);} } }
return seen.size===N;
}
function adj(a,b){ return EDGES.some(([x,y])=>(x===a&&y===b)||(x===b&&y===a)); }
function draw(litNodes, litEdges){
const g=document.getElementById('graph'); g.innerHTML='';
litEdges = litEdges||new Set(); litNodes = litNodes||new Set();
EDGES.forEach(([a,b],i)=>{
const l=document.createElementNS('http://www.w3.org/2000/svg','line');
l.setAttribute('x1',POS[a][0]); l.setAttribute('y1',POS[a][1]);
l.setAttribute('x2',POS[b][0]); l.setAttribute('y2',POS[b][1]);
l.setAttribute('class','edge'+(litEdges.has(i)?' lit':'')); g.appendChild(l);
});
for(let v=0;v<N;v++){
const grp=document.createElementNS('http://www.w3.org/2000/svg','g');
grp.setAttribute('class','node'+(litNodes.has(v)?' lit':''));
const c=document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx',POS[v][0]); c.setAttribute('cy',POS[v][1]); c.setAttribute('r',16); grp.appendChild(c);
const t=document.createElementNS('http://www.w3.org/2000/svg','text');
t.setAttribute('x',POS[v][0]); t.setAttribute('y',POS[v][1]); t.textContent=LABEL[v]; grp.appendChild(t);
g.appendChild(grp);
}
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.innerHTML=t; s.className='status '+(cls||''); }
function euler(){
const d=degrees(); const odd=d.filter(x=>x%2===1).length;
const ok = connected() && (odd===0||odd===2);
draw(new Set(), new Set(EDGES.map((_,i)=>i)));
const degTxt = d.map((x,i)=>LABEL[i]+'='+x).join(', ');
if(ok) setStatus(fmt('✓ Euler trail EXISTS. Degrees: %degTxt%. Odd-degree vertices: %odd% (≤2). One count, instant answer — this is in P.', { degTxt: degTxt, odd: odd }) + " ",'ok');
else setStatus(fmt('✗ No Euler trail. Degrees: %degTxt%. Odd-degree vertices: %odd% (must be 0 or 2).', { degTxt: degTxt, odd: odd }),'bad');
}
// Brute-force Hamiltonian path: try permutations until consecutive vertices are adjacent.
function permute(arr){ if(arr.length<=1) return [arr]; const out=[];
for(let i=0;i<arr.length;i++){ const rest=arr.slice(0,i).concat(arr.slice(i+1));
for(const p of permute(rest)) out.push([arr[i]].concat(p)); } return out; }
function hamilton(){
// Try orderings in an unbiased sequence so the search visibly does work
// before stumbling on a valid path (the answer is found, just not for free).
const perms=permute([1,3,0,4,2]); let tried=0, found=null;
for(const p of perms){ tried++; let ok=true;
for(let i=0;i<p.length-1;i++){ if(!adj(p[i],p[i+1])){ ok=false; break; } }
if(ok){ found=p; break; } }
if(found){
const litE=new Set();
for(let i=0;i<found.length-1;i++){ const a=found[i],b=found[i+1];
EDGES.forEach(([x,y],idx)=>{ if((x===a&&y===b)||(x===b&&y===a)) litE.add(idx); }); }
draw(new Set(found), litE);
setStatus(fmt('✓ Hamiltonian path: %map%. Found after trying <b><b>%tried%</b></b> of %length% orderings. No rule — only search.', { map: found.map(v=>LABEL[v]).join(' → '), tried: tried, length: perms.length }),'ok');
console.log('Hamiltonian path', found.map(v=>LABEL[v]).join(''), '| tried', tried, 'of', perms.length);
} else setStatus(fmt('✗ No Hamiltonian path exists, after trying all %length% orderings.', { length: perms.length }),'bad');
}
document.getElementById('euler').addEventListener('click',euler);
document.getElementById('hamilton').addEventListener('click',hamilton);
document.getElementById('reset').addEventListener('click',()=>{ draw(); setStatus('Pick a question below.',''); });
draw();
<p class="hint">One graph, two questions. <b>Euler:</b> can you trace every <i>edge</i> exactly once? (Answered instantly by a degree rule.) <b>Hamilton:</b> can you visit every <i>vertex</i> exactly once? (No rule — the computer must search.)</p>
<svg id="graph" viewBox="0 0 320 220" role="img" aria-label="graph"></svg>
<div class="status" id="status">Pick a question below.</div>
<div class="btns">
<button id="euler" type="button">Euler check</button>
<button id="hamilton" type="button">Find Hamilton 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; }
svg { width: 100%; max-width: 340px; height: auto; display: block; margin: .2rem auto; }
.edge { stroke: #adb1b8; stroke-width: 3; }
.edge.lit { stroke: #1d3557; stroke-width: 5; }
.node circle { fill: #e8eef3; stroke: #1d3557; stroke-width: 2; }
.node.lit circle { fill: #1d3557; }
.node text { font: 700 13px ui-monospace, monospace; fill: #1d3557; text-anchor: middle; dominant-baseline: central; }
.node.lit text { fill: #fff; }
.status { font-size: .98rem; font-weight: 600; margin: .5rem 0; min-height: 2.6em; line-height: 1.4; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; 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] ?? ''));
// A small graph. Vertices 0..4 with fixed positions; EDGES are undirected pairs.
// EULER: an Euler trail (every edge once) exists iff the graph is connected and
// the number of odd-degree vertices is 0 or 2 (Euler, 1736) — a one-pass count.
// HAMILTON: a Hamiltonian path (every vertex once) — no shortcut rule is known;
// we brute-force permutations of the vertices (NP-complete, Karp 1972).
const POS = [[60,40],[260,40],[160,110],[60,180],[260,180]];
const LABEL = ['A','B','C','D','E'];
// Edges chosen so: degrees -> A:2 B:2 C:4 D:2 E:2 => 0 odd vertices => Euler circuit exists.
// And a Hamiltonian path A-B-C-D... we will let the search discover one.
const EDGES = [[0,1],[0,2],[1,2],[2,3],[2,4],[3,4]];
const N = 5;
function degrees(){ const d=Array(N).fill(0); for(const [a,b] of EDGES){ d[a]++; d[b]++; } return d; }
function connected(){
const seen=new Set([0]); const st=[0];
while(st.length){ const v=st.pop();
for(const [a,b] of EDGES){ if(a===v&&!seen.has(b)){seen.add(b);st.push(b);} if(b===v&&!seen.has(a)){seen.add(a);st.push(a);} } }
return seen.size===N;
}
function adj(a,b){ return EDGES.some(([x,y])=>(x===a&&y===b)||(x===b&&y===a)); }
function draw(litNodes, litEdges){
const g=document.getElementById('graph'); g.innerHTML='';
litEdges = litEdges||new Set(); litNodes = litNodes||new Set();
EDGES.forEach(([a,b],i)=>{
const l=document.createElementNS('http://www.w3.org/2000/svg','line');
l.setAttribute('x1',POS[a][0]); l.setAttribute('y1',POS[a][1]);
l.setAttribute('x2',POS[b][0]); l.setAttribute('y2',POS[b][1]);
l.setAttribute('class','edge'+(litEdges.has(i)?' lit':'')); g.appendChild(l);
});
for(let v=0;v<N;v++){
const grp=document.createElementNS('http://www.w3.org/2000/svg','g');
grp.setAttribute('class','node'+(litNodes.has(v)?' lit':''));
const c=document.createElementNS('http://www.w3.org/2000/svg','circle');
c.setAttribute('cx',POS[v][0]); c.setAttribute('cy',POS[v][1]); c.setAttribute('r',16); grp.appendChild(c);
const t=document.createElementNS('http://www.w3.org/2000/svg','text');
t.setAttribute('x',POS[v][0]); t.setAttribute('y',POS[v][1]); t.textContent=LABEL[v]; grp.appendChild(t);
g.appendChild(grp);
}
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.innerHTML=t; s.className='status '+(cls||''); }
function euler(){
const d=degrees(); const odd=d.filter(x=>x%2===1).length;
const ok = connected() && (odd===0||odd===2);
draw(new Set(), new Set(EDGES.map((_,i)=>i)));
const degTxt = d.map((x,i)=>LABEL[i]+'='+x).join(', ');
if(ok) setStatus(fmt('✓ Euler trail EXISTS. Degrees: %degTxt%. Odd-degree vertices: %odd% (≤2). One count, instant answer — this is in P.', { degTxt: degTxt, odd: odd }) + " ",'ok');
else setStatus(fmt('✗ No Euler trail. Degrees: %degTxt%. Odd-degree vertices: %odd% (must be 0 or 2).', { degTxt: degTxt, odd: odd }),'bad');
}
// Brute-force Hamiltonian path: try permutations until consecutive vertices are adjacent.
function permute(arr){ if(arr.length<=1) return [arr]; const out=[];
for(let i=0;i<arr.length;i++){ const rest=arr.slice(0,i).concat(arr.slice(i+1));
for(const p of permute(rest)) out.push([arr[i]].concat(p)); } return out; }
function hamilton(){
// Try orderings in an unbiased sequence so the search visibly does work
// before stumbling on a valid path (the answer is found, just not for free).
const perms=permute([1,3,0,4,2]); let tried=0, found=null;
for(const p of perms){ tried++; let ok=true;
for(let i=0;i<p.length-1;i++){ if(!adj(p[i],p[i+1])){ ok=false; break; } }
if(ok){ found=p; break; } }
if(found){
const litE=new Set();
for(let i=0;i<found.length-1;i++){ const a=found[i],b=found[i+1];
EDGES.forEach(([x,y],idx)=>{ if((x===a&&y===b)||(x===b&&y===a)) litE.add(idx); }); }
draw(new Set(found), litE);
setStatus(fmt('✓ Hamiltonian path: %map%. Found after trying <b><b>%tried%</b></b> of %length% orderings. No rule — only search.', { map: found.map(v=>LABEL[v]).join(' → '), tried: tried, length: perms.length }),'ok');
console.log('Hamiltonian path', found.map(v=>LABEL[v]).join(''), '| tried', tried, 'of', perms.length);
} else setStatus(fmt('✗ No Hamiltonian path exists, after trying all %length% orderings.', { length: perms.length }),'bad');
}
document.getElementById('euler').addEventListener('click',euler);
document.getElementById('hamilton').addEventListener('click',hamilton);
document.getElementById('reset').addEventListener('click',()=>{ draw(); setStatus('Pick a question below.',''); });
draw();
Comments
Loading comments...