Dijkstra's algorithm

다익스트라 알고리즘

Finds minimum path costs from one source when edge weights are nonnegative.

···
html
<canvas id="stage"></canvas><div class="controls"><button id="restart" type="button">처음부터</button><button id="step" type="button">한 단계</button><label>속도 <input id="speed" type="range" min="1" max="5" value="3"></label></div>
css
body{display:block}#stage{position:absolute;inset:0;width:100%;height:100%}.controls{position:absolute;bottom:9px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:7px;white-space:nowrap}.controls button,.controls label{font:12px sans-serif;color:var(--fg);background:var(--surface);border:1px solid var(--line);border-radius:6px;padding:5px 8px}.controls button{cursor:pointer}.controls input{width:75px;vertical-align:middle;accent-color:var(--accent)}@media(max-width:500px){.controls{display:none}}
js
const frames = [{"kind":"graph","caption":"A 거리 0, 나머지는 ∞","data":{"nodes":[{"id":"A","x":0.1,"y":0.5},{"id":"B","x":0.32,"y":0.16},{"id":"C","x":0.32,"y":0.84},{"id":"D","x":0.59,"y":0.17},{"id":"E","x":0.59,"y":0.8},{"id":"F","x":0.88,"y":0.5}],"edges":[["A","B",2],["A","C",4],["B","D",5],["B","E",1],["C","E",2],["D","E",2],["D","F",2],["E","F",4]],"distances":{"A":0,"B":null,"C":null,"D":null,"E":null,"F":null}}},{"kind":"graph","caption":"A 확정 · 이웃 거리 갱신","data":{"nodes":[{"id":"A","x":0.1,"y":0.5},{"id":"B","x":0.32,"y":0.16},{"id":"C","x":0.32,"y":0.84},{"id":"D","x":0.59,"y":0.17},{"id":"E","x":0.59,"y":0.8},{"id":"F","x":0.88,"y":0.5}],"edges":[["A","B",2],["A","C",4],["B","D",5],["B","E",1],["C","E",2],["D","E",2],["D","F",2],["E","F",4]],"active":"A","visited":["A"],"distances":{"A":0,"B":2,"C":4,"D":null,"E":null,"F":null}}},{"kind":"graph","caption":"B 확정 · 이웃 거리 갱신","data":{"nodes":[{"id":"A","x":0.1,"y":0.5},{"id":"B","x":0.32,"y":0.16},{"id":"C","x":0.32,"y":0.84},{"id":"D","x":0.59,"y":0.17},{"id":"E","x":0.59,"y":0.8},{"id":"F","x":0.88,"y":0.5}],"edges":[["A","B",2],["A","C",4],["B","D",5],["B","E",1],["C","E",2],["D","E",2],["D","F",2],["E","F",4]],"active":"B","visited":["A","B"],"distances":{"A":0,"B":2,"C":4,"D":7,"E":3,"F":null}}},{"kind":"graph","caption":"E 확정 · 이웃 거리 갱신","data":{"nodes":[{"id":"A","x":0.1,"y":0.5},{"id":"B","x":0.32,"y":0.16},{"id":"C","x":0.32,"y":0.84},{"id":"D","x":0.59,"y":0.17},{"id":"E","x":0.59,"y":0.8},{"id":"F","x":0.88,"y":0.5}],"edges":[["A","B",2],["A","C",4],["B","D",5],["B","E",1],["C","E",2],["D","E",2],["D","F",2],["E","F",4]],"active":"E","visited":["A","B","E"],"distances":{"A":0,"B":2,"C":4,"D":5,"E":3,"F":7}}},{"kind":"graph","caption":"C 확정 · 이웃 거리 갱신","data":{"nodes":[{"id":"A","x":0.1,"y":0.5},{"id":"B","x":0.32,"y":0.16},{"id":"C","x":0.32,"y":0.84},{"id":"D","x":0.59,"y":0.17},{"id":"E","x":0.59,"y":0.8},{"id":"F","x":0.88,"y":0.5}],"edges":[["A","B",2],["A","C",4],["B","D",5],["B","E",1],["C","E",2],["D","E",2],["D","F",2],["E","F",4]],"active":"C","visited":["A","B","E","C"],"distances":{"A":0,"B":2,"C":4,"D":5,"E":3,"F":7}}},{"kind":"graph","caption":"D 확정 · 이웃 거리 갱신","data":{"nodes":[{"id":"A","x":0.1,"y":0.5},{"id":"B","x":0.32,"y":0.16},{"id":"C","x":0.32,"y":0.84},{"id":"D","x":0.59,"y":0.17},{"id":"E","x":0.59,"y":0.8},{"id":"F","x":0.88,"y":0.5}],"edges":[["A","B",2],["A","C",4],["B","D",5],["B","E",1],["C","E",2],["D","E",2],["D","F",2],["E","F",4]],"active":"D","visited":["A","B","E","C","D"],"distances":{"A":0,"B":2,"C":4,"D":5,"E":3,"F":7}}},{"kind":"graph","caption":"F 확정 · 이웃 거리 갱신","data":{"nodes":[{"id":"A","x":0.1,"y":0.5},{"id":"B","x":0.32,"y":0.16},{"id":"C","x":0.32,"y":0.84},{"id":"D","x":0.59,"y":0.17},{"id":"E","x":0.59,"y":0.8},{"id":"F","x":0.88,"y":0.5}],"edges":[["A","B",2],["A","C",4],["B","D",5],["B","E",1],["C","E",2],["D","E",2],["D","F",2],["E","F",4]],"active":"F","visited":["A","B","E","C","D","F"],"distances":{"A":0,"B":2,"C":4,"D":5,"E":3,"F":7}}}];
const title = "다익스트라 · 최단 비용";
const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
const restart = document.getElementById('restart');
const step = document.getElementById('step');
const speed = document.getElementById('speed');
let index = 0, last = performance.now(), width = 300, height = 200;
function colors(){ const s=getComputedStyle(document.documentElement); const v=(key)=>s.getPropertyValue(key).trim(); return {bg:v('--bg'),fg:v('--fg'),muted:v('--muted'),line:v('--line'),surface:v('--surface'),accent:v('--accent'),accent2:v('--accent-2'),accent3:v('--accent-3')}; }
function resize(){ const dpr=Math.min(devicePixelRatio||1,2); width=innerWidth;height=innerHeight;canvas.width=Math.round(width*dpr);canvas.height=Math.round(height*dpr);ctx.setTransform(dpr,0,0,dpr,0,0);draw(); }
function text(value,x,y,color,size=12,align='left'){ ctx.fillStyle=color;ctx.font='600 '+size+'px sans-serif';ctx.textAlign=align;ctx.textBaseline='middle';ctx.fillText(String(value),x,y); }
function line(x1,y1,x2,y2,color,thick=2){ctx.strokeStyle=color;ctx.lineWidth=thick;ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();}
function dot(x,y,r,color){ctx.fillStyle=color;ctx.beginPath();ctx.arc(x,y,r,0,Math.PI*2);ctx.fill();}
function draw(){
  if(!frames.length)return;
  const p=colors(), frame=frames[index], d=frame.data, detail=width>500;
  ctx.fillStyle=p.bg;ctx.fillRect(0,0,width,height);
  text(title,12,18,p.fg,detail?16:13);text(frame.caption,12,detail?43:39,p.muted,detail?13:10);
  text((index+1)+' / '+frames.length,width-12,18,p.muted,10,'right');
  const top=detail?62:53, bottom=detail?height-47:height-9, span=Math.max(10,bottom-top), areaW=Math.min(width-22,detail?710:width-22), left=(width-areaW)/2;
  if(frame.kind==='graph'){
    const nodes=d.nodes, edges=d.edges, positions=Object.fromEntries(nodes.map(n=>[n.id,{x:left+areaW*(.06+.88*n.x),y:top+span*(.11+.78*n.y)}]));
    for(const edge of edges){const a=edge[0],b=edge[1],weight=edge[2],key=[a,b].sort().join('-'),pa=positions[a],pb=positions[b];
      if(d.hideUnvisited&&(!d.visited.includes(a)||!d.visited.includes(b)))continue;
      const chosen=d.selected?.includes(key), active=d.activeEdge===key;ctx.globalAlpha=active||chosen?1:.58;line(pa.x,pa.y,pb.x,pb.y,active?p.accent2:chosen?p.accent:p.muted,active?4:chosen?3:2);ctx.globalAlpha=1;
      if(d.directed){const angle=Math.atan2(pb.y-pa.y,pb.x-pa.x),x=pb.x-Math.cos(angle)*(detail?18:13),y=pb.y-Math.sin(angle)*(detail?18:13);line(x-Math.cos(angle-.55)*7,y-Math.sin(angle-.55)*7,x,y,p.muted,1.5);line(x-Math.cos(angle+.55)*7,y-Math.sin(angle+.55)*7,x,y,p.muted,1.5);}
      if(weight>0&&!d.hideUnvisited)text(weight,(pa.x+pb.x)/2,(pa.y+pb.y)/2-7,p.muted,10,'center');
    }
    for(const node of nodes){if(d.hideUnvisited&&!d.visited.includes(node.id))continue;const pt=positions[node.id],selected=d.visited?.includes(node.id),front=d.frontier?.includes(node.id),active=d.active===node.id;let color=active?p.accent2:front?p.accent3:selected?p.accent:p.surface;
      if(d.groups){const group=d.groups[nodes.findIndex(n=>n.id===node.id)];color=[p.accent,p.accent2,p.accent3][nodes.findIndex(n=>n.id===group)%3];}
      dot(pt.x,pt.y,detail?18:12,color);ctx.strokeStyle=p.line;ctx.lineWidth=1;ctx.stroke();text(node.id,pt.x,pt.y,selected||active||front||d.groups?'#fff':p.fg,detail?13:10,'center');
      if(d.distances)text(Number.isFinite(d.distances[node.id])?d.distances[node.id]:'∞',pt.x,pt.y+(detail?29:22),p.muted,10,'center');
    }
  } else if(frame.kind==='grid'||frame.kind==='table'||frame.kind==='bits'){
    const table=frame.kind==='table',bits=frame.kind==='bits';const rows=bits?1:table?d.values.length:d.rows,cols=bits?d.bits.length:table?d.values[0].length:d.cols;
    const labelX=table?24:0,labelY=table?18:0,cell=Math.min((areaW-labelX)/cols,(span-labelY)/rows,detail?56:32),ox=left+(areaW-(cols*cell+labelX))/2+labelX,oy=top+(span-(rows*cell+labelY))/2+labelY;
    if(table){d.colLabels.forEach((label,c)=>text(label,ox+c*cell+cell/2,oy-10,p.muted,10,'center'));d.rowLabels.forEach((label,r)=>text(label,ox-12,oy+r*cell+cell/2,p.muted,10,'center'));}
    for(let r=0;r<rows;r++)for(let c=0;c<cols;c++){const n=r*cols+c,x=ox+c*cell,y=oy+r*cell,active=bits?d.active?.includes(c):table?d.active?.[0]===r&&d.active?.[1]===c:d.active===n,source=table?d.sources?.some(v=>v[0]===r&&v[1]===c):false,visited=bits?d.bits[c]:table?false:d.visited?.includes(n),front=d.frontier?.includes(n),blocked=d.blocked?.includes(n),path=d.path?.includes(n),queen=d.queens?.[r]===c;
      ctx.fillStyle=blocked?p.line:active?p.accent2:path||queen?p.accent3:front?p.accent2:visited?p.accent:source?p.accent3:bits&&d.bits[c]?p.accent:p.surface;ctx.fillRect(x+2,y+2,cell-4,cell-4);ctx.strokeStyle=p.muted;ctx.globalAlpha=.42;ctx.strokeRect(x+1,y+1,cell-2,cell-2);ctx.globalAlpha=1;
      const value=bits?d.bits[c]:table?d.values[r][c]:queen?'Q':d.start===n?'S':d.goal===n?'G':blocked?'':visited?'·':'';
      if(value!==''&&value!==undefined)text(value,x+cell/2,y+cell/2,(active||path||queen||front||visited||bits&&d.bits[c])?'#fff':p.fg,Math.min(13,cell*.43),'center');
      if(bits)text(c,x+cell/2,y+cell+11,p.muted,9,'center');
    }
  } else if(frame.kind==='intervals'){
    const rowH=Math.min(31,span/d.intervals.length),unit=(areaW-28)/10,ox=left+10,oy=top+(span-rowH*d.intervals.length)/2;
    d.intervals.forEach((it,i)=>{const y=oy+i*rowH;ctx.fillStyle=d.active===i?p.accent2:d.selected.includes(i)?p.accent:p.line;ctx.fillRect(ox+it[0]*unit,y+3,Math.max(5,(it[1]-it[0])*unit-3),rowH-7);text(i+1,ox-5,y+rowH/2,p.muted,10,'right');});
  } else if(frame.kind==='ring'){
    const cx=width/2,cy=top+span/2,r=Math.min(areaW,span)*.35,loc=(v,rr)=>({x:cx+Math.sin(v*2*Math.PI)*rr,y:cy-Math.cos(v*2*Math.PI)*rr});ctx.strokeStyle=p.muted;ctx.globalAlpha=.6;ctx.lineWidth=2;ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.stroke();ctx.globalAlpha=1;
    d.keys.forEach((key,i)=>{const point=loc(key,r),server=d.servers.find(s=>s.id===d.owners[i]),dest=loc(server.pos,r);line(point.x,point.y,dest.x,dest.y,d.active===i?p.accent2:p.line,1);dot(point.x,point.y,d.active===i?6:4,d.active===i?p.accent2:p.fg);});
    d.servers.forEach((server,i)=>{const point=loc(server.pos,r);dot(point.x,point.y,detail?15:11,[p.accent,p.accent2,p.accent3,p.fg][i]);text(server.id,point.x,point.y,i===3?p.bg:'#fff',11,'center');});
  } else if(frame.kind==='points'){
    const side=Math.min(areaW,span)-8,ox=(width-side)/2,oy=top+(span-side)/2;ctx.strokeStyle=p.muted;ctx.globalAlpha=.5;ctx.strokeRect(ox,oy,side,side);ctx.globalAlpha=1;ctx.beginPath();ctx.arc(ox,oy+side,side,Math.PI*1.5,0);ctx.strokeStyle=p.muted;ctx.stroke();
    d.points.forEach(([x,y])=>dot(ox+x*side,oy+side-y*side,detail?2.5:1.7,x*x+y*y<=1?p.accent:p.accent2));
  }
}
function advance(){index=(index+1)%frames.length;draw();}
restart.addEventListener('click',()=>{index=0;last=performance.now();draw();});
step.addEventListener('click',()=>{advance();last=performance.now();});
addEventListener('resize',resize);resize();
function loop(now){const delay=900-Number(speed.value)*140;if(now-last>delay){advance();last=now;}requestAnimationFrame(loop);}requestAnimationFrame(loop);

When roads take different amounts of time, counting roads does not find the fastest route. Dijkstra's algorithm expands a map of the cheapest cost found so far from one source to every reachable vertex.

Set the source distance to zero and all others to infinity. Repeatedly take the unsettled vertex with the smallest distance and relax each neighbour if the current distance plus edge weight is cheaper. A priority queue commonly gives O((V+E) log V) time. The numbers in the demo are source distances after relaxation.

Weights must be nonnegative for a removed distance to be final. Use a different algorithm for negative edges. If only one destination matters, stop when it is settled.

When to use

Use for minimum-cost routes with nonnegative weights. BFS is simpler when every edge costs the same.

Open as page ↗