[알고리즘]너비 우선 탐색(BFS)

너비 우선 탐색 (Breadth-First Search, BFS)

image-20241031001807630

  1. 시작 정점을 에 넣고 방문 처리한다.
  2. 큐에서 정점을 꺼내어 해당 정점에 인접한 정점들을 모두 큐에 넣는다. (이미 방문한 정점은 제외)
  3. 큐가 비워질 때까지 2의 과정을 반복한다.

BFS의 특징

  • 최단 경로 탐색 : BFS는 너비 우선이므로 그래프에서 두 정점 간의 최단 경로를 찾을 때 유용하다.
  • 큐(Queue) 사용 : 방문할 정점의 순서를 큐로 관리한다.
  • 그래프의 모든 노드 탐색 : 연결 된 모든 정점을 방문할 때 사용한다.

BFS 구현 방법 1 : 인접리스트 사용

먼저 그래프를 인접 리스트로 표현한 후, BFS 적용

BFS로 최단 경로 찾기

  package Graph;
  
  import java.util.ArrayList;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Queue;
  
  public class BFSExample {
  
      // 인접 리스트로 표현된 그래프
      private static List<List<Integer>> graph = new ArrayList<List<Integer>>();
      private static boolean[] visited;
  
      // BFS 메서드
      public static void bfs(int startNode){
          Queue<Integer> queue = new LinkedList<Integer>(); // BFS에서 사용할 큐
          queue.add(startNode);
          visited[startNode] = true;
  
          while(!queue.isEmpty()){ // 큐가 빌 때까지 반복
              int currentNode = queue.poll(); // 큐에서 하나의 노드를 디큐함.
              System.out.println(currentNode + ""); // 방문한 노드 출력(확인)
  
              //현재 노드와 연결 된 모든 인접 노드를 확인한다.
              for(int neighbor : graph.get(currentNode)){
                  if(!visited[neighbor]){ // 아직 방문하지 않은 정점이라면
                      queue.offer(neighbor);  // 큐에 삽입하고
                      visited[neighbor] = true;   // 방문 처리한다.
                  }
              }
          }
      }
  
      public static void main(String[] args) {
          int numberOfNodes = 5; // 노드 개수 ( 0 ~ 4 )
          visited = new boolean[numberOfNodes]; // 노드 방문여부 저장
  
          // 그래프 초기화
          for(int i=0; i<numberOfNodes; i++){
              graph.add(new ArrayList<>()); // 각 노드마다 빈 리스트 추가
          }
  
          // 그래프의 간선 정보 추가 (무방향 그래프)
          graph.get(0).add(1); graph.get(0).add(2); // 노드 0 -> 노드 1, 2
          graph.get(1).add(0); graph.get(1).add(3); // 노드 1 -> 노드 0, 3
          graph.get(2).add(0); graph.get(2).add(3); // 노드 2 -> 노드 0, 3
          graph.get(3).add(1); graph.get(3).add(2); graph.get(3).add(4); // 노드 3 -> 노드 1, 2, 4
          graph.get(4).add(3); // 노드 4 -> 노드 3
  
          // bfs 수행
          bfs(0);
      }
  }

