Minimum spanning tree

최소 신장 트리

Connects every vertex without cycles while minimizing total edge weight.

Also known as: MST
···
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":"각 노드는 별도 집합","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]],"groups":["A","B","C","D","E","F"]}},{"kind":"graph","caption":"B–E (1): 합침","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]],"activeEdge":"B-E","selected":["B-E"],"groups":["A","B","C","D","B","F"]}},{"kind":"graph","caption":"A–B (2): 합침","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]],"activeEdge":"A-B","selected":["B-E","A-B"],"groups":["A","A","C","D","A","F"]}},{"kind":"graph","caption":"C–E (2): 합침","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]],"activeEdge":"C-E","selected":["B-E","A-B","C-E"],"groups":["C","C","C","D","C","F"]}},{"kind":"graph","caption":"D–E (2): 합침","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]],"activeEdge":"D-E","selected":["B-E","A-B","C-E","D-E"],"groups":["D","D","D","D","D","F"]}},{"kind":"graph","caption":"D–F (2): 합침","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]],"activeEdge":"D-F","selected":["B-E","A-B","C-E","D-E","D-F"],"groups":["D","D","D","D","D","D"]}},{"kind":"graph","caption":"A–C (4): 순환이라 건너뜀","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]],"activeEdge":"A-C","selected":["B-E","A-B","C-E","D-E","D-F"],"groups":["D","D","D","D","D","D"]}},{"kind":"graph","caption":"E–F (4): 순환이라 건너뜀","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]],"activeEdge":"E-F","selected":["B-E","A-B","C-E","D-E","D-F"],"groups":["D","D","D","D","D","D"]}},{"kind":"graph","caption":"B–D (5): 순환이라 건너뜀","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]],"activeEdge":"B-D","selected":["B-E","A-B","C-E","D-E","D-F"],"groups":["D","D","D","D","D","D"]}}];
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);

Suppose every town needs a cable connection and you want the smallest total cable length. This is a different goal from finding the shortest route between two towns. Bold edges in the demo are the chosen connections.

Kruskal's algorithm sorts edges by weight and accepts an edge only when its endpoints are in different components. Otherwise it would make a cycle. Union–find tracks components, giving O(E log E) time dominated by sorting.

Use it to design an inexpensive connected network in an undirected weighted graph. If the graph is disconnected, the result is a minimum spanning forest, not one spanning tree.

When to use

Use to minimize the cost of connecting everything. For a single cheapest route, use a shortest-path algorithm.

Open as page ↗