Algorithm/유형별 문제 풀기

[DFS/BFS] 문제이름 : 음료수 얼려 먹기

sw_develop 2021. 7. 5. 11:59

문제 설명

N X M 크기의 얼음 틀이 있다. 구멍이 뚫린 부분은 0, 칸막이가 존재하는 부분은 1로 표시된다. 구멍이 뚫려 있는 부분끼리 상 하 좌 우로 붙어 있는 경우 서로 연결되어 있는 것으로 간주한다. 이때 얼음 틀의 모양이 주어졌을 때 생성되는 총 아이스크림의 개수를 구하는 프로그램을 작성하시오. 

 

1. 사용 알고리즘 

그래프 탐색 - BFS & DFS 

 

2. 문제 해결 아이디어 

주어진 좌표의 위치와 해당 위치의 상하좌우에 대해 구멍이 뚫려서 연결되어 있는지 탐색하면 된다. 이때 주어진 2차원 배열을 그래프 형태로 생각하면, DFS와 BFS 알고리즘을 적용해 해결할 수 있다.  

 

3. 코드

구현방식1) BFS(너비 우선 탐색)를 사용해 가까운 좌표부터 확인하기

각 좌표가 상하좌우에 대해 움직일 수 있는 좌표의 범위를 dx[], dy[] 리스트로 미리 선언해둔다. 

main 메서드 내에서 이중for문으로 주어진 좌표의 모든 위치에 대해 hole(구멍이 뚫린 부분)인 경우 BFS를 실행한다. 

bfs 함수 내에서

주어진 좌표의 row와 col을 각각 큐에 저장(enqueue) -> row와 col 값 꺼내기(dequeue) -> 주어진 좌표의 상하좌우에 대해        hole(구멍이 뚫린 경우)인 경우 큐에 저장(enqueue) -> 방문했음을 체크하기 위해(main에서 해당 좌표에 대해 bfs() 호출하지 않도록 하기 위해) visited로 설정

import java.util.LinkedList;
import java.util.Queue;

import java.util.Scanner;

public class Ex1 {
	public static int mapRowSize, mapColSize;
	
	public static int dx[] = {-1, 1, 0, 0}; //행의 움직임 (상하좌우)
	public static int dy[] = {0, 0, -1, 1}; //열의 움직임 (상하좌우) 
	
	public static int hole = 0, wall = 1, visited = 2;
	
