알고리즘/백준

[백준] 16947 서울 지하철 2호선 - JAVA

체제 2023. 6. 23. 21:44

https://www.acmicpc.net/problem/16947

 

16947번: 서울 지하철 2호선

첫째 줄에 역의 개수 N(3 ≤ N ≤ 3,000)이 주어진다. 둘째 줄부터 N개의 줄에는 역과 역을 연결하는 구간의 정보가 주어진다. 같은 구간이 여러 번 주어지는 경우는 없고, 역은 1번부터 N번까지 번호

www.acmicpc.net

 

 

알고리즘

  • DFS + BFS
  • 순환선에 포함되는 역을 찾는다. DFS
  • (무방향)그래프 내에서 순환을 찾기 위한 방법 : DFS/ Union-find 
    💡처음에 유니온 파인드로 시도했으나, 사이클이 있다는 것은 확인되지만 사이클에 포함되는 노드가 제대로 찾아지지 않아서 DFS로 ! 
  • 순환선에 포함되지 않은 역은 순환선까지의 최소값을 구하기 위해 BFS로 접근 

 

🌟 그래프 내에서 순환 찾기 

  • isCycle[] : 사이클에 포함된 역인지 확인하기 위한 boolean 형 배열 

 

순환 조건 

1. 시작점 == 도착점 

2. 시작점과 도착점 사이에 2개 이상의 역이 존재해야 한다. (ex) 1-3-1 : 순환 아님) 

 

2번 조건을 판별하기 위해 현재 탐색 중인 node 에 대해 직전 노드(prev)와 다음노드(next)가 같은지 확인하였다. 만약 prev==next  라면 1-3-1 과 같은 경우이므로 순환이 아니다. 

 

  • 현재 노드에 연결되어 있는 다음 노드가 사이클에 포함되어 있지 않다면, DFS 재귀 계속해서 진행 
  • 다음 노드가 사이클에 포함되어 있다면, prev != next (1-3-1 방지) && next == start 일 경우에만 순환 O 

 

만약 순환이 아닐 경우, 다음 시작노드로 부터의 탐색을 위해 isCycle 값을 다시 false 로 바꾼 뒤 false 반환 

static boolean dfs(int prev, int node, int start) {
		isCycle[node] = true; 
		
		for(int next: list.get(node)) {
			if(!isCycle[next]) {
				if(dfs(node, next, start)) return true; 
			}
			else {
				//이미 방문했던 곳인데, 시작점을 만났을 때, 바로 이전 정점이 아닐때 
				//next==prev 이면, 1-3-1 과 같이 바로 돌아온 경우 (순환 x ) 
				if(next != prev && next ==start) return true; 
			}
		}
		isCycle[node]=false; //사이클 못났으면 다시 false 로 바꿔주기 
		return false; 
	}

 


구현 과정

  • list : 역 간 연결 정보를 그래프로 구현하기 위한 인접 리스트 
  • isCycle : 해당 역이 순환선에 포함되어 있다면 true 
  • dfs : 순환선을 찾기 위한 dfs 탐색 
  • bfs : 순환선 까지의 최단거리를 찾기 위한 bfs 탐색 

 

1. 역 간 연결 정보를 입력받아 list 에 저장한다. 

 

2. DFS 탐색을 통해 순환선을 찾는다. 

 

3. 순환선에 포함된 역이라면 0을 출력하고, 그렇지 않다면 BFS 탐색을 통해 순환선까지의 최단 거리를  구한다. 

 


소스 코드

import java.util.*;
import java.io.*;
	
public class Main {
	static ArrayList<ArrayList<Integer>> list = new ArrayList<>(); 
	static int N; 
	static boolean isCycle[]; 
	
	public static void main(String[] args) throws Exception{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
		StringTokenizer st;
		
		N= Integer.parseInt(br.readLine());//역의 갯수 		
		for(int i=0; i<=N; i++) {
			list.add(new ArrayList<Integer>()); 
		}
		
		for(int i=0; i<N; i++) {
			st= new StringTokenizer(br.readLine(), " ");
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken()); 

			list.get(a).add(b);
			list.get(b).add(a);
		}
		
		isCycle = new boolean[N+1]; 
		for(int i= 1; i<=N; i++) {
			//1~N 역을 출발점으로 하여 사이클이 만들어 지는지 확인 
			if(dfs(i,i,i)) break; //사이클 만나면 종료 
		}


		StringBuilder sb= new StringBuilder(); 
		for(int i=1; i<=N; i++) {
			//순환선에 포함되어 있는 역이라면 0 출력
			if(isCycle[i]) sb.append(0); 
			else sb.append(bfs(i)); 
			sb.append(" "); 
		
		}
		
		System.out.println(sb);
		
	}//end of main 
	
	static boolean dfs(int prev, int node, int start) {
		isCycle[node] = true; 
		
		for(int next: list.get(node)) {
			if(!isCycle[next]) {
				if(dfs(node, next, start)) return true; 
			}
			else {
				//이미 방문했던 곳인데, 시작점을 만났을 때, 바로 이전 정점이 아닐때 
				//next==prev 이면, 1-3-1 과 같이 바로 돌아온 경우 (순환 x ) 
				if(next != prev && next ==start) return true; 
			}
		}
		isCycle[node]=false; //사이클 못났으면 다시 false 로 바꿔주기 
		return false; 
	}
	
	static class Node implements Comparable<Node>{
		int node; 
		int dis; 
		public Node(int node, int dis) {
			this.node= node;
			this.dis= dis; 
		}
		@Override
		public int compareTo(Node o) {
			return this.dis -o.dis; 
		}
		
	}
	//노드에서 순환까지의 최소 거리 찾기 
	static int bfs(int node) {
		boolean visited[] = new boolean[N+1]; 
		PriorityQueue<Node> que =new PriorityQueue<>(); 
		que.add(new Node(node, 0)); 
		visited[node] = true; 
		
		while(!que.isEmpty()) {
			Node now_node = que.poll();
			int now= now_node.node;
			int dis= now_node.dis;
			
			//순환 만남 
			if(isCycle[now]) return dis; 
			
			for(int i=0; i<list.get(now).size(); i++) {
				int next= list.get(now).get(i); 
				
				if(visited[next]) continue; 
				
				visited[next] = true; 
				que.add(new Node(next, dis+1)); 
			}
		}
		return -1; 
	}
}//end of class