Algorithm

[알고리즘] 크루스칼 알고리즘 (Kruskal Algorithm)

bu119 2023. 12. 14. 17:00
728x90
반응형

신장 트리 (Spanning Tree)

그래프에서 모든 노드를 포함하면서 사이클이 존재하지 않는 부분 그래프를 의미한다.

  • 모든 노드가 포함되어 서로 연결되면서 사이클이 존재하지 않는다는 조건은 트리의 조건이기도 하다.

 

 

신장 트리 사용 목적

모든 노드가 연결되어 있지만 일부 간선을 사용하지 않아도 된다는 점에서 실제 문제 상황에서 효과적으로 사용될 수 있다.

 

최소 신장 트리 (MST, Minimum Spanning Tree)

  • 최소한의 비용으로 구성되는 신장 트리를 찾아야 할 때 어떻게 해야 할까요?
  • 예를 들어 N개의 도시가 존재하는 상황에서 두 도시 사이에 도로를 놓아 전체 도시가 서로 연결될 수 있게 도로를 설치하는 경우를 생각해 봅시다.
    • 두 도시 A, B를 선택했을 때 A에서 B로 이동하는 경로가 반드시 존재하도록 도로를 설치합니다.
  • 1 - 2(23)와 2 - 3(13)을 연결하면 1 - 3(25)을 연결하지 않아도 모든 노드가 연결되어있고 최소 비용을 가진다.

 

즉, 최소 신장 트리모든 노드가 서로 연결되어 이동이 가능하도록 만들되 최소한의 비용으로 전체 신장 트리를 구성하는 것을 의미한다.

 

크루스칼 알고리즘 (Kruskal Algorithm)

  • 대표적인 최소 신장 트리 알고리즘이다.
  • 그리디 알고리즘으로 분류된다.

 

1. 크루스칼 알고리즘의 동작 과정

  1. 간선 데이터를 비용에 따라 오름차순으로 정렬합니다. (비용이 적은 간선부터 확인한다.)
  2. 간선을 하나씩 확인하며 현재의 간선이 사이클을 발생시키는지 확인합니다.
    • 사이클이 발생하지 않는 경우, 최소 신장 트리에 포함시킵니다. (By Union-Find)
    • 사이클이 발생하는 경우, 최소 신장 트리에 포함시키지 않습니다. 
  3. 모든 간선에 대하여 2번의 과정을 반복합니다.

 

 

 

 

 

 

 

 

 

 

 

반응형

 

2. 크루스칼 알고리즘 소스 코드

1) 크루스칼 알고리즘 (Python)

# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
    # 루트 노드를 찾을 때까지 재귀 호출
    if parent[x] != x:
    	parent[x] = find_parent(parent, parent[x])
    return parent[x]
    
# 두 원소가 속한 집합을 합치기
def union_parent(parent, a, b):
	a = find_parent(parent, a)
    b = find_parent(parent, b)
    if a<b:
    	parent[b] = a
    else:
    	parent[a] = b

# 노드의 개수와 간선(Union 연산)의 개수 입력 받기
v,e = map(int, input().split())
parent = [0]*(v+1)  # 부모 테이블 초기화하기

# 모든 간선을 담을 리스트와, 최종 비용을 담을 변수
edges = []
result = 0

# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, v+1):
	parent[i] = i
    
# 모든 간선에 대한 정보를 입력 받기
for _ in range(e):
	a,b,cost = map(int, input().split())
    # 비용순으로 정렬하기 위해서 튜플의 첫 번째 원소를 비용으로 설정
    edges.append((cost, a, b))

# 간선을 비용순으로 정렬
edges.sort()

# 간선을 하나씩 확인하며
for edge in edges:
	cost, a, b = edge
    # 사이클이 발생하지 않는 경우에만 집합에 포함
    if find_parent(parent, a) != find_parent(parent, b):
    	union_parent(parent, a, b)
        result += cost

print(result)

2) 크루스칼 알고리즘 (Java)

import java.util.*;

class Edge implements Comparable<Edge> {

    private int distance;
    private int nodeA;
    private int nodeB;

    public Edge(int distance, int nodeA, int nodeB) {
        this.distance = distance;
        this.nodeA = nodeA;
        this.nodeB = nodeB;
    }

    public int getDistance() {
        return this.distance;
    }

    public int getNodeA() {
        return this.nodeA;
    }

    public int getNodeB() {
        return this.nodeB;
    }

    // 거리(비용)가 짧은 것이 높은 우선순위를 가지도록 설정
    @Override
    public int compareTo(Edge other) {
        if (this.distance < other.distance) {
            return -1;
        }
        return 1;
    }
}

public class Main {

    // 노드의 개수(V)와 간선(Union 연산)의 개수(E)
    // 노드의 개수는 최대 100,000개라고 가정
    public static int v, e;
    public static int[] parent = new int[100001]; // 부모 테이블 초기화하기
    // 모든 간선을 담을 리스트와, 최종 비용을 담을 변수
    public static ArrayList<Edge> edges = new ArrayList<>();
    public static int result = 0;

    // 특정 원소가 속한 집합을 찾기
    public static int findParent(int x) {
        // 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
        if (x == parent[x]) return x;
        return parent[x] = findParent(parent[x]);
    }

    // 두 원소가 속한 집합을 합치기
    public static void unionParent(int a, int b) {
        a = findParent(a);
        b = findParent(b);
        if (a < b) parent[b] = a;
        else parent[a] = b;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        v = sc.nextInt();
        e = sc.nextInt();

        // 부모 테이블상에서, 부모를 자기 자신으로 초기화
        for (int i = 1; i <= v; i++) {
            parent[i] = i;
        }

        // 모든 간선에 대한 정보를 입력 받기
        for (int i = 0; i < e; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            int cost = sc.nextInt();
            edges.add(new Edge(cost, a, b));
        }

        // 간선을 비용순으로 정렬
        Collections.sort(edges);

        // 간선을 하나씩 확인하며
        for (int i = 0; i < edges.size(); i++) {
            int cost = edges.get(i).getDistance();
            int a = edges.get(i).getNodeA();
            int b = edges.get(i).getNodeB();
            // 사이클이 발생하지 않는 경우에만 집합에 포함
            if (findParent(a) != findParent(b)) {
                unionParent(a, b);
                result += cost;
            }
        }

        System.out.println(result);
    }
}

 

3. 크루스칼 알고리즘 성능 분석

  • 크루스칼 알고리즘은 간선의 개수가 E개일 때, 의 시간 복잡도를 가진다.
  • 크루스칼 알고리즘에서 가장 많은 시간을 요구하는 곳은 간선에 대해서 정령을 수행하는 부분이다.
    • 표준 라이브러리를 이용해 E개의 데이터를 정렬하기 위한 시간 복잡도는 O(ElogE)이다.

 


참고자료
https://esoongan.tistory.com/78

https://velog.io/@yeahxne/%EC%9D%B4%EC%BD%94%ED%85%8C-2021-16.-%ED%81%AC%EB%A3%A8%EC%8A%A4%EC%B9%BC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98

https://www.youtube.com/watch?v=Gj7s-Nrt1xE

728x90
반응형