프림 알고리즘: 최소 신장 트리와 연결되지 않은 그래프 처리

·설명 약 6분·작성: 해비·블로그 소개

프림 알고리즘은 연결되지 않은 그래프에서 어떤 결과를 만드나요?

프림은 이미 연결한 정점 집합에서 바깥으로 나가는 가장 싼 간선을 반복해서 고릅니다. 모든 정점이 연결되어 있으면 최소 신장 트리이며, 연결되지 않았다면 각 연결 요소의 최소 신장 트리를 모은 숲이 됩니다.

적용 조건·범위: 미방문 정점이 남았는데 유한한 key를 가진 정점이 없다면 현재 집합에서 더 갈 수 있는 곳이 없습니다. 없는 간선을 비용 무한대로 선택한 것처럼 처리해서는 안 됩니다. 이 구현은 그 탐색을 끝내고 다음 미방문 정점을 새 시작점으로 잡습니다. 반환값의 components는 이렇게 시작한 연결 요소 수이고, connected는 빈 그래프 또는 연결 요소 한 개일 때 참입니다. 빈 그래프는 components=0, 총합=0인 자명한 경우로 약속합니다.

본문 근거: Competitive Programmer’s Handbook — 15.3 Prim’s algorithm

읽기 전에 알아두면 좋아요

아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.

개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.

과정 위치와 선택 복습 안내

과정 위치: 트리·그래프와 최단거리 · 알고리즘 개념

과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.

JavaScript로 문법·실행 복습하기 (선택)

개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.

프림 알고리즘: 최소 신장 트리와 연결되지 않은 그래프 처리 주제를 표현한 밝은 일러스트와 작은 곰 표지

프림은 이미 연결한 정점 집합에서 바깥으로 나가는 가장 싼 간선을 반복해서 고릅니다. 모든 정점이 연결되어 있으면 최소 신장 트리이며, 연결되지 않았다면 각 연결 요소의 최소 신장 트리를 모은 숲이 됩니다.

선수 지식: 무방향 가중 그래프·visited·최솟값 순회

  • key가 누적 최단 거리가 아님을 설명하기
  • 선택 간선과 총 가중치를 추적하기
  • connected·components로 연결 실패를 구분하기

한 출발점의 거리가 아니라 전체 연결 비용을 줄입니다

무방향 가중 그래프의 신장 트리는 모든 정점을 연결하면서 순환이 없는 간선 집합입니다. 연결된 정점 V개에는 V−1개의 선택 간선이 필요합니다. 최소 신장 트리는 그런 집합 중 가중치 합이 최소인 것입니다. 출발점에서 각 정점으로 가는 거리를 최소화하는 문제와 다르므로, 최소 신장 트리 안의 경로가 원래 그래프의 최단 경로일 필요는 없습니다.

네 정점의 최소 신장 트리를 이루는 비용3·1·2의 세 무방향 간선
선택한0—2,2—1,1—3의 총합은6입니다. 출발점별 최단 거리와 전체 연결 비용을 구분합니다. 원본 보기 (새 탭)

예제는 간선 한 개를 [u,v,w]로 받으며 내부에서 양방향 인접 목록을 만듭니다. 입력에 같은 무방향 간선을 두 번 적을 필요는 없습니다. 서로 같은 두 정점 사이의 평행 간선도 각각 유지하여 더 싼 연결을 선택할 수 있습니다. 자기 루프는 다른 정점을 연결하지 않으므로 선택되지 않습니다. 가중치는 음수도 허용하지만 안전한 정수로 한정하며, 전체 간선 가중치 절댓값 합도 안전한 정수 범위인지 검사합니다.

key는 현재 집합에 붙일 수 있는 최소 간선입니다

각 미방문 정점 v의 key[v]에는 지금까지 선택한 집합과 v 사이에서 찾은 최소 간선 가중치를 저장합니다. 그 간선의 반대편 정점은 parent[v]에 둡니다. 시작 정점의 key를 0으로 두고, 미방문 정점 중 유한한 key가 가장 작은 것을 선택합니다. 새 정점 u를 넣으면 u의 이웃에 대해 key[v]와 간선 가중치 w를 직접 비교합니다. key[u]+w를 비교하면 최단 경로의 누적 거리를 구하는 다른 갱신이 됩니다.