최단 경로 찾기 - 실제 응용

  package Graph;
  
  import java.util.*;
  
  public class ShortestPathBFS {
      private static List<List<Integer>> graph = new ArrayList<>();
      private static int[] distances; // 최단 경로를 저장하는 배열
  
      public static void bfs(int startNode){
          Queue<Integer> queue = new LinkedList<>();
          queue.add(startNode);
          distances[startNode] = 0;   // 시작점의 거리는 0
  
          while(!queue.isEmpty()){
              int currentNode = queue.poll(); // 디큐
  
              for(int neighbor : graph.get(currentNode)){
                  if(distances[neighbor] == -1){ // 아직 방문하지 않은 노드
                      distances[neighbor] = distances[currentNode] + 1;
                      queue.offer(neighbor);  // 인큐
                  }
              }
          }
      }
  
      public static void main(String[] args) {
          int numberOfNodes = 6;
          distances = new int[numberOfNodes];
          Arrays.fill(distances, -1);
  
          for(int i=0; i<numberOfNodes; i++){
              graph.add(new ArrayList<>());
          }
  
          // 그래프 간선 추가
          graph.get(0).add(1); graph.get(0).add(2);
          graph.get(1).add(0); graph.get(1).add(3);
          graph.get(2).add(0); graph.get(2).add(3);
          graph.get(3).add(1); graph.get(3).add(2); graph.get(3).add(4);
          graph.get(4).add(3); graph.get(4).add(5);
          graph.get(5).add(4);
  
          // bfs 실행
          bfs(0);
  
          // 각 노드로부터 최단 거리 출력
          for (int i=0; i<numberOfNodes; i++){
              System.out.println("Node "+i+" = "+distances[i]);
          }
      }
  }

2D미로문제

  • 미로는 2차원 배열로 주어지며, 시작 지점에서 출발하여 출구까지 도달하는 최단 경로를 구하는 것이 목표 최단 경로 = 너비우선탐색

    문제 2D 미로에서 (0,0)위치에 있는 사람이 출구인 (n-1 ,m-1) 위치로 이동하려고 합니다. 이동할 수 있는 방향은 상,하,좌,우 4가지 입니다. 이동할 수 있는 칸은 1로 표시되고 이동할 수 없는 벽은 0으로 표시됩니다. 사람이 출구까지 이동할 수 있는 최단 경로의 길이를 구하세요
    📍 1 ≤ N , M ≤ 100
    📍 시작 지점 (0,0)에서 이동할 수 있는 칸은 1칸이며, 출구도 마찬가지로 이동할 수 있는 칸은 1칸입니다.
    📍 dx, dy
    📍 x축 이동 → 행 이동 ( 움직임은으로는 상하로 움직여야함)
    📍 y축 이동 → 열 이동 ( 움직임으로는 좌우로 움직여야함)

    cf) 아래의 코드에서 미로의 갈래가 여러 갈래인 건 고려할 필요가 없음. Queue의 특성상 각각의 경로를 다 찾으면서 마지막 distance에 각각의 경로가 증가한 값을 넣게 되고 최종적으로 목적지 (n,m)에 도달하는 점이 있으면 그 점에서 바로 return 하기 때문. 빙 돌아오는 다른 경로의 경우 distance값만 증가하고 결국 (n,m) 에 먼저 도달하지 못하기 때문에 고려XXX !!!!

  package Graph;
  
  import java.util.LinkedList;
  import java.util.Queue;
  
  public class MazeSolver {
      // 방향을 나타내는 배열 (상,하,좌,우)
      private static final int[] dx = {-1, 1, 0, 0};  // x축 이동
      private static final int[] dy = {0, 0, -1, 1};  // y축 이동
  
      // bfs 메서드 : 미로의 최단경로 찾기
      public static int bfs(int[][] maze, int n, int m){
          // 방문 여부를 저장하는 배열
          boolean[][] visited = new boolean[n][m];
          // bfs 탐색을 위한 큐 : {x, y, 현재까지 이동한 거리}
          Queue<int[]> queue = new LinkedList<>();
  
          // 시작점 (0,0)에서 탐색 시작
          queue.offer(new int[]{0, 0, 1});
          visited[0][0] = true; // 시작 지점 방문 처리
  
          while(!queue.isEmpty()){
              int[] current = queue.poll();
              int x = current[0];
              int y = current[1];
              int distance = current[2];
  
              //출구에 도착하면 최단 경로 반환
              if (x== n-1 && y== m-1){
                  return distance;
              }
  
              //상,하,좌,우 네방향으로 탐색
              for(int i=0; i<4; i++){
                  int nx = x+dx[i];
                  int ny = y+dy[i];
  
                  //미로 범위를 벗어나지 않고, 이동할수 있는 칸이며, 방문하지 않았다면
                  if(nx >= 0 && ny >=0 && nx <n && ny < m && maze[nx][ny]==1 && !visited[nx][ny]){
                      queue.offer(new int[]{nx, ny, distance+1});
                      visited[nx][ny] = true; // 방문처리
                  }
              }
          }
          // 출구에 도착하지 못하면 -1 반환
          return -1;
      }
  
      public static void main(String[] args) {
          // N x M 크기의 미로 입력
          int[][] maze = {
                  {1, 0, 1, 1, 1},
                  {1, 0, 1, 0, 1},
                  {1, 1, 1, 1, 1},
                  {0, 0, 0, 0, 1},
                  {1, 1, 1, 1, 1}
          };
  
          int n = maze.length; // 미로의 세로 길이
          int m = maze[0].length; // 미로의 가로 길이
  
          // BFS 실행 및 결과 출력
          int result = bfs(maze, n, m);
          if (result != -1) {
              System.out.println("최단 경로의 길이: " + result);
          } else {
              System.out.println("출구까지의 경로가 없습니다.");
          }
      }
  }

