Algorithm|Javascript Algorithm

가성비 최고의 네트워크 구축하기 (1)

0

크리스에게 새로운 임무가 떨어졌다.

"회사 사무실이 5군데(A, B, C, D, E) 있는데, 이들을 모두 연결하는 내부 네트워크망을 구축해 주세요. 단, 케이블 비용이 비싸니까 가장 적은 비용으로 모든 사무실을 연결해야 합니다."

크리스는 고민에 빠졌다.

"A랑 B를 잇는 건 10만 원, A랑 C는 5만 원... 전부 다 연결하면 돈이 너무 많이 드는데? 사이클(순환) 없이 딱 필요한 만큼만 연결해서 비용을 최소화할 수는 없을까?"

이것이 바로 컴퓨터 과학에서 말하는 최소 신장 트리(Minimum Spanning Tree, MST) 문제다. 도시 가스 파이프 설치, 전력망 구축 등 **"최소 비용으로 모든 지점을 연결"**해야 하는 모든 곳에 쓰이는 알고리즘이다.

1. 신장 트리(Spanning Tree)란?

  • 신장 트리: 그래프 내의 **모든 정점(Vertex)**을 포함하면서, 사이클(Cycle)이 없는 그래프다. (모든 노드가 연결되어 있어야 함)
  • 최소 신장 트리 (MST): 가능한 신장 트리들 중에서, 간선(Edge)들의 가중치 합이 가장 작은 것이다.
  • 쉽게 말해 **"가성비 있게 모든 점을 잇는 방법"**이다. 이를 구하는 대표적인 알고리즘 두 가지, **크루스칼(Kruskal)**과 **프림(Prim)**을 알아보자.

    2. 크루스칼 알고리즘 (Kruskal's Algorithm)

    크루스칼은 아주 직관적이고 "욕심쟁이(Greedy)" 같은 방식이다.

    "가장 싼 케이블부터 일단 쓰고 보자!"

  • 모든 간선을 비용(가중치) 순으로 오름차순 정렬한다.
  • 가장 싼 간선부터 하나씩 선택한다.
  • 단, 사이클이 생기면 안 된다. (이미 연결된 두 지점을 또 연결하는 선이라면 과감히 버린다.)
  • 모든 정점이 연결될 때까지 반복한다.
  • 사이클은 어떻게 알지? (Union-Find)

    크루스칼 알고리즘의 핵심은 "이 선을 그으면 사이클이 생기나?"를 판단하는 것이다.

    이를 위해 **Union-Find(유니온 파인드)**라는 자료구조를 사용한다. "A와 B가 이미 같은 집합(연결됨)인가?"를 $O(1)$에 가깝게 확인해 준다.

    JavaScript

    // 간단한 크루스칼 로직 (개념) function kruskal(n, edges) { // 1. 비용순 정렬 edges.sort((a, b) => a.cost - b.cost); const parent = Array.from({ length: n }, (_, i) => i); // Union-Find용 배열 let totalCost = 0; // 부모 찾기 (Find) const getParent = (x) => { if (parent[x] === x) return x; return parent[x] = getParent(parent[x]); }; // 합치기 (Union) const unionParent = (a, b) => { const rootA = getParent(a); const rootB = getParent(b); if (rootA < rootB) parent[rootB] = rootA; else parent[rootA] = rootB; }; // 2. 간선 선택 for (const { from, to, cost } of edges) { // 부모가 다르면(연결 안 되어 있으면) 연결! if (getParent(from) !== getParent(to)) { unionParent(from, to); totalCost += cost; } } return totalCost; }

    3. 프림 알고리즘 (Prim's Algorithm)

    프림 알고리즘은 "땅따먹기" 방식이다.

  • 임의의 시작점 하나를 고른다. (예: 사무실 A)
  • 현재 연결된 영역(A)에서 갈 수 있는 곳 중 가장 싼 간선을 고른다.
  • 새로운 정점을 영역에 추가한다.
  • 영역이 모든 정점을 포함할 때까지 반복한다.
  • 크루스칼이 "전국 각지에서 싼 것부터 연결"한다면, 프림은 "시작점에서부터 조금씩 뻗어나가는" 방식이다.

    4. 크루스칼 vs 프림: 승자는?

  • 크루스칼: 간선이 적은 **희소 그래프(Sparse Graph)**에서 유리하다. 구현이 쉽다.
  • 프림: 간선이 매우 많은 **밀집 그래프(Dense Graph)**에서 유리하다.
  • 하지만 대부분의 코딩 테스트나 실무 문제에서는 구현이 더 직관적인 크루스칼 알고리즘을 많이 사용한다.

    5. 3장을 마치며

    우리는 3장에서 데이터를 찾는 다양한 방법을 배웠다.

  • 선형 탐색: 무식하게 다 찾기 ($O(N)$)
  • 이진 탐색: 정렬된 곳에서 반씩 쪼개기 ($O(\log N)$)
  • DFS/BFS: 미로와 그래프 탐험하기
  • MST: 최소 비용으로 연결하기
  • 이제 우리는 효율적인 경로를 찾고, 데이터를 정렬하는 법을 안다.

    하지만 알고리즘의 세계에는 "답을 구하긴 구하는데, 똑같은 계산을 수백만 번 반복하느라 느려지는" 멍청한 상황들이 있다.

    "한 번 계산한 건 기억해두면 안 돼?"

    메모장 하나로 알고리즘 성능을 극한으로 끌어올리는 기술. 4장의 주제는 바로 **동적 프로그래밍(Dynamic Programming)**이다.

    4장 동적 알고리즘 - 1. 동적 프로그래밍이란? 편에서 계속된다.


    🔗 참고 링크

  • VisuAlgo - MST (Kruskal & Prim)
  • Programiz - Kruskal's Algorithm
  • Comments (0)

    0/1000 characters
    Loading comments...