20강 DFS & BFS 기초 문제 풀이

레테·2021년 10월 19일
1

✅참고 : DFS, BFS 문제유형 특징

Q. 음료수 얼려 먹기 (X)


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

🎁입력예시
4 5
00110
00011
11111
00000

🎊출력예시
3

전략

  • 이 문제는 DFS 혹은 BFS로 해결 할 수 있다. (모든 정점을 방문하기만 하면 되므로 둘 다 사용 가능) 얼음을 얼릴 수 있는 공간이 상, 하, 좌, 우로 연결되어있다고 표현 할 수 있으므로 그래프 형태로 모델링 가능하다. 방문처리 영역만 카운트하면 된다.
    ex.
    001
    010
    101

  • DFS를 활용한 알고리즘

    1. 특정 지점 주변 상, 하, 좌, 우를 살펴 본 뒤에 주변 지점 중에서 값이 0이면서 아직 방문하지 않은 지점이 있다면 해당 지점을 방문
    2. 방문한 지점에서 다시 상, 하, 좌, 우를 살펴보면서 방문을 진행하는 과정을 반복하면,
      (칸막이 안쪽의) 연결된 모든 지점을 방문 가능
    3. 모든 노드에 대하여 1~2번의 과정을 반복하며, 방문하지 않은 지점의 수를 카운트

정답

import java.util.*;

public class Main {

    public static int n, m;
    // n * m 최대크기로 배열 생성해둔다.
    public static int[][] graph = new int[1000][1000];

    // DFS로 특정 노드를 방문하고 연결된 모든 노드들도 방문
    public static boolean dfs(int x, int y) {
        // 주어진 범위를 벗어나는 경우에는 즉시 종료
        if (x <= -1 || x >=n || y <= -1 || y >= m) {
            return false;
        }
        // 현재 노드를 아직 방문하지 않았다면
        if (graph[x][y] == 0) {
            // 해당 노드 방문 처리
            graph[x][y] = 1;
            // 상, 하, 좌, 우의 위치들도 모두 재귀적으로 호출 -> 해당 연속 영역 내에서 연결된 지점들이 연속적으로 방문처리된다.
            // 상, 하, 좌, 우 호출에 대해서는 리턴값을 사용하지 않으므로 단순히 영역 내 연결된 모든 노드들에 대해 방문처리만 수행
            dfs(x - 1, y);
            dfs(x, y - 1);
            dfs(x + 1, y);
            dfs(x, y + 1);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // N, M을 공백을 기준으로 구분하여 입력 받기
        n = sc.nextInt();
        m = sc.nextInt();
        sc.nextLine(); // 버퍼 지우기

        // 2차원 리스트의 맵 정보 입력 받기
        for (int i = 0; i < n; i++) {
            String str = sc.nextLine();
            for (int j = 0; j < m; j++) {
                graph[i][j] = str.charAt(j) - '0';
            }
        }

        // 모든 노드(위치)에 대하여 DFS호출하여 음료수 채우기
        // -> 막힌 칸막이 때문에 모든 위치에 대하여 for문을 돌려야함 
        int result = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 현재 위치에서 DFS 수행
            	// -> 각 연속 영역의 첫 시작 지점만 result에 카운트된다. 
                if (dfs(i, j)) {
                    result += 1;
                }
            }
        }
        System.out.println(result); // 정답 출력 
    }

}



Q. 미로탈출 (X)


동빈이는 N * M 크기의 직사각형 형태의 미로에 갇혔습니다. 미로에는 여러 마리의 괴물이 있어 이를 피해 탈출해야 합니다.
동빈이의 위치는 (1, 1) 이며 미로의 출구는 (N, M)의 위치에 존재하며 한 번에 한 칸씩 이동할 수 있습니다. 이때 괴물이 있는 부분은 0으로 괴물이 없는 부분은 1로 표시되어 있습니다. 미로는 반드시 탈출할 수 있는 형태로 제시됩니다.
이때 동빈이가 탈출하기 위해 움직여야 하는 최소 칸의 개수를 구하세요. 칸을 셀 때는 시작 칸과 마지막 칸을 모두 포함해서 계산합니다.

🎁입력조건
첫째 줄에 두 정수 N, M(4<=N, M<=200)이 주어집니다. 다음 N개의 줄에는 각각 M개의 정수(0 혹은 1)로 미로의 정보가 주어집니다. 각각의 수들은 공백 없이 붙여서 입력으로 제시됩니다. 또한 시작 칸과 마지막 칸은 항상 1입니다.