이 외에 활용할 수 있는 문제 유형

섬의 개수 세기

0과 1 로 이루어진 2D 배열이 주어질 때, 1이 연속적으로 연결된 섬의 개수를 세는 문제.
섬은 상하좌우로만 연결 되어 있다.

🔍 Hint : 섬의 각 위치를 BFS로 탐색하여 방문한 위치를표시하고, 새로운 섬을 발견할 때마다 카운트를 증가시킨다.

바이러스 전파

2D 격자판이 주어질 때, 특정 위치에서 바이러스가 퍼지는 과정을 시뮬레이션하고
특정 시간이 지난 후에 감염 된 셀의 개수를 묻는 문제

🔍 Hint : BFS를 통해 감염된 셀에서 인접한 셀로 바이러스를 퍼뜨리며 시간 제한을 두고 진행한다.

미로탐색

주어진 미로에서 시작점에서 출발하여 목표지점까지 도달하는 최단 경로를 찾는 문제. 🔍 Hint : BFS를 사용하여 최단 경로를 탐색하고, 각 위치에서의 거리를 기록한다.

가장 먼 노드 찾기

주어진 그래프에서 가장 먼 노드를 찾는 문제.
노드와 간선이 주어질 때, 시작 노드에서 가장 먼 노드까지의 거리와 그 노드의 개수를 구합니다.

🔍Hint : BFS로 모든 노드를 탐색하여 각 노드까지의 거리를 기록하고, 가장 먼 거리를 찾아 수를 센다.

가장 짧은 경로

가중치가 있는 그래프에서 두 노드 간의 최단 경로를 찾는 문제
BFS와 dijkstra 알고리즘을 조합하여 해결

🔍 Hint : 가중치를 고려한 BFS를 통해 각 노드까지의 최단거리를 계산한다.

숨바꼭질

주어진 위치에서 시작하여 다른 위치로 이동하는 과정에서 숨바꼭질을 하는 문제 여러 가지 이동 방법이 주어지고, 몇 번의 이동으로 도착할 수 있는지를 묻는 문제
🔍 Hint : BFS로 모든 가능한 위치를 탐색하여 각 위치에서의 최소 이동 횟수를 계산한다.

퍼즐문제

숫자가 적힌 타일이 주어지고, 특정 규칙에 따라 타일을 이동시켜 목표 상태로 만드는 문제.
예를들어 3x3 퍼즐에서 공백을 이용하여 타일을 이동시키는 문제

🔍 Hint : BFS를 활용하여 가능한 모든 상태를 탐색하고, 목표 상태에 도달하는 최단 경로를 찾는다.

Leave a comment