최소 신장 트리


최소 신장트리

  • 사용한 간선들의 가중치 합이 최소

  • 간선 가중치를 고려하여 최소 비용의 신장 트리 선택

  • 그래프의 모든 노드 포함

  • 모든 노드는 서로 연결

  • 사이클 X

  • Kruskal 알고리즘, Prim 알고리즘으로 구현

Kruskal 알고리즘

  • 가장 적은 비용으로 모든 노드 연결

  • 간선 선택 기반

  • 서로소 집합(Disjoint Set) / Union-Find 알고리즘 적용

  • 그래프를 간선 리스트로 표현

  • 순서

    1. 모든 가중치를 오름차순으로 정렬

    2. 가중치가 가장 작은 간선 선택

    3. 해당 간선이 연결하려는 노드가 서로 연결되지 않은 상태라면 2개의 노드 연결

    4. 1~3 과정을 반복

Disjoint Set 서로소 집합

  • 공통 원소가 없는 두 집합

  • union, find 두 가지 연산

  • union

    • 두 개의 원소가 각각 포함되어 있는 집합을 하나로 합침
  • find

    • 특정한 원소가 속한 집합이 어떤 집합인지 찾음
  • 하나의 트리를 하나의 집합으로 볼 때, find는 트리의 루트 노드를 찾고, 그 루트 노드를 통해 특정 집합 표현

  • union은 두 원소에 대해 find 연산을 수행하여 각각의 루트 노드를 찾고, 한 쪽의 루트 노드를 다른 쪽에 연결함으로써 하나의 트리로 만드는 합집합 연산 수행

static int find_parent(int x) { if (parent[x] != x) { return find_parent(parent[x]); } return x; } // 경로 압축 기법 적용 static int find_parent_compress(int x){ if (parent[x] != x) { parent[x] = find_parent(parent[x]); } return parent[x]; } static void union_parent(int a, int b){ a = find_parent(a); b = find_parent(b); if (a < b) { parent[b] = a; } else { parent[a] = b; } } public static void main (String[] args) { Scanner sc = new Scanner(System.in); int V = sc.nextInt(); int E = sc.nextInt(); for (int i = 1; i <= V; i++) { parent[i] = i; // 부모 테이블 자기 자신으로 초기화 } for (int i = 1; i <= E; i++) { int a = sc.nextInt(); int b = sc.nextInt(); // 각 간선마다 유니온 연산 union_parent(parent, a, b); } }

프림 알고리즘

  • 시작 정점을 기준으로 가장 작은 간선과 연결된 정점을 선택하여 신장 트리 확장

  • Heap 이용

  • 정점 선택 기반

  • 그래프를 인접 리스트로 표현

  • 순서

    1. 임의의 간선 선택

    2. 선택한 간선의 정점으로부터 가장 낮은 가중치를 갖는 정점 선택

    3. 모든 정점이 선택될 때까지 반복

// O(N^2) 알고리즘 (배열 이용) public static void main (String[] args) { Scanner sc = new Scanner(System.in); N = sc.nextInt(); int[][] input = new int[N][N]; int[] minEdge = new int[N]; boolean[] V = new boolean[N]; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { input[i][j] = sc.nextInt(); } minEdge[i] = Integer.MAX_VALUE; } int minVertex, min, result = 0; minEdge[0] = 0; for (int c = 0; c < N; c++) { min = Integer.MAX_VALUE; minVertex = 0; // priority queue로 대체 가능 // V와 연결된 간선 중 비용이 최소인 간선, 그 간선과 연결된 노드를 찾는다 for (int i = 0; i < N; i++){ if (!V[i] && min > minEdge[i]) { min = minEdge[i]; minVertex = i; } } // 해당 노드를 V에 포함시킨다. result += min; V[minVertex] = true; for (int i = 0; i < N; i++) { // V에서 해당 노드까지 가는 최소 비용 간선 갱신 if (!V[i] && input[minVertex][i] != 0 && minEdge[i] > input[minVertex][i]) { minEdge[i] = iput[minVertex][i]; } } } System.out.println(result); } // O(E log V) 알고리즘 (Priority Queue 이용) static class Vertex implements Comparable<Vertex> { int n, edge; public Vertex(int n, int edge) { super(); this.n = n; this.edge = edge; } @Override public int compareTo(Vertex o) { return this.edge - o.edge; } } public static void main (String[] args) { Scanner sc = new Scanner(System.in); N = sc.nextInt(); int[][] input = new int[N][N]; int[] minEdge = new int[N]; PrioirtyQueue<Vertex> queue = new PriorityQueue<>(); boolean[] V = new boolean[N]; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { input[i][j] = sc.nextInt(); } minEdge[i] = Integer.MAX_VALUE; } int result = 0, nodeCount = 0; // 임의의 노드(0번 노드)를 포함시킨다 minEdge[0] = 0; queue.offer(new Vertex(0, 0)); while (!queue.isEmpty()) { Vertex minVertex = queue.poll(); if (V[minVertex.n]) continue; result += minVertex.edge; V[minVertex.n] = true; if(++nodeCount == N) break; for (int to = 0; to < N; to++) { if (!V[to] && input[minVertex.n][to] != 0 && minEdge[to] > input[minVertex.n][to] { minEdge[to] = input[minVertex.n][to]; queue.offer(new Vertex(to, inut[minVerted.n][to])); } } } System.out.println(result); }

Kruskal vs Prim

  • 프림은 사이클 가능성이 없음

  • 크루스칼은 사이클이 이루어지는지 항상 확인 (find(a) == find(b))

  • 간선의 개수가 적은 경우 크루스칼

  • 간선의 개수가 많은 경우 프림