프림의 선택0·2·1·3과 key1·key3 갱신 및 누적합
key는 현재 선택 집합에 붙는 간선 비용입니다. key[u]+w를 누적 거리처럼 비교하지 않습니다. 원본 보기 (새 탭)
간선 0—1:4, 0—2:3, 1—2:1, 1—3:2, 2—3:4
새 정점 선택 간선 변한 후보 총합
0 없음 key[1]=4, key[2]=3 0
2 0—2:3 key[1]=1, key[3]=4 3
1 2—1:1 key[3]=2 4
3 1—3:2 남은 정점 없음 6

후보 key는 더 싼 연결을 발견하면 줄어듭니다. 한 번 선택한 정점에 대해서는 부모를 다시 바꾸지 않으므로 선택 간선들은 현재 집합에 정점을 하나씩 추가하며 순환 없이 이어집니다.

경계를 가로지르는 최소 간선은 안전하게 고를 수 있습니다

현재 선택 집합 S와 바깥 정점 집합을 나누면 둘 사이의 간선들이 하나의 경계를 가로지릅니다. 프림이 고르는 것은 이 간선 중 최소 가중치입니다. 현재 선택 간선들을 포함하는 최소 신장 트리에서 그 간선이 빠져 있다고 생각해 보세요. 그 간선을 추가하면 생기는 순환에는 같은 경계를 가로지르는 다른 간선이 있습니다. 그 다른 간선을 빼면 연결은 유지되고, 선택한 간선이 최소이므로 총비용은 증가하지 않습니다.

선택 집합S={0,2} 경계를 가로지르는 비용4·1·4 중 최소1
선택 집합과 바깥을 잇는 최소 간선2—1을 봅니다. 교환 논증은 이를 포함해도 총비용이 증가하지 않음을 설명합니다. 원본 보기 (새 탭)

따라서 이번 간선을 포함하도록 바꾸어도 최소인 트리가 있으며, 이 논리를 반복하여 선택의 정당성을 이어갈 수 있습니다. 가중치가 같은 간선이 여럿이면 최소 신장 트리도 여럿일 수 있습니다. 특정 간선 목록 하나가 유일한 정답이라고 가정하지 말고, 전체 연결·순환 없음·간선 수·가중치 합을 함께 보세요. 가중치가 음수여도 위 교환 논리는 유지되므로, 음수 때문에 프림을 거절할 필요는 없습니다.

남은 key가 무한대이면 새 연결 요소를 시작합니다

미방문 정점이 남았는데 유한한 key를 가진 정점이 없다면 현재 집합에서 더 갈 수 있는 곳이 없습니다. 없는 간선을 비용 무한대로 선택한 것처럼 처리해서는 안 됩니다. 이 구현은 그 탐색을 끝내고 다음 미방문 정점을 새 시작점으로 잡습니다. 반환값의 components는 이렇게 시작한 연결 요소 수이고, connected는 빈 그래프 또는 연결 요소 한 개일 때 참입니다. 빈 그래프는 components=0, 총합=0인 자명한 경우로 약속합니다.

0—1 비용2와2—3 비용3으로 분리된 최소 신장 숲
연결 요소2개이므로 connected=false이고 총합5는 숲의 비용입니다. 유한 후보가 없으면 다음 요소를 시작합니다. 원본 보기 (새 탭)

예를 들어 간선 0—1:2와 2—3:3만 있으면 components=2, connected=false, 총합=5입니다. 이 값은 모든 정점이 하나로 연결된 트리의 비용이 아니라 각 요소를 최소로 연결한 숲의 비용입니다. 정점마다 최소 후보를 선형 탐색하므로 시간은 O(V²+E), 인접 목록과 상태 공간은 O(V+E)입니다. 우선순위 큐를 사용하는 변형과 복잡도를 혼동하지 마세요. 이 학습본은 선택과 갱신을 그대로 관찰할 수 있는 배열 탐색 구현입니다.

전체 코드와 직접 실행

개념을 읽었다면 아래 예제를 직접 실행하며 상태 변화를 확인할 수 있습니다.

전체 JavaScript 코드와 실행 방법 펼치기

별도 패키지는 필요하지 않습니다. Node.js가 처음이라면 브라우저와 Node.js 시작 안내부터 확인하세요.

algorithm-prim-minimum-spanning-forest.mjs 파일을 만들고 아래 전체 코드를 저장합니다.

