카테고리 없음

[알고리즘] JAVA 자료구조(3) - BFS, DFS

lee_jy_1204 2024. 9. 19. 21:09
작성자 이재영
일 시 2024. 9. 19  (목) 18:00 ~ 21:00
장 소 복지관 b-128호
참가자 명단 임혜진, 이재영, 성창민, 김명원, 장원준
 사 진


더보기

목차

  1. DFS란?
  2. DFS 백준 풀이
  3. BFS란?
  4. BFS 백준 풀이

이번 주차에서는 지난 주차에서 공부한 Stack, Queue를 활용한 깊이 우선 탐색(DFS), 너비 우선 탐색(BFS)에 대해서 알아보고, 백준도 풀어보려고 한다.

1. DFS란?

깊이 우선 탐색(Depth-First Search, DFS)란 그래프 탐색 방법 중 하나이다. 루트 노드에서 시작하여 다음 분기로 넘어가기 전에 해당 분기를 완벽하게 탐색하는 방법이다. 즉, 정점에서 자식들을 우선으로 탐색하는 알고리즘이라고 생각하면 쉽다. 더 쉽게 예시를 들자면, 미로찾기를 할 때 최대한 한 방향으로 갈 수 있을 때까지 쭉 가다가 더 이상 갈 수 없게 되면 다시 가장 가까운 갈림길로 돌아와서 그 갈림길부터 다른 방향으로 탐색을 진행하는 방식을 생각하면 될 것이다. 

 

DFS의 특징은 다음과 같다.

  • 자기 자신을 호출하는 순환 알고리즘의 형태를 가지고 있다.
  • 전위 순회를 포함한 다른 형태의 트리 순회는 모두 DFS의 한 종류이다.
  • 어떤 노드를 방문했었는지 여부를 반드시 검사해야 한다.
  • 주로 Stack을 사용한다.

위 사진 속 트리를 DFS로 탐색하게 될 경우, 각 노드에 적혀있는 숫자대로 탐색을 하게 된다.(https://ko.wikipedia.org/wiki/%EA%B9%8A%EC%9D%B4_%EC%9A%B0%EC%84%A0_%ED%83%90%EC%83%89)

 

2. DFS 백준 풀이

import java.util.Scanner;

public class _2606 {
    // DFS 메서드를 사용하기 위해 전역으로 선언
    // visited는 방문 여부를 확인하는 배열
    static boolean[] visited;
    // list는 컴퓨터와 네트워크로 연결되어 있는 컴퓨터 쌍의 정보를 저장하는 2차원 배열
    static int[][] list;
    // n은 전체 컴퓨터의 수, t는 컴퓨터 쌍의 개수, count는 바이러스에 감염되는 컴퓨터의 수를 셀 변수
    static int n, t, count = 0;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        // 컴퓨터의 수 입력
        n = sc.nextInt();
        // 네트워크 연결 쌍의 개수 입력
        t = sc.nextInt();
        
        // 컴퓨터 번호는 1번부터 시작하므로 크기가 n+1인 배열을 선언
        visited = new boolean[n+1];
        list = new int[n+1][n+1];

        // 네트워크 연결 정보를 입력받음
        for (int i = 0; i < t; i++) {
            int node = sc.nextInt();   // 첫 번째 컴퓨터
            int node2 = sc.nextInt();  // 두 번째 컴퓨터
            // 양방향 연결이므로 두 컴퓨터 간의 연결을 각각 설정
            list[node][node2] = list[node2][node] = 1;
        }

        // 1번 컴퓨터부터 DFS 탐색 시작 (문제에서 1번 컴퓨터가 감염된다고 가정)
        DFS(1);
        
        // 감염된 컴퓨터 수 출력 (1번 컴퓨터는 제외하므로 count-1)
        System.out.println(count - 1);
    }

    // 깊이 우선 탐색(DFS)을 통해 네트워크를 탐색하는 메서드
    static void DFS(int x) {
        // 현재 컴퓨터를 방문 처리
        visited[x] = true;
        // 감염된 컴퓨터 수 증가
        count++;

        // 모든 컴퓨터에 대해 연결 여부를 확인
        for (int i = 0; i <= n; i++) {
            // 아직 방문하지 않았고, 현재 컴퓨터(x)와 연결된 컴퓨터(i)가 있다면 재귀 호출
            if (!visited[i] && list[x][i] == 1) {
                DFS(i);
            }
        }
    }
}

 