🎊출력조건
첫째 줄에 최소 이동 칸의 개수를 출력합니다.

🎁입력예시
5 6
101010
111111
000001
111111
111111

🎊출력예시
10

전략

  • BFS는 시작 지점에서 가까운 노드부터 차례대로 그래프의 모든 노드를 탐색합니다.
    -> 따라서 최단 경로 탐색에 활용 가능
  • 상, 하, 좌, 우로 연결된 모든 노드로의 거리가 1로 동일합니다.
    따라서(1, 1) 지점부터 BFS를 수행하여 모든 노드의 최단 거리 값을 기혹하면 해결할 수 있습니다.
  • 예시로 다음과 같이 3*3 크기의 미로가 있다고 가정합시다
    1 1 0
    0 1 0
    0 1 1
    [Step 1] 처음에는 (1, 1)의 위치에서 시작[Step 2] (1, 1) 좌표에서 상 하 좌 우 로 탐색을 진행하면 바로 옆 노드인 (1, 2) 위치의 노드를 방문하게 되고 새롭게 방문하는 (1,2)노드의 값을 2로 바꾸게 됩니다.[Step 3] 마찬가지로 BFS를 계속 수행하면 결과적으로 다음과 같이 최단 경로의 값들이 1씩 증가하는 형태로 변경

[참고]

  • (1, 2)에서 상하좌우 확인 시, 왼쪽의 시작 노드인 (1,1) 또한 1이므로 조건에 걸려서 3으로 값이 변경되면서 방문처리 되지만, 마지막 (n, m)의 값만 확인하면 되기 때문에 다른 노드들은 값이 변경되어도 상관없음
  • 만약 특정 노드 상하좌우에 1 값을 가진 두 노드가 존재한다고 해도 두 노드 모두 똑같은 값으로 변경 될 것이다. 하지만 마찬가지로 마지막 (n, m)의 값만 확인하면 되기 때문에 다른 노드들은 값이 변경되어도 상관없다.

정답

import java.util.*;

class Node {

    private int x;
    private int y;

    public Node(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return this.x;
    }
    
    public int getY() {
        return this.y;
    }
}

public class Main {

    public static int n, m;
    // n * m 최대크기로 배열 생성 해 둔다.
    // 문제에서는 (1, 1)부터로 제시되어있지만 실제로 (0, 0)부터 데이터를 넣어도 상관없으므로 201 * 201일 필요 없다.
    public static int[][] graph = new int[200][200];

    // 이동할 네 가지 방향 정의 (상, 하, 좌, 우) 
    public static int dx[] = {-1, 1, 0, 0};
    public static int dy[] = {0, 0, -1, 1};

    public static int bfs(int x, int y) {
        // 큐(Queue) 구현을 위해 queue 라이브러리 사용 
    	// Node클래스 대신 int[] 사용해도 될 듯
        Queue<Node> q = new LinkedList<>();
        q.offer(new Node(x, y));
        // 큐가 빌 때까지 반복하기 
        while(!q.isEmpty()) {
            Node node = q.poll();
            x = node.getX();
            y = node.getY();
            // 현재 위치에서 4가지 방향으로의 위치 확인
            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i];
                int ny = y + dy[i];
                // 미로 찾기 공간을 벗어난 경우 무시
                if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
                // 벽인 경우 무시
                if (graph[nx][ny] == 0) continue;
                // 해당 노드를 처음 방문하는 경우에만(첫 시작지점은 예외) 최단 거리 기록 
                if (graph[nx][ny] == 1) {
                    graph[nx][ny] = graph[x][y] + 1;
                    q.offer(new Node(nx, ny));
                } 
            } 
        }
        // 가장 오른쪽 아래까지의 최단 거리 반환
        return graph[n - 1][m - 1];
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // N, M을 공백을 기준으로 구분하여 입력 받기
        n = sc.nextInt();
        m = sc.nextInt();
        sc.nextLine(); // 버퍼 지우기

        // 2차원 리스트의 맵 정보 입력 받기
        for (int i = 0; i < n; i++) {
            String str = sc.nextLine();
            for (int j = 0; j < m; j++) {
                graph[i][j] = str.charAt(j) - '0';
            }
        }

        // BFS를 수행한 결과 출력
        System.out.println(bfs(0, 0));
    }

}



0개의 댓글

관련 채용 정보