본문 바로가기

알고리즘/Do it! 알고리즘 코딩 테스트

너비 우선 탐색 (BFS)

너비 우선 탐색 (BFS, breadth-first search)도 그래프를 완전 탐색하는 방법 중 하나로, 시작 노드에서 출발해 시작 노드를 기준으로 가까운 노드를 먼저 방문하면서 탐색하는 알고리즘이다.

 

기능 특징 시간 복잡도 (노드 수: V, 에지 수: E)
그래프 완전 탐색 - FIFO 탐색
- Queue 자료구조 이용
O(V+E)

 

너비 우선 탐색은 선입선출 방식으로 탐색하므로 를 이용해 구현한다. 또한 탐색 시작 노드와 가까운 노드를 우선하여 탐색하므로 목표 노드에 도착하는 경로가 여러 개일 때 최단 경로를 보장한다.

 

핵심 이론

1. BFS를 시작할 노드를 정한 후 사용할 자료구조 초기화하기

  • BFS도 DFS와 마찬가지로 방문했던 노드는 다시 방문하지 않으므로 방문한 노드를 체크하기 위한 배열이 필요하다.
  • DFS와 마찬가지로 그래프를 인접 리스트로 표현한다.
  • 탐색을 위해 스택이 아닌 큐를 이용한다.

 

시작 노드를 큐에 삽입하며 방문 배열 체크한 모습

 

 

2. 큐에서 노드를 꺼낸 후 꺼낸 노드의 인접 노드를 다시 큐에 삽입하기

  • 큐에서 노드를 꺼내면서 인접 노드를 큐에 삽입한다.
  • 이때 방문 배열을 체크하여 이미 방문한 노드는 큐에 삽입하지 않는다.
  • 또한 큐에서 꺼낸 노드는 탐색 순서에 기록한다.

 

1을 꺼내며 탐색 순서에 1을 기록, 인접 노드 3, 2를 큐에 삽입하며 방문 배열에 체크 (3과 2 아직 방문 X)

 

 

 

3. 큐 자료구조에 값이 없을 때까지 반복하기

  • 큐에 노드가 없을 때까지 앞선 과정을 반복한다.

 

 

 

 

 

 


 

[백준 1260번] DFS와 BFS

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

 

[정답]

import java.io.*;
import java.util.*;

public class Main {
    static boolean visited[];
    static ArrayList<Integer>[] A;
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();   // 노드 개수
        int m = sc.nextInt();   // 에지 개수
        int v = sc.nextInt();   // 시작점
        A = new ArrayList[n+1];
        for (int i = 1; i <= n; i++) {
            A[i] = new ArrayList<Integer>();
        }
        for (int i = 0; i < m; i++) {
            int s = sc.nextInt();
            int e = sc.nextInt();
            A[s].add(e);
            A[e].add(s);
        }
        // 번호가 작은 것을 먼저 방문하기 위해 정렬하기
        for (int i = 1; i <= n; i++) {
            Collections.sort(A[i]);
        }
        visited = new boolean[n+1]; // 방문 배열 초기화
        DFS(v);
        System.out.println();
        visited = new boolean[n+1]; // 방문 배열 초기화
        BFS(v);
        System.out.println();
    }
    static void DFS(int node) {
        System.out.print(node + " ");
        visited[node] = true;
        for (int a : A[node]) {
            if(!visited[a]) {
                DFS(a);
            }
        }
    }
    static void BFS(int node) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(node);
        visited[node] = true;

        while (!queue.isEmpty()) {
            int now = queue.poll();
            System.out.print(now + " ");
            for (int a : A[now]) {
                if(!visited[a]) {
                    queue.add(a);
                    visited[a] = true;
                }
            }
        }
    }
}

 

answer 배열을 n 크기로 만들어 놓고 대입한 후 출력했더니 예제 입력 3같은 경우 998번 0을 출력해 버린다. 이럴 때는 탐색 경로 배열을 만들어 값을 넣는 대신에 바로 바로 출력해야 한다. 

 


 

[백준 2178번] 미로 탐색

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

 

문제의 요구사항은 지나야 하는 칸 수의 최솟값을 찾는 것으로, 이는 완전 탐색을 진행하며 몇 번째 깊이에서 원하는 값을 찾을 수 있는지를 구하는 것과 동일하다. 따라서 BFS를 사용해 최초로 도달했을 때 깊이를 출력하면 문제를 해결할 수 있다. DFS보다 BFS가 적합한 이유는 BFS는 해당 깊이에서 갈 수 있는 노드 탐색을 마친 후 다음 깊이로 넘어가기 때문이다.

 

[정답]

import java.io.*;
import java.util.*;

public class Main {
    // 상하좌우를 탐색하기 위한 배열 선언하기
    static int[] dx = {-1, 0, 1, 0};
    static int[] dy = {0, 1, 0, -1};
    static boolean[][] visited;
    static int[][] map;
    static int n, m;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        map = new int[n+1][m+1];
        visited = new boolean[n+1][m+1];
        for (int i = 1; i <= n; i++) {
            st = new StringTokenizer(br.readLine());
            String line = st.nextToken();
            for (int j = 1; j <= m; j++) {
                map[i][j] = Integer.parseInt(line.substring(j-1, j));
            }
        }
        BFS(1, 1);
        System.out.println(map[n][m]);
    }
    static void BFS(int x, int y) {
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[] {x, y});
        visited[x][y] = true;
        while(!queue.isEmpty()) {
            int[] now = queue.poll();
            for (int i = 0; i < 4; i++) {
                int movedX = now[0] + dx[i];
                int movedY = now[1] + dy[i];
                if(movedX > 0 && movedX <= n && movedY > 0 && movedY <= m) {    // 좌표 유효성 검사
                    if(map[movedX][movedY] != 0 && !visited[movedX][movedY]) {  //갈 수 있는 칸 && 방문 검사
                        visited[movedX][movedY] = true;
                        map[movedX][movedY] = map[now[0]][now[1]] + 1;  // 깊이 업데이트하기
                        queue.offer(new int[] {movedX, movedY});
                    }
                }
            }
        }
    }
}

 