3. BFS란?

너비 우선 탐색(Breadth-First Search, BFS)란 그래프 탐색 방법 중 하나이다. 루트 노드에서 시작해서 인접한 노드를 먼저 탐색하는 방법이다. 즉, 정점에서 가까운 정점을 먼저 방문하는 방식으로 탐색하는 알고리즘이라고 생각하면 쉽다.

 

BFS의 특징은 다음과 같다.

  • DFS와 다르게 재귀적으로 동작하지 않는다.
  • 어떤 노드를 방문했었는지 여부를 반드시 검새해야 한다.
  • 직관적이지 않은 면이 있다.
  • 주로 Queue를 사용한다.

위 사진 속 트리를 BFS로 탐색하게 될 경우, 각 노드에 적혀있는 숫자대로 탐색을 하게 된다.(https://ko.wikipedia.org/wiki/%EB%84%88%EB%B9%84_%EC%9A%B0%EC%84%A0_%ED%83%90%EC%83%89)

4. BFS 백준 풀이

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

public class _2178 {
    // BFS를 함수로 만들 것이기 때문에 사용할 배열, 변수 등을 전역으로 선언한다.
    static int n, m;
    static int[][] list;
    static boolean[][] visited;
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        list = new int[n][m];
        visited = new boolean[n][m];

        // list에 값을 입력받을 때 문제에서 각각의 수들은 붙여서 주어진다고 했기 때문에
        // 문자열로 받아 그 문자열을 한글자 씩 떼어 리스트에 넣어준다.
        for(int i=0;i<n;i++){
            String line = sc.next();
            for(int j=0;j<m;j++){
                list[i][j] = line.charAt(j)-'0';
            }
        }

        // 0,0 부터 시작할 것이기 때문에 BFS 로직을 위해 true로 값을 설정해두고 시작한다.
        visited[0][0] = true;
        // 초기 좌표값 0, 0을 넣어주며 BFS 시작
        BFS(0,0);
        // BFS 함수가 끝나면 문제에서 구하려 했던 마지막 좌표값에 값은 초기 0,0 에서 걸리는 좌표값이 되어 있을 것이다.
        System.out.println(list[n-1][m-1]);
    }

    static void BFS(int x, int y){
        // Queue를 선언하고 초기값으로 x,y를 배열로 넣어준다. (0, 0) 좌표 형식
        Queue<int[]> queue = new LinkedList<>();
        queue.add(new int[]{x, y});

        // Queue가 값이 없을 때까지 반복
        while(!queue.isEmpty()){
            // 선입선출인 Queue 특징에 따라 front에 있는 좌표를 하나 빼온다.
            int[] cur = queue.poll();
            int x_ = cur[0];
            int y_ = cur[1];

            // 상,하,좌,우 좌표값을 하나 씩 넣어주기 위해 4번 반복해준다.
            for(int i=0;i<4;i++){
                // (-1,0), (1,0), (0,-1), (0,1)
                int nextX = x_ + dx[i];
                int nextY = y_ + dy[i];

                // 좌표값의 유효성을 검사한다. 상,하,좌,우에 좌표값이 주어진 미로에 벗어날 경우.
                if (nextX < 0 || nextY < 0 || nextX >= n || nextY >= m)
                    continue;
                // 주어진 좌표 또는 상,하,좌,우에 좌표값이 이미 방문한 곳인 경우
                if (visited[nextX][nextY] || list[nextX][nextY] == 0)
                    continue;

                // 유효성 검사에 통과하면 그 좌표를 queue에 넣어둔다.
                queue.add(new int[] {nextX, nextY});
                // 그리고 그 좌표값에 기존 좌표값 + 1 한 값을 넣어주는데
                // 좌표값까지 오는데 지나야 하는 칸수를 뜻한다.
                list[nextX][nextY] = list[x_][y_] + 1;
                // 방문한 좌표값을 표시해준다.
                visited[nextX][nextY] = true;
            }
        }
    }
}

Stack과 Queue에 대해서 개념만 할 때는 몰랐는데 실제 활용을 하니 생각보다 너무 어려웠다. Java 언어의 난이도가 많이 높아서 놀랐고, 기초를 좀 더 다져야 하겠다고 느꼈다.