[자료구조] 프림알고리즘
업데이트:
개념
하나의 정점에 연결된 간선들 중에 가중치가 가장 작은 간선을 하나씩 선택하면서❓최소신장트리(MST)를 만들어 가는 알고리즘
정점 중심
-
정점보다 간선이 많을때 좋다.
- 알고리즘 순서
- 임의의 정점을 하나 선택해서 시작한다.
- 선택한 정점과 인접하는 정점들 중 최소 비용의 간선이 존재하는 정점을 선택한다.
- 배열, 우선순위큐를 사용해서 간선 비용의 최소값을 관리할 수 있다.
- 모든 정점이 선택될 때까지 1,2과정을 반복한다.
- 서로소인 2개의 집합으로 정보 유지
- 트리정점들: MST를 만들기 위해 선택된 정점들
- 비트리 정점들: 선택되지 않은 정점들
⇒ boolean형 visited[]배열로 관리 가능: true이면 선택 / false이면 비선택
최소신장트리(MST) 무향 가중치 그래프에서 신장 트리의 가중치 합이 최소인 신장트리
구현 코드
- 백준 1197번을 기준으로 작성함 정점 개수, 간선 개수, 간선 정보를 입력받아 그래프의 최소신장트리를 만드는 코드
간선의 최소 비용을 관리하기 위한 배열, 우선순위큐로 분류하였다.
그래프 구현은 이와 상관없이 인접행렬, 인접리스트, 간선리스트 중 하나로 사용해도 무관하다.
1. Dist[]
- 인접행렬로 구현 시 정점의 수가 매우 많으면 메모리초과 나는 경우가 많으므로 필요한 정점만을 저장하는 인접리스트를 자주 사용하자.
인접행렬 + 배열
package 프림알고리즘;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
//배열
public class PrimTest {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine().trim());
int[][] input = new int[N][N]; //인접행렬
int[] minEdge = new int[N]; //연결할 수 있는 정점중 가중치가 최소인 정점 계속 업데이트
boolean[] visited = new boolean[N]; //최소신장트리에 포함된 것1, 포함되지 않은 것0 처리
for(int i=0; i<N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for(int j=0; j<N; j++) {
input[i][j] = Integer.parseInt(st.nextToken());
}
minEdge[i] = Integer.MAX_VALUE; //초기에 최대 비용으로 셋팅
}//i에서 j노드까지의 비용을 모두 배열에 저장
int minVertex, min, result = 0; //최소 간선비용의 정점 기억, 최소 비용, 최소비용 누적(답)
//0번 정점을 시작정점으로 한다고 가정
minEdge[0] = 0; //시작점 최소간선비용은 0
// 정점 N개를 모두 돌려야하기 때문에 N번 반복, 모든 정점 돌기, 이미 방문한 정점으로 돌아가지x
for (int c = 0; c < N; c++) {
min = Integer.MAX_VALUE;
minVertex = 0;
//1. 신장트리에 포함되지 않은 정점 중 최소간선비용의 정점 찾기
//PQ에서는 이부분이 빠지고 PQ로 접목함
for (int i = 0; i < N; i++) { //정점만큼 모두 돌면서
if(!visited[i] && min > minEdge[i]) { //신장트리가 아니고 자신의 간선의 비용이 최소인 놈을 찾아서
min = minEdge[i]; //계속 갱신하고
minVertex = i; //그때의 정점번호 기억하고
}
}
result += min; //신장트리 비용 누적
visited[minVertex] = true; //신장트리에 포함 시킴
//2. 선택된 최소비용 정점 기준으로 신장트리에 포함되지 않은 다른 정점으로의 비용 계산하여 최소값 갱신
for(int i=0; i<N; i++) {
if(!visited[i] && input[minVertex][i] != 0 && minEdge[i] > input[minVertex][i]) {
minEdge[i] = input[minVertex][i];
//아직 신장트리에 포함되지 않은 정점, 선택된 정점에서 i로 인접
//현재 i의 최소비용보다 로직에서 들고있는 minVertext에서 i까지의 비용이 더 적다면
//업데이트한다.
}
}
System.out.println(result);
}
}
}
2. 우선순위큐
자료구조 ❓Heap은 일반적으로 우선순위 큐를 사용해서 구현한다.
Heap 완전이진트리 형태로 관리한다.
- 부모노드 < 자식노드: 최소힙
-
부모노드 > 자식노드: 최대힙 데이터를 추가, 삭제할 때마다 교환을 해서 위 성질을 계속 유지한다. 데이터가 N개 있어도 이진트리이기 때문에 위로 올라가면 반으로 준다. ⇒ 시간복잡도 O(logN) ⇒ 원소가 많을때 유리하다.
- 여기서는 위 코드의 1번 ⇒ 우선순위큐로 변경된다.
- 아래는 연결리스트+우선순위큐코드이다며 인접행렬을 대신 사용해도 된다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.StringTokenizer;
//프림알고리즘
public class Main {
static int V, E;
static int from, to;
static long weight;
static ArrayList<Node>[] graph; //간선 정보
static int Ans; //최소신장트리의 가중치
static StringTokenizer st;
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
st = new StringTokenizer(br.readLine());
V = Integer.parseInt(st.nextToken());
E = Integer.parseInt(st.nextToken());
graph = new ArrayList[V + 1]; //1번~V번
for(int i=1; i<V+1; i++) {
graph[i] = new ArrayList<>();
}
for(int i=0; i<E; i++) {
st = new StringTokenizer(br.readLine());
from = Integer.parseInt(st.nextToken());
to = Integer.parseInt(st.nextToken());
weight = Long.parseLong(st.nextToken());
graph[from].add(new Node(to, weight));
graph[to].add(new Node(from, weight));
}
makeMST();
System.out.println(Ans);
}
//최소 신장 트리
private static void makeMST() {
PriorityQueue<Node> pq = new PriorityQueue<>();
boolean[] visited = new boolean[V+1];
int now = 1; //1번 정점에서 출발
visited[1] = true; //방문 표시
//모든 정점 돌아야하니까
for(int i=1; i<V; i++) {
//현재 정점에 있는 간선 정보를 큐에다 넣어줌
for(Node node : graph[now]) {
if(!visited[node.num]) //아직 방문하지 않았다면
pq.offer(node);
}
//다음으로 갈 정점 찾기
//큐에 담긴 정점 중 가장 가중치가 작은 정점 고르기
while(!pq.isEmpty()) {
Node node = pq.poll();
if(!visited[node.num]) {
now = node.num;
visited[now] = true;
Ans += node.weight;
break;
}
}
}
}
static public class Node implements Comparable<Node>{
int num; //연결된 정점
long weight; //가중치
public Node(int num, long weight) {
super();
this.num = num;
this.weight = weight;
}
@Override
public int compareTo(Node o) {
// TODO Auto-generated method stub
return Long.compare(this.weight, o.weight);
}
}
}
- ArrayList
[] graph 배열이 헷갈려서 그림으로 정리함