컴퓨터 구조 운영체제 네트워크 자료구조 알고리즘 데이터베이스 내 학습
algo · 09

최소 신장 트리

알고리즘그래프신장 트리

최소 신장 트리(Minimum Spanning Tree, MST)는 그래프의 모든 노드를 사이클 없이 연결하면서, 사용한 간선의 가중치 합을 최소로 만드는 부분 그래프입니다. algo-05(다익스트라)와 같은 그래프를 쓰지만, 다익스트라는 "한 지점에서 각 지점까지의 최단 거리"를 구하고 MST는 "전체를 잇는 가장 싼 연결망"을 구한다는 점이 다릅니다.

핵심 개념

신장 트리(Spanning Tree)

그래프의 모든 노드를 포함하면서 사이클이 없는 부분 그래프입니다. 노드가 n개인 연결 그래프의 신장 트리는 간선이 정확히 n-1개입니다.

핵심 개념

크루스칼(Kruskal) — 간선 중심

모든 간선을 가중치가 작은 순서로 정렬한 뒤, 사이클을 만들지 않는 간선만 골라 추가합니다. 이미 연결된 두 노드를 다시 연결하면 사이클이 생기므로 건너뜁니다.

핵심 개념

프림(Prim) — 노드 중심

시작 노드에서 출발해, 지금까지 만든 트리와 트리 밖 노드를 잇는 가장 싼 간선을 매번 골라 트리를 한 노드씩 키워나갑니다. algo-05 다익스트라와 거의 같은 골격입니다.

핵심 개념

사이클 검사 (유니온-파인드)

크루스칼에서 "두 노드가 이미 연결되어 있는지"를 빠르게 확인하기 위해 유니온-파인드(Union-Find) 자료구조를 사용합니다. 같은 그룹이면 연결 시 사이클이 생깁니다.

핵심 개념

둘 다 그리디다

크루스칼은 "가장 싼 간선"을, 프림은 "트리에 연결하는 가장 싼 간선"을 매번 선택하고 절대 번복하지 않습니다. algo-07(그리디)과 달리, MST 문제에서는 이 그리디 선택이 최적해를 보장한다고 증명되어 있습니다.

핵심 개념

같은 그래프, 같은 답

이 시각화의 두 알고리즘은 서로 다른 순서로 진행하지만, 최종 MST의 총 가중치는 정확히 같습니다(13). 접근 방식이 달라도 "최소"라는 답 자체는 하나로 수렴합니다.

다익스트라와 헷갈리기 쉬운 지점 — 프림의 코드는 다익스트라와 거의 동일합니다. 차이는 딱 하나, 다익스트라는 "시작 노드부터의 누적 거리"를 기준으로 고르고, 프림은 "트리에 새로 연결하는 그 간선 하나의 가중치"만 기준으로 고릅니다.
언제 크루스칼, 언제 프림 — 간선 수가 적고 노드 수가 많은 그래프(희소 그래프)는 크루스칼이, 노드 수가 적고 간선이 촘촘한 그래프(밀집 그래프)는 프림이 더 효율적인 경우가 많다고 알려져 있지만, 구현에 따라 달라집니다.
가중치가 같은 간선이 있다면 — 이 그래프처럼 가중치가 같은 간선(1-2와 2-4 모두 2)이 있어도, 어느 쪽을 먼저 골라도 최종 총 가중치는 항상 같습니다. MST의 "간선 구성"은 여러 개일 수 있어도 "총 가중치"는 유일합니다.
이 시각화에서 확인할 것 — "크루스칼" 탭에서 정렬된 간선을 순서대로 확인하며 사이클 여부를 판단하는 과정을, "프림" 탭에서 트리가 노드를 하나씩 흡수하며 자라나는 과정을 확인하세요.

최소 신장 트리의 역사

최소 신장 트리(MST) 알고리즘은 20세기 전반부터 여러 사람이 따로 발견한 것으로 알려져 있습니다. 보루프카(Borůvka)가 1926년에, 크루스칼(Kruskal)이 1956년에, 프림(Prim)이 1957년에 발표했습니다. 프림의 알고리즘은 이보다 앞서 야르니크(Jarník)가 1930년에 발표한 것과 같아서 프림–야르니크 알고리즘이라고도 부릅니다.

숫자로 확인하기