이 문제에서 핵심은 깊이를 업데이트하는 부분이다. 나는 answer 변수를 따로 두고 계속 더해가야하나 했는데 그럼 지나친 모든 경로가 다 더해지니 당연히 안된다. 맵 안의 값은 0과 1로 되어있는데, 지나갈 수 없는 곳은 0으로 둔 채 그 이전 값에 1을 더해가며 맵의 값을 계속 업데이트 해주면 원하는 목적지에 최소 경로 값이 저장되게 된다. 기억해두자.


 

[백준 1167번] 트리의 지름

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

 

풀이 과정은 다음과 같다.

 

1. 그래프를 인접 리스트로 저장한다. (노드, 가중치)를 표현하기 위해 노드는 클래스로 선언한다.

 

2. 임의의 노드에서 BFS를 수행하고 탐색할 때 각 노드의 거리를 배열에 저장한다. 예를 들어 노드 2에서 시작한다고 했을 때, 2는 4와 연결되어 있으므로 4를 방문하게 된다. 2와 4의 거리는 4이므로 distance[4]에는 4가 기록된다. 이어서 4는 2,3,5와 연결되어 있는데 2는 방문했으므로 3과 5를 방문한다. 4에서 3으로 향하는 에지 길이는 3이므로 distance[3]distance[4]+3을 기록한다. 그 결과 distance[3]에는 7이 기록된다. 이런 방식으로 노드를 방문하며 거리 배열을 업데이트 한다. 

 

3. 과정 2에서 얻은 배열에서 임의의 노드와 가장 먼 노드를 찾는다. 그런 다음 그 노드부터 BFS를 다시 수행한다. 이와 마찬가지로 탐색할 때 각 노드의 거리를 배열에 저장한다.

 

4. 과정 3에서 배열에 저장한 값 중 가장 큰 값을 이 트리의 지름으로 출력한다.

 

 

[정답]

import java.io.*;
import java.util.*;

public class Main {
    static ArrayList<Node>[] A;
    static int[] distance;
    static boolean[] visited;
    
    public static class Node {
        public int value;
        public int distance;
        public Node(int value, int distance) {
            this.value = value;
            this.distance = distance;
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int v = Integer.parseInt(br.readLine());    // 노드 개수
        A = new ArrayList[v+1];
        distance = new int[v+1];
        visited = new boolean[v+1];
        for (int i = 1; i <= v; i++) {
            A[i] = new ArrayList<Node>();
        }
        StringTokenizer st;
        for (int i = 0; i < v; i++) {   // A 인접 리스트에 그래프 데이터 저장
            st = new StringTokenizer(br.readLine());
            int node = Integer.parseInt(st.nextToken());
            while(true) {
                int E = Integer.parseInt(st.nextToken());
                if(E == -1) break;
                int V = Integer.parseInt(st.nextToken());
                A[node].add(new Node(E, V));
            }
        }
        
        BFS(1);
        int max = 1;
        for (int i = 2; i <= v; i++) {  // distance 배열에서 가장 큰 값으로 다시 시작점 설정
            if(distance[max] < distance[i]) max = i;
        }
        distance = new int[v+1];
        visited = new boolean[v+1];
        BFS(max);   // 새로운 시작점으로 실행
        Arrays.sort(distance);
        System.out.println(distance[v]);
    }
    
    static void BFS(int index) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(index);
        visited[index] = true;
        while(!queue.isEmpty()) {
            int now_node = queue.poll();
            for (Node node : A[now_node]) {
                if(!visited[node.value]) {
                    visited[node.value] = true;
                    distance[node.value] = distance[now_node] + node.distance;  // 거리 배열 업데이트
                    queue.add(node.value);
                }
            }
        }
    }
}

 

거리 배열이 업데이트 되는 모습을 잘 보자.

 

 

 

[참고 자료]

- 알고리즘 코딩 테스트 자바편

- https://velog.io/@e414/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%83%90%EC%83%89-%EB%84%88%EB%B9%84-%EC%9A%B0%EC%84%A0-%ED%83%90%EC%83%89

 

알고리즘 [탐색] - 너비 우선 탐색

너비 우선 탐색(BFS : Breadth-first search)는 그래프를 완전 탐색하는 방법 중 하나로, 시작 노드에서 출발해 시작 노드 기준으로 가까운 노드를 먼저 방문하면서 탐색하는 알고리즘이다.너비 우선 탐

velog.io

- https://velog.io/@e414/%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%83%90%EC%83%89-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84-%EA%B5%AC%ED%95%98%EA%B8%B0

'알고리즘 > Do it! 알고리즘 코딩 테스트' 카테고리의 다른 글

그리디  (0) 2024.05.28
이진 탐색 (Binary Search)  (0) 2024.05.25
깊이 우선 탐색(DFS)  (0) 2024.05.23
기수 정렬 (Radix sort)  (0) 2024.05.21
병합 정렬 (Merge sort)  (0) 2024.05.21