function primForest(n, edges) {
  if (!Number.isInteger(n) || n < 0 || !Array.isArray(edges)) {
    throw new RangeError('정점 수와 간선 배열을 확인하세요.');
  }
  const graph = Array.from({length:n}, () => []);
  let magnitude = 0;
  for (const edge of edges) {
    if (!Array.isArray(edge) || edge.length !== 3) throw new TypeError('간선은 [u,v,w]입니다.');
    const [u,v,w] = edge;
    if (![u,v].every(x => Number.isInteger(x) && x >= 0 && x < n)) {
      throw new RangeError('간선 정점 번호가 범위를 벗어났습니다.');
    }
    magnitude += Math.abs(w);
    if (!Number.isSafeInteger(w) || !Number.isSafeInteger(magnitude)) {
      throw new RangeError('가중치와 절댓값 합은 안전한 정수여야 합니다.');
    }
    graph[u].push([v,w]);
    graph[v].push([u,w]);
  }
  const visited = Array(n).fill(false);
  const key = Array(n).fill(Infinity);
  const parent = Array(n).fill(-1);
  const chosen = [];
  let components = 0, weight = 0;
  for (let start = 0; start < n; start++) {
    if (visited[start]) continue;
    components++;
    key[start] = 0;
    while (true) {
      let u = -1;
      for (let v = 0; v < n; v++) {
        if (!visited[v] && key[v] < Infinity && (u === -1 || key[v] < key[u])) u = v;
      }
      if (u === -1) break;
      visited[u] = true;
      if (parent[u] !== -1) {
        chosen.push([parent[u],u,key[u]]);
        weight += key[u];
      }
      for (const [v,w] of graph[u]) {
        if (!visited[v] && w < key[v]) { key[v] = w; parent[v] = u; }
      }
    }
  }
  return { connected: n === 0 || components === 1, components, weight, edges: chosen };
}

console.log(JSON.stringify(primForest(4,[[0,1,4],[0,2,3],[1,2,1],[1,3,2],[2,3,4]])));
console.log(JSON.stringify(primForest(4,[[0,1,2],[2,3,3]])));

파일이 있는 폴더의 터미널에서 실행합니다.

node algorithm-prim-minimum-spanning-forest.mjs

기대 출력

{"connected":true,"components":1,"weight":6,"edges":[[0,2,3],[2,1,1],[1,3,2]]}
{"connected":false,"components":2,"weight":5,"edges":[[0,1,2],[2,3,3]]}

비용과 적용 범위

정점 V개, 무방향 입력 간선 E개에 대해 선형 최소 후보 탐색 구현의 시간 O(V²+E), 인접 목록과 상태 공간 O(V+E)입니다. 안전한 정수 가중치와 그 절댓값 합을 검사합니다. 연결되지 않은 입력은 connected=false인 최소 신장 숲으로 반환합니다.

선형 최소 키 탐색을 사용하는 프림 구현의 시간과 공간 비용
미방문 정점의 최소 key를 선형 탐색하므로 O(V²+E), 인접 목록과 상태 저장은 O(V+E)입니다. 원본 보기 (새 탭)

직접 확인해 보기

정점 0·1·2·3에 간선 [0,1,2]와 [2,3,3]만 있으면 반환된 총합 5를 최소 신장 트리 비용이라고 불러도 될까요?

두 연결 요소가 분리된 프림 자기 확인 문제의 초기 그래프
가중치 2와 3인 두 간선이 분리되어 있습니다. 반환 총합 5를 어떤 이름으로 불러야 하는지 본문에서 확인해 보세요. 원본 보기 (새 탭)
답과 이유 확인

모든 정점을 잇는 트리가 존재하지 않으므로 그 표현은 맞지 않습니다. connected=false, components=2이며, 5는 두 연결 요소의 최소 신장 트리를 합한 최소 신장 숲의 비용입니다.

참고 자료

이 글이 도움이 되었나요?

조회 중

이어서 공부하기

체크포인트: 예제의 상태 변화를 손으로 추적하고 아래 문제에 적용하세요. 값이 없는 경우와 원소 하나인 경우도 설명해 보세요. 층별 방문 순서와 최솟값 추출을 추적하고, 다익스트라의 비음수 조건과 오래된 후보를 설명하세요.

다음 추천 · 알고리즘 개념: 탐욕 알고리즘: 큰 동전 선택과 반례로 확인하는 최적성

다음 주제는 그리디 선택과 반례입니다. 당장의 선택을 반복하는 규칙을 세우고 최적해를 보장하는 조건과 반례를 비교합니다.

권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.

원래 과정 목록 순서 (선택)

읽음 기록

읽음 — / 전체 30개 · 필수 0개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기