이 시각화의 그래프 — 노드 6개, 간선 9개에서 MST는 간선 5개(노드 수 − 1)로 총 가중치가 13입니다. 크루스칼은 가중치 순으로 간선을 보다가 5개를 고르면 멈추는데, 이 시각화에서는 9개 중 7개를 살펴보고 그중 5개를 고릅니다(가중치가 같은 간선의 순서에 따라 살펴보는 개수는 달라질 수 있습니다)(0-2: 1, 1-2: 2, 2-4: 2, 3-4: 3, 4-5: 5).
사이클로 건너뛰는 간선 — 0-1(4)과 1-3(5)은 이미 연결된 노드를 다시 잇게 되어 건너뜁니다.
복잡도 — 크루스칼은 간선 정렬이 대부분이라 O(E log E), 이진 힙을 쓴 프림은 O(E log V)(인덱스 우선순위 큐 기준)입니다. 둘 다 같은 차수이고, 어느 쪽이 더 빠른지는 그래프의 밀도와 구현에 따라 달라집니다.

자주 하는 오해

MST는 항상 하나뿐이다 — 총 가중치는 하나로 정해지지만 같은 가중치의 간선이 있으면 간선 구성이 여러 개일 수 있습니다. 다만 동률이 있다고 항상 여러 개인 것은 아니어서, 이 시각화의 그래프는 같은 가중치 간선(1-2와 2-4)이 있어도 MST가 하나뿐입니다.
MST는 두 노드 사이의 최단 경로를 준다 — MST는 모든 노드를 연결하는 데 드는 총합을 줄이는 것이고, 두 노드 사이의 경로가 최단이 되지는 않습니다. 그 문제는 다익스트라가 풉니다.
가중치가 음수면 MST를 못 구한다 — 크루스칼과 프림은 음수 가중치에서도 동작합니다. 음수 간선을 제한하는 쪽은 다익스트라입니다.
연결되지 않은 그래프에도 MST가 있다 — 신장 트리는 모든 노드를 연결해야 하므로 그래프가 연결되어 있어야 합니다. 연결되지 않으면 각 연결 요소마다 신장 트리를 구해 신장 숲이라고 부릅니다.

면접에서 이렇게 물어요

면접 질문

크루스칼과 프림의 차이는 무엇인가요?

크루스칼은 모든 간선을 가중치순으로 보며 사이클이 안 생기는 것만 고르고, 프림은 한 노드에서 시작해 현재 트리와 바깥을 잇는 가장 싼 간선으로 트리를 키웁니다.

면접 질문

유니온-파인드는 왜 필요한가요?

간선을 추가했을 때 사이클이 생기는지는 두 노드가 이미 같은 집합에 있는지로 판단할 수 있고, 유니온-파인드는 이를 거의 상수 시간에 확인하고 합칩니다.

면접 질문

MST가 유일하지 않을 수 있는 이유는 무엇인가요?

가중치가 같은 간선이 여러 개 있으면 어느 것을 먼저 고르느냐에 따라 구성이 달라지지만, 총 가중치는 같습니다.

면접 질문

프림과 다익스트라는 어떻게 다른가요?

둘 다 우선순위 큐로 가장 가까운 노드를 고르는 같은 골격이지만, 다익스트라는 시작점에서의 누적 거리를, 프림은 트리에 연결하는 간선 하나의 가중치를 기준으로 합니다.

실제 시스템에서는

NetworkX — 파이썬 그래프 라이브러리 NetworkX의 minimum_spanning_tree는 알고리즘으로 kruskal(기본), prim, boruvka를 고를 수 있습니다.
네트워크 설계 — 모든 지점을 최소 비용으로 연결하는 통신망·배선 문제를 MST로 모델링할 수 있습니다.

함께 보면 좋은 토픽

다익스트라 — 프림과 같은 골격이지만 기준이 다른 알고리즘입니다.
그리디 — MST의 그리디 선택이 항상 최적인 이유의 배경을 봅니다.
그래프 — 간선 목록과 인접 리스트 표현을 봅니다.
힙 — 프림에서 쓰는 우선순위 큐의 바탕을 봅니다.
라우팅 — 경로 계산과 트리 구조가 네트워크에서 쓰이는 방식을 봅니다.

참고 자료

원 논문 — Kruskal, "On the shortest spanning subtree of a graph and the traveling salesman problem", Proceedings of the AMS 7, 1956
원 논문 — Prim, "Shortest connection networks and some generalizations", Bell System Technical Journal 36, 1957
교재 — Cormen, Leiserson, Rivest, Stein, "Introduction to Algorithms" 4판, "Minimum Spanning Trees" 장

Java — 크루스칼 (유니온-파인드로 사이클 검사)

