Java.APS/APS.Algorythm

[APS] MST : 프림(Prim)

개발자 아구몬 2023. 3. 30. 11:54

프림 알고리즘


Prim 알고리즘

- 하나의 정점에서 시작하여, 연결된 간선들 중 최소비용 만을 선택하면서 MST를 만드는 알고리즘

  • Kruskal이 간선을 선택하는 알고리즘이라면, Prim은 정점을 선택하는 알고리즘
  • 일종의 그리디 알고리즘 이며, MST의 일정한 형태를 담보하지 않음

Prim 특징

  • 정점을 기준으로 하므로, 정점이 적고 간선이 많은 밀집 그래프(dense graph)에서 유리
  • 시간 복잡도 : dist[] 배열을 이용하는 경우 : O(V^2), heap 구조를 이용하는 경우 : O(ElogV);


Prim : 가중치 배열  + 인접 행렬 구현


1) 초기화

  • 각 정점에 대하여, 정점의 방문 여부, 부모 정점, 가중치를 저장할 배열을 선언
  • 시작 정점을 선택하고 가중치를 0으로 초기화, 나머지는 매우 큰 값 INF로 초기화
boolean[] visited = new boolean[V]; // 방문 배열
int[] p = new int[V]; // 출발 정점 배열
int[] dist = new int[V]; // 가중치(거리) 배열
Arrays.fill(dist, INF); // 전체 가중치를 매우 큰값 INF로 초기화
dist[0] = 0; // 시작 정점(여기서는 0번 정점)은 0으로 초기화

 

2) 연결 노드 선택

  • 방문하지 않은 정점 중, 가장 가중치가 작은 정점을 선택하여 연결
int min = INF;
int idx = -1;
for(int j = 0; j<V; j++){
	if(!visited[j] && dist[j] < min) {
    // 방문한 적 없는 정점 중 최저값 갱신
		min = dist[j];
		idx = j;
	}
}
visited[idx] = true;
ans += dist[idx];

  

3) 가중치 갱신

  • 선택된 정점과 연결된 아직 방문하지 않은 정점들의 가중치 확인
  • 현재 정점에서 출발했을 떄의 가중치가 더 작다면 가중치와 출발정점값을 갱신
for(int j = 0; j<V; j++) {
	if(!visited[j] && adjArr[idx][j] != 0 && dist[j] > adjArr[idx][j]) {
	// 미방문 + 뽑은 정점이랑 연결되어있음 + 뽑은 정점이랑의 연결값이 지금까지 값보다 작음 
		dist[j] = adjArr[idx][j];
        // 연결값 갱신
		p[j] = idx;
        // 출발 정점값 갱신
	}
}

 

▼ 전체 코드 : 위의 2, 3 과정을 정점의 갯수만큼 반복함

더보기
import java.io.*;
import java.util.*;

public class Main {
	static final int INF = Integer.MAX_VALUE;
	public static void main(String[] args) throws Exception{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		int V = Integer.parseInt(st.nextToken());
		int E = Integer.parseInt(st.nextToken());
		
		int[][] adjArr = new int[V][V];
		
		for(int i = 0; i < E; i++) {
			st = new StringTokenizer(br.readLine());
			int A = Integer.parseInt(st.nextToken());
			int B = Integer.parseInt(st.nextToken());
			int C = Integer.parseInt(st.nextToken());
			
			adjArr[A][B] = C;
			adjArr[B][A] = C;
		}
		int ans = 0;
		boolean[] visited = new boolean[V];
		int[] p = new int[V]; //
		int[] dist = new int[V];
		
		Arrays.fill(dist, INF);
		dist[0] = 0; // 0번 정점으로 시자칼꼬얌
		for(int i = 0; i < V; i++) {
			//1. 내가 가장 작은 값을 뽑겠다.
			int min = INF;
			int idx = -1;
			// 아직 안뽑힌 친구들 중 가장 작은 값을 뽑겠다.
			for(int j = 0; j<V; j++) {
				if(!visited[j] && dist[j] < min) {
					min = dist[j];
					idx = j;
				}
			} 
			System.out.println(idx);
			visited[idx] = true;
			ans += dist[idx];
			//2. 뽑은 정점을 이용해서 갱신할 수 있는게 있다면 모조리 갱신
			for(int j = 0; j<V; j++) {
				if(!visited[j] && adjArr[idx][j] != 0 && dist[j] > adjArr[idx][j]) {
					dist[j] = adjArr[idx][j];
					p[j] = idx;
				}
			}
		}
		
		System.out.println(ans);
	}
}

 