	public static void bfs(int iceMap[][], int startVertexRow, int startVertexCol) { 
		Queue<Integer> rowQueue = new LinkedList<Integer>();
		Queue<Integer> colQueue = new LinkedList<Integer>();
		
		rowQueue.offer(startVertexRow); //enqueue
		colQueue.offer(startVertexCol);
		iceMap[startVertexRow][startVertexCol] = visited; 
		
		while(!rowQueue.isEmpty()) {
			startVertexRow = rowQueue.poll(); //dequeue 
			startVertexCol = colQueue.poll();
			
			for(int i = 0; i < dx.length; i++) {
				int x = startVertexRow + dx[i]; //row -> 헷갈린 부분 : x가 좌표에서의 x값이 아니라 행임! 
				int y = startVertexCol + dy[i]; //col -> y는 열임! 
				
				if(x != -1 && x != mapRowSize && y != -1 && y != mapColSize) { //x좌표 -> col, y좌표 -> row 
					if(iceMap[x][y] == hole) {
						rowQueue.offer(x);
						colQueue.offer(y); 
						iceMap[x][y] = visited; 
					}
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Scanner kbd = new Scanner(System.in);
		
		mapRowSize = kbd.nextInt();
		mapColSize = kbd.nextInt();
		kbd.nextLine(); //버퍼 지우기 
		
		int iceMap[][] = new int[mapRowSize][mapColSize]; //그래프의 경우 -> 인접행렬이라고 생각하기 
		
		/* 2차원 리스트의 맵 정보 입력 받기 */
		for(int i = 0; i < mapRowSize; i++) {
			String str = kbd.nextLine(); //문자열로 입력받기 
			for(int j = 0; j < mapColSize; j++) {
				iceMap[i][j] = str.charAt(j) - '0'; //Char to Int 
			}
		}
			
		
		int totalIceCream = 0; 
		
		for(int i = 0; i < mapRowSize; i++)
			for(int j = 0; j < mapColSize; j++) {
				if(iceMap[i][j] == hole) {
					bfs(iceMap, i, j);
					totalIceCream++; 
				}
			}
		
		System.out.println(totalIceCream);
	}

}

구현방식2) DFS(깊이 우선 탐색)를 사용해 연결된 좌표에 대해 최대한 깊게 들어가 확인하기 

main 메서드 내에서 이중for문으로 모든 좌표 위치에 대해 DFS를 수행한다. 

dfs 함수 내에서 좌표 값이 hole(구멍이 뚫린 경우)인 경우 -> 해당 위치 방문 처리 -> 상하좌우에 대해 재귀함수 호출  

import java.util.Scanner;

public class Main {

	public static int mapRowSize, mapColSize;
	
	public static int hole = 0, wall = 1, visited = 2;
	
	public static boolean dfs(int iceMap[][], int vertexRow, int vertexCol) {
		//Map의 좌표 범위 체크 
		if(vertexRow <= -1 || vertexRow >= mapRowSize || vertexCol <= -1 || vertexCol >= mapColSize)
			return false;
		
		if(iceMap[vertexRow][vertexCol] == hole) {
			//해당 노드 방문 처리
			iceMap[vertexRow][vertexCol] = visited;
			//상하좌우 좌표에 대해 재귀적 호출 
			dfs(iceMap, vertexRow-1, vertexCol);
			dfs(iceMap, vertexRow+1, vertexCol);
			dfs(iceMap, vertexRow, vertexCol-1);
			dfs(iceMap, vertexRow, vertexCol+1);
			return true; 
		}
		
		return false; 
	}
	
	public static void main(String[] args) {
		Scanner kbd = new Scanner(System.in);
		
		mapRowSize = kbd.nextInt();
		mapColSize = kbd.nextInt();
		kbd.nextLine(); //버퍼 지우기 
		
		int iceMap[][] = new int[mapRowSize][mapColSize]; //그래프의 경우 -> 인접행렬이라고 생각하기 
		
		/* 2차원 리스트의 맵 정보 입력 받기 */
		for(int i = 0; i < mapRowSize; i++) {
			String str = kbd.nextLine(); //문자열로 입력받기 
			for(int j = 0; j < mapColSize; j++) {
				iceMap[i][j] = str.charAt(j) - '0'; //Char to Int 
			}
		}
			
		int totalIceCream = 0; 
		
		for(int i = 0; i < mapRowSize; i++)
			for(int j = 0; j < mapColSize; j++) {
				//현재 위치에서 DFS 수행
				if(dfs(iceMap, i, j))
					 totalIceCream += 1;
			}
		
		System.out.println(totalIceCream);

	}
}

 

개선할 부분

Q. Queue에 원소 값 2개를 저장할 수 없나? BFS 구현 시 row와 col 각각에 대해 2개의 큐 선언해서 저장했는데,  한 번에 저장할 방법을 찾아보자!

방식1) Pair 역할을 담당할 새로운 클래스 생성하기

주어진 좌표에 대해 Pair 클래스 객체 생성 -> 해당 객체를 Queue에 넣어주기 

//나머지 부분은 위에서 작성한 코드와 동일함 
//추가하고(Pair 클래스) 변경한(bfs함수) 코드 부분만 첨부함 

class Pair{
	public int row;
	public int col;
	
	Pair(int row, int col){
		this.row = row;
		this.col = col;
	}
}

public static void bfs(int iceMap[][], int startVertexRow, int startVertexCol) { 
		Queue<Pair> queue = new LinkedList<Pair>();
		
		Pair pair = new Pair(startVertexRow, startVertexCol);
		
		queue.offer(pair);
		iceMap[startVertexRow][startVertexCol] = visited; 
		
		while(!queue.isEmpty()) {
			Pair dequeueVal = queue.poll();
			startVertexRow = dequeueVal.row; 
			startVertexCol = dequeueVal.col;
			
			for(int i = 0; i < dx.length; i++) {
				int x = startVertexRow + dx[i]; //row -> 헷갈린 부분 : x가 좌표에서의 x값이 아니라 행임! 
				int y = startVertexCol + dy[i]; //col -> y는 열임! 
				
				if(x != -1 && x != mapRowSize && y != -1 && y != mapColSize) { //x좌표 -> col, y좌표 -> row 
					if(iceMap[x][y] == hole) {
						pair = new Pair(x, y);
						queue.offer(pair);
						iceMap[x][y] = visited; 
					}
				}
			}
		}
	}