/* ── 가중치 오름차순 정렬 후, 사이클을 안 만들면 추가 ──────── */
public int kruskal(int n, int[][] edges) {  // edges[i] = {u, v, weight}
    Arrays.sort(edges, (a, b) -> a[2] - b[2]);   // 가중치 기준 정렬
    int[] parent = new int[n];
    for (int i = 0; i < n; i++) parent[i] = i;

    int totalWeight = 0, edgeCount = 0;
    for (int[] e : edges) {
        int ru = find(parent, e[0]), rv = find(parent, e[1]);
        if (ru != rv) {                     // 서로 다른 그룹 → 연결해도 사이클 없음
            parent[ru] = rv;
            totalWeight += e[2];
            edgeCount++;
            if (edgeCount == n - 1) break;  // 간선을 n-1개 모으면 완성
        }
    }
    return totalWeight;
}

private int find(int[] parent, int x) {
    while (parent[x] != x) x = parent[x];   // x가 속한 그룹의 대표 노드 찾기
    return x;
}

find(parent, u) != find(parent, v) 한 줄이 "사이클이 생기는지" 판단의 전부입니다 — 두 노드가 이미 같은 그룹(같은 대표 노드)이면 연결 시 반드시 사이클이 생깁니다.
정렬에 O(E log E), 유니온-파인드 연산은 사실상 O(1)에 가까워 전체는 O(E log E)입니다.
algo-07(그리디)의 활동 선택처럼 "정렬 후 한 번 훑기"와 같은 패턴입니다.

Java — 프림 (algo-05 다익스트라와 거의 동일한 골격)

/* ── "누적 거리" 대신 "트리에 연결하는 간선 하나의 가중치"만 비교 ── */
public int prim(int n, List<List<int[]>> adj, int start) {
    boolean[] inTree = new boolean[n];
    int[] minEdge = new int[n];
    Arrays.fill(minEdge, Integer.MAX_VALUE);
    minEdge[start] = 0;

    int totalWeight = 0;
    for (int count = 0; count < n; count++) {
        int u = -1;
        for (int i = 0; i < n; i++) {          // 트리 밖에서 연결 비용이 가장 싼 노드 탐색
            if (!inTree[i] && (u == -1 || minEdge[i] < minEdge[u])) u = i;
        }
        inTree[u] = true;
        totalWeight += minEdge[u];

        for (int[] edge : adj.get(u)) {          // u의 인접 간선으로 연결 비용 갱신
            int v = edge[0], w = edge[1];
            if (!inTree[v] && w < minEdge[v]) minEdge[v] = w;
        }
    }
    return totalWeight;
}

다익스트라 코드와 나란히 놓고 비교해보면 구조가 거의 동일합니다 — 차이는 minEdge[v] = w 딱 한 줄입니다. 다익스트라는 dist[u] + w(누적 거리)와 비교하지만, 프림은 w(간선 하나의 가중치)만 비교합니다.
"지금까지 온 총 거리"가 중요한가, "트리에 새로 연결하는 이 간선 하나"가 중요한가 — 이 관점 차이가 최단 경로 문제와 MST 문제를 가릅니다.

Java — 유니온-파인드 경로 압축 (최적화)

/* ── find할 때마다 트리를 납작하게 눌러 다음 find를 더 빠르게 ── */
private int findCompressed(int[] parent, int x) {
    if (parent[x] != x) {
        parent[x] = findCompressed(parent, parent[x]);  // 재귀로 대표 노드를 찾은 뒤
    }
    return parent[x];                    // x를 대표 노드에 직접 연결(경로 압축)
}

기본 find는 그룹이 길게 이어져 있으면 대표 노드까지 거슬러 올라가는 데 시간이 걸립니다.
경로 압축은 한 번 찾은 뒤 중간 노드들을 전부 대표 노드에 직접 연결해, 다음번 find는 거의 O(1)에 끝나게 만듭니다.
이 시각화의 크루스칼은 간단한 버전(경로 압축 없음)으로 동작하지만, 그래프가 커지면 이 최적화가 성능에 큰 차이를 만듭니다.

Q1

최소 신장 트리(MST)의 정의는?

'신장(spanning)'은 모든 정점을 포함한다는 뜻입니다.
MST는 그래프의 모든 정점을 사이클 없이 연결하면서 간선 가중치의 합이 최소가 되는 트리입니다.

Q2

크루스칼(Kruskal) 알고리즘의 방식은?

간선 중심으로, 가장 싼 것부터 고릅니다.
크루스칼은 모든 간선을 가중치 오름차순으로 정렬한 뒤, 사이클을 만들지 않는 간선을 순서대로 선택해 MST를 구성합니다.

Q3

프림(Prim) 알고리즘과 크루스칼의 차이는?

프림은 '정점 중심 확장', 크루스칼은 '간선 중심 선택'입니다.
프림은 시작 정점에서부터 연결된 간선 중 가장 싼 것을 골라 트리를 점점 확장해가고, 크루스칼은 전체 간선을 미리 정렬해두고 사이클을 피하며 선택합니다. 둘 다 MST를 구하지만 접근 방식이 다릅니다.