구현 과정 (우선순위 큐 + 인접 리스트)

 

  • 최소 힙을 이용하여, 반복문을 돌지 않고 매번 최저 가중치를 갖는 노드 값을 얻을 수 있음에 착안

1. 간선 클래스 선언

  • Edge는 해당 간선의 도착점과 가중치를 담고 있는 객체 
  • 우선순위 큐 내부에서 비교해야 하므로, Comparable<Edge>를 구현해야 함
static class Edge implements Comparable<Edge>{
		int ed, w;

		public Edge(int ed, int w) {
			super();
			this.ed = ed;
			this.w = w;
		}

		@Override
		public int compareTo(Edge o) {
			return this.w - o.w;
			// return Integer.compare(this.w , o.w);
		}
	}

 

2. 초기화

  • 배열 대신 우선 순위 큐를 선언하고, 시작 정점을 선택하고 연결된 정점을 모두 넣어줌
PriorityQueue<Edge> pq = new PriorityQueue<>();
visited[0] = true; // 첫 정점 방문 처리
pq.addAll(adjList[0]); // 연결된 정점 넣기

// 아래의 방법으로도 가능
for(edge e : adjList[0]){
	pq.add(e);
}

 

3. 방문 처리

  • 방문한 정점의 갯수가 V개가 될 때 까지 반복
  • 우선순위 큐에서 나온 정점이 방문되지 않았다면 방문 처리
int pick = 1; // 확보한 정점의 갯수
int ans = 0;
		
while(pick < V) {
	Edge e = pq.poll();
	if(visited[e.ed]) continue;
			
	ans += e.w;
	pq.addAll(adjList[e.ed]);
	visited[e.ed] = true;
	pick++;
}

 

▼ 전체 코드

더보기
public class Prim {
	static final int INF = Integer.MAX_VALUE;
	
	static class Edge implements Comparable<Edge>{
		int ed, w;

		public Edge(int ed, int w) {
			super();
			this.ed = ed;
			this.w = w;
		}

		@Override
		public int compareTo(Edge o) {
			return this.w - o.w;
			// return Integer.compare(this.w , o.w);
		}
	}
	public static void main(String[] args) throws Exception{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		int V = Integer.parseInt(st.nextToken());
		int E = Integer.parseInt(st.nextToken());
		
		List<Edge>[] adjList = new ArrayList[V];
		
		for(int i = 0; i<V; i++) {
			adjList[i] = new ArrayList<>();
		}
		
		for(int i = 0; i < E; i++) {
			st = new StringTokenizer(br.readLine());
			int A = Integer.parseInt(st.nextToken());
			int B = Integer.parseInt(st.nextToken());
			int C = Integer.parseInt(st.nextToken());
			
			adjList[A].add(new Edge(B,C));
			adjList[B].add(new Edge(A,C));
		}
		
		boolean[] visited = new boolean[V];
		
		PriorityQueue<Edge> pq = new PriorityQueue<>();
		
		visited[0] = true;
		
		for(int i = 0; i<adjList[0].size(); i++) {
			pq.add(adjList[0].get(i));
		}
		
		// for(edge e : adjList[0]){
		//	pq.add(e);
		//}
		
		// pq.addAll(adjList[0]);
		
		int pick = 1; // 확보한 정점의 갯수
		int ans = 0;
		
		while(pick < V) {
			Edge e = pq.poll();
			if(visited[e.ed]) continue;
			
			ans += e.w;
			pq.addAll(adjList[e.ed]);
			visited[e.ed] = true;
			pick++;
		}
		
		System.out.println(ans);
	}
}