3 분 소요

크루스칼 알고리즘

  • 최소 신장 트리를 만들기 위한 알고리즘입니다.
  • 이 글은 크루스칼 알고리즘의 동작 과정, 코드 구현 방법에 대해서 정리한 내용입니다.


동작 과정

  1. 간선 데이터를 가중치 기준으로 오름차순 정렬합니다.
  2. 가중치 낮은 간선부터 확인하며 현재의 간선이 사이클을 발생시키는지 확인합니다.
    • 사이클이 발생하지 않는 경우 최소 신장 트리에 포함시킵니다.
    • 사이클이 발생하는 경우 최소 신장 트리에 포함시키지 않습니다.
  3. 모든 간선에 대하여 2번 과정을 반복합니다.

예시

sample

  • 간선 리스트로 그래프를 구현합니다.
    • 정렬을 위해서 (가중치, 시작 노드, 도착 노드)로 저장합니다.
  • 사이클 테이블을 초기화합니다.
    • 초기에는 아무도 연결하지 않았기 때문에 자기 자신을 부모로 설정합니다.

sample

  • 간선 데이터를 가중치 기준으로 오름차순 정렬합니다.

sample

  • 가중치가 2간선 (1,2)를 확인합니다.
  • 노드 1과 노드 2의 부모는 서로 다르기 때문에 Union 연산을 수행합니다.
    • 유니온 파인드 알고리즘에 따라 노드 2의 부모를 1로 설정합니다.

sample

  • 가중치가 4간선 (1,3)을 확인합니다.
  • 노드 1과 노드 3의 부모는 서로 다르기 때문에 Union 연산을 수행합니다.
    • 유니온 파인드 알고리즘에 따라 노드 3의 부모를 1로 설정합니다.

sample

  • 가중치가 5간선 (2,3)을 확인합니다.
  • 노드 2과 노드 3의 부모는 1로 같기 때문에 Union 연산을 수행하지 않습니다.

sample

  • 가중치가 7간선 (2,4)를 확인합니다.
  • 노드 2과 노드 4의 부모는 서로 다르기 때문에 Union 연산을 수행합니다.
    • 유니온 파인드 알고리즘에 따라 노드 4의 부모를 1로 설정합니다.

sample

  • 가중치가 10간선 (3,4)를 확인합니다.
  • 노드 3과 노드 4의 부모는 1로 같기 때문에 Union 연산을 수행하지 않습니다.
  • 모든 간선을 확인한 결과 주어진 그래프에서 최소 신장 트리는 가중치 합이 13인 부분 그래프가 됩니다.


코드 구현

간선 리스트 구현

vector<array<int, 3>> list_e; // 간선 리스트

// 간선 리스트 초기화
list_e.push_back({ 2,1,2 });
list_e.push_back({ 4,1,3 });
list_e.push_back({ 10,3,4 });
list_e.push_back({ 7,2,4 });
list_e.push_back({ 5,2,3 });
  • 간선 리스트를 vector를 사용해서 구현합니다.
  • (가중치, 시작 노드, 도착 노드)로 리스트를 저장합니다.

사이클 테이블 구현

int parent[100001]; // 유니온 파인드의 사이클 테이블

// 유니온 파인드 초기화
for (int i = 1; i <= 4; i++)
	parent[i] = i;
  • 사이클 테이블을 1차원 배열로 구현합니다.
  • 초기에는 노드가 연결되어 있지 않기 때문에 자기 자신을 부모로 설정합니다.

Find 함수, Union 함수 구현

// 특정 원소의 집합을 찾는 함수
int FindParent(int node) {
	if (parent[node] == node) return parent[node]; // 부모와 노드가 같으면 재귀 종료
	return parent[node] = FindParent(parent[node]); // 부모와 노드가 다르면 재귀 호출
}

// 두 노드를 합치는 함수
void UnionParent(int node1, int node2) {
    // 노드의 부모를 탐색
	node1 = FindParent(node1);
	node2 = FindParent(node2);

	// 부모를 비교했을 때 부모가 큰 노드의 부모를 작은 노드의 부모 값에 넣는다.
	if (node1 < node2) 
		parent[node2] = node1;
	else 
		parent[node1] = node2;
}
  • 특정 원소의 집합을 찾는 Find 함수를 구현합니다.
    • 부모와 노드가 같으면 재귀를 종료합니다.
    • 부모와 노드가 다르면 해당 노드의 부모를 찾기 위해 재귀 호출합니다.
    • 경로 압축으로 재귀를 종료하면 부모의 값을 바로 수정하게 설정합니다.
  • 두 노드를 합치는 Union 함수를 구현합니다.
    • 두 노드의 부모를 비교합니다.
    • 부모가 큰 노드의 부모를 작은 노드의 부모로 설정합니다.

사이클 판별 부분

// 최소 신장 트리 구성
// 사이클이 발생하면 트리에 포함하지 않음
// 사이클이 발생하지 않으면 트리에 포함
int sum = 0; // 가중치 합
for (array<int, 3> edge : list_e) {
	if (FindParent(edge[1]) != FindParent(edge[2])) {
		sum += edge[0];
		UnionParent(edge[1], edge[2]);
	}
}
  • 가중치가 낮은 간선부터 확인합니다.
  • 시작 노드의 부모와 도착 노드의 부모가 다르면 Union 연산을 수행합니다.

전체 코드

#include <iostream>
#include <vector>
#include <array>
#include <algorithm>

using namespace std;

int parent[100001]; // 유니온 파인드의 사이클 테이블
vector<array<int, 3>> list_e; // 간선 리스트

// 특정 원소의 집합을 찾는 함수
int FindParent(int node) {
	if (parent[node] == node) return parent[node]; // 부모와 노드가 같으면 재귀 종료
	return parent[node] = FindParent(parent[node]); // 부모와 노드가 다르면 재귀 호출
}

// 두 노드를 합치는 함수
void UnionParent(int node1, int node2) {
	// 노드의 부모를 탐색
	node1 = FindParent(node1);
	node2 = FindParent(node2);

	// 부모를 비교했을 때 부모가 큰 노드의 부모를 작은 노드의 부모 값에 넣는다.
	if (node1 < node2) 
		parent[node2] = node1;
	else 
		parent[node1] = node2;
}

int main() {

	// 간선 리스트 초기화
	list_e.push_back({ 2,1,2 });
	list_e.push_back({ 4,1,3 });
	list_e.push_back({ 10,3,4 });
	list_e.push_back({ 7,2,4 });
	list_e.push_back({ 5,2,3 });

	// 유니온 파인드 초기화
	for (int i = 1; i <= 4; i++)
		parent[i] = i;

	// 간선 리스트 가중치 기준 정렬
	sort(list_e.begin(), list_e.end());

	// 최소 신장 트리 구성
	// 사이클이 발생하면 트리에 포함하지 않음
	// 사이클이 발생하지 않으면 트리에 포함
	int sum = 0; // 가중치 합
	for (array<int, 3> edge : list_e) {
		if (FindParent(edge[1]) != FindParent(edge[2])) {
			sum += edge[0];
			UnionParent(edge[1], edge[2]);
		}
	}

	// 가중치 합 출력
	cout <<"가중치 합: " <<sum << "\n";

	return 0;
}
  • 출력
가중치 합: 13


참조

카테고리:

업데이트: