본문 바로가기
알고리즘/백준

1260번 - DFS와 BFS

by 배우고 성장하는 청년 블로그 2021. 4. 9.

출처: 백준 알고리즘

주소: www.acmicpc.net/problem/1260

 

1260번: DFS와 BFS

첫째 줄에 정점의 개수 N(1 ≤ N ≤ 1,000), 간선의 개수 M(1 ≤ M ≤ 10,000), 탐색을 시작할 정점의 번호 V가 주어진다. 다음 M개의 줄에는 간선이 연결하는 두 정점의 번호가 주어진다. 어떤 두 정점 사

www.acmicpc.net

1260번 - DFS와 BFS

문제 요약:

 (1) 입력으로 정점의 개수(N), 간선의 개수(M), 탐색을 시작할 정점의 번호(V)를 제공

     => (각 입력의 범위는 N: 1 ~ 1000, M: 1 ~ 10000, V: 1 ~ N 이다.)

 (2) 이후 M개의 간선을 입력으로 제공한다.

 (3) 입력으로 제공된 정보를 이용하여 DFS을 수행한 결과 출력

 (4) 또한, BFS를 수행한 결과 출력

   => (3)과 (4)을 수행할 때, 방문할 수 있는 정점이 여러 개인 경우에는 정점 번호가 작은 순으로 방문

 

문제 해결:

 A. 정점이라는 것을 Vertex 구조체로 정의

 B. DFS와 BFS의 클래스 정의

 C. 각 클래스에 입력 함수 및 수행 함수 정의

 

미리 공부할 내용:

 

 (1) Depth First Search(DFS): 깊이 우선 탐색

- 그래프나 트리를 탐색을 할 때, 사전에 정의된 깊이를 우선적으로 탐색을 하는 알고리즘을 말한다.

- 간단하고 이해하기 쉬운 트리를 통해 이해해보자.

트리 예시 사진

 - 1에서 시작해서 8을 찾는 예시

   => (가정: 자식 노드로 내려온 경우는 작은 수 먼저 탐색을 한다)

   => 먼저, 1에서 시작한다.

1에서 시작

   => 1을 탐색 후, 8이 아니여서 1에 방문표시를 해놓고, 갈 곳을 정해 놓는다.

   => 그 중에서 제일 작은 값부터 방문한다.

2와 5 방문

   => 2와 5를 같은 방식으로 방문한다.

   => 그 후 3과 4 등을 방문한다.

3, 4, 6, 7, 8 방문

   => 차례로 방문을 하고 마지막에 8을 찾게 된다.

 

 - DFS의 장점

   => 만약 찾고자 하는 값이 깊은 곳에 존재하는 경우 해를 빨리 구할 수 있다.

 

 - DFS의 단점

   => 해가 없는 경로의 끝까지 가는 경우가 발생한다.

   => 해를 탐색한 순서가 최단 경로라는 보장이 없다.

      (위 예시를 보더라도 1 -> 4 -> 6 -> 8이 제일 빠른 방법인데, 다른 곳도 끝까지 탐색을 하다보니 오래 걸리게 된다.)

 

 

 (2) Breadth First Search(BFS): 너비 우선 탐색

- 그래프나 트리를 탐색을 할 때, 사전에 정의된 너비를 우선적으로 탐색을 하는 알고리즘을 말한다.

- 간단하고 이해하기 쉬운 트리를 통해 이해해보자.

 

- 1에서 시작해서 8을 찾는 예시

   => (가정: 자식 노드로 내려온 경우는 작은 수 먼저 탐색을 한다)

   => 먼저, 1에서 시작한다.

1 탐색

   => 1을 탐색 후, 8이 아니여서 1에 방문표시를 해놓고, 갈 곳을 정해 놓는다.

   => 그 중에서 작은 값부터 모든 자식 노드를 방문한다.

2, 3, 4 방문

   => 모든 자식 노드를 방문하면서 자식 노드의 자식 노드의 값을 저장해놓는다.

   => 이후, 저장해 놓은 값에서 작은 값부터 모두 방문한다.

5, 6 방문

   => 5와 6을 방문하면서 자식 노드의 값을 저장해놓는다.

   => 이후, 저장해 놓은 값에서 작은 값부터 모두 방문한다.

7, 8 방문

   => 7와 8을 방문해서 찾고자하는 값인 8을 찾았다.

 

 - BFS의 장점

   => 찾은 경로가 시작 노드에서 끝 노드까지의 최단 경로를 보장한다.

   (1 -> 4 -> 6 -> 8 이로 인해, 최단 경로을 얻을 수 있게 되었다.)

 

 - BFS의 단점

   => 찾고자 하는 값이 깊은 곳에 위치하게 되면 모든 너비를 탐색하기 때문에 오래 걸리게 된다.

   => 또한, 가고자 하는 값을 저장해야하므로 기억공간이 DFS보다 많이 필요하게 된다.

 

 

알고리즘:

 

 A. 정점이라는 것을 Vertex 구조체로 정의

  => 정점을 나타내는 구조체를 선언한다.

  => 구조체에는 해당 정점을 방문(visit)여부를 판단하는 변수를 저장

  => 해당 정점의 이웃(neighbor)을 vector를 통해 저장하였다.

typedef struct Vertex {
    bool visit;
    std::vector<int> neighbor;
};
cs

 

 B. DFS와 BFS의 클래스 정의

 

- DFS 클래스

  - 변수

     => Vertex 타입 배열 v를 설정

  - 함수

     => 값을 배열 v에 입력하는 함수(InputNei)

       -> 반환값 없음, 입력 값(int insert_idx, int insert_val)

       -> 배열 v에 입력할 값(insert_val)를 해당 위치(insert_idx)에 대입

     => 값을 찾는 함수(Search)

       -> 반환값 없음, 입력값(int start_idx)

       -> 시작 위치(start_idx)에서 탐색을 하여 끝날 때까지의 값을 출력

// DFS (Depth First Search)
class DFS {
private:
    Vertex v[MAX_N_LEN+1];
public:
    void InputNei(int insert_idx, int insert_val);
    void Search(int start_idx);
};
cs

 

- BFS 클래스

  - 변수

     => Vertex 타입 배열 v를 설정

  - 함수

     => 값을 배열 v에 입력하는 함수(InputNei)

       -> 반환값 없음, 입력 값(int insert_idx, int insert_val)

       -> 배열 v에 입력할 값(insert_val)를 해당 위치(insert_idx)에 대입

     => 값을 찾는 함수(Search)

       -> 반환값 없음, 입력값(int start_idx)

       -> 시작 위치(start_idx)에서 탐색을 하여 끝날 때까지의 값을 출력

// BFS (Breadth First Search)
class BFS {
private:
    Vertex v[MAX_N_LEN+1];
public:
    void InputNei(int insert_idx, int insert_val);
    void Search(int start_idx);
};
cs

 

 C. 각 클래스에 입력 함수 및 수행 함수 정의

 

- DFS나 BFS나 값을 삽입하는 함수의 방식은 같다.

   => 삽입할 위치에 삽입하고자 하는 값을 넣는 방식이다.

 

- DFS 함수

   (1) 시작 위치에 방문 표시

   (2) 시작 위치 출력

   (3) 시작 위치의 이웃 정점들을 정렬

      => 정렬하는 이유는 "방문할 수 있는 정점이 여러 개인 경우에는 정점 번호가 작은 순으로 방문" 하기 때문이다.

   (4) 값이 작은 이웃 정점들부터 방문 여부 판단

    (4-1) 방문하지 않은 경우, 해당 위치부터 DFS 함수 재귀 호출

 

- 함수 코드:

// 시작 위치(start_idx)부터 DFS 하는 함수
void DFS::Search(int start_idx) {
 
    // (1) 시작 위치에 방문 표시
    v[start_idx].visit = true;
    // (2) 시작 위치 출력
    printf("%d ", start_idx);
 
    // (3) 이웃의 정점을 정렬
    std::sort(v[start_idx].neighbor.begin(), v[start_idx].neighbor.end());
 
    // (4) 값이 작은 이웃부터 반복
    for (int i = 0; i < v[start_idx].neighbor.size(); i++) {
        // (4) 다음 위치 계산
        int next = v[start_idx].neighbor[i];
 
        // (4-1) 다음 위치를 방문하지 않았으면
        if (!v[next].visit) {
            // (4-1) DFS 함수 재귀
            DFS::Search(next);
        }
    }
}
cs

 

- DFS 함수

   (1) 탐색할 위치들을 저장하는 queue 변수 설정

   (2) 시작 위치 queue 변수에 삽입

   (3) 시작 위치 방문 표시

   (4) 탐색할 위치들을 저장하는 queue가 비어있지 않는 동안 반복

    (4-1) queue의 처음 값을 저장하고 출력 후, queue에서 제거

    (4-2) queue의 처음 값의 이웃들을 정렬

      => 정렬하는 이유는 "방문할 수 있는 정점이 여러 개인 경우에는 정점 번호가 작은 순으로 방문" 하기 때문이다.

    (4-3) 값이 작은 이웃부터 반복

      (4-3-1) 다음에 방문할 위치 찾기

      (4-3-2) 방문할 위치를 방문하지 않은 경우 해당위치를 방문 표시를 한 후, queue에 삽입

   

- 함수 코드:

// 시작 위치(start_idx)부터 BFS 하는 함수
void BFS::Search(int start_idx) {
 
    // (1) 탐색할 위치들을 저장하는 queue 변수 설정
    std::queue<int> q;
    // (2) 시작 위치 queue 변수에 삽입
    q.push(start_idx);
    // (3) 시작 위치 방문 표시
    v[start_idx].visit = true;
 
    // (4) 탐색할 위치들을 저장하는 queue가 비어있지 않는 동안 반복
    while (!q.empty()) {
 
        // (4-1) queue의 처음 값을 저장
        int cur = q.front();
        // (4-1) queue에서 제거
        q.pop();
        // (4-1) queue의 처음 값 출력
        printf("%d ", cur);
 
        // (4-2) queue의 처음 값의 이웃들을 정렬
        std::sort(v[cur].neighbor.begin(), v[cur].neighbor.end());
        // (4-3) 값이 작은 이웃부터 반복
        for (int i = 0; i < v[cur].neighbor.size(); i++) {
            // (4-3-1) 다음에 방문할 위치 찾기
            int next = v[cur].neighbor[i];
 
            // (4-3-2) 다음에 방문할 위치를 방문하지 않았을 경우
            if (!v[next].visit) {
                // (4-3-2) 해당 위치를 방문 표시
                v[next].visit = true;
                // (4-3-2) 해당 위치를 queue에 삽입
                q.push(next);
            }
        }
    }
}
cs

 

소스 코드(전체적인 코드):

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
 
#define MAX_N_LEN 1000
 
typedef struct Vertex {
    bool visit;
    std::vector<int> neighbor;
};
 
// DFS (Depth First Search)
class DFS {
private:
    Vertex v[MAX_N_LEN+1];
public:
    void InputNei(int insert_idx, int insert_val);
    void Search(int start_idx);
};
 
// 입력 값(insert_val)을 해당 위치(insert_idx)에 넣는 함수
void DFS::InputNei(int insert_idx, int insert_val) {
    v[insert_idx].visit = false;
    v[insert_idx].neighbor.push_back(insert_val);
}
 
// 시작 위치(start_idx)부터 DFS 하는 함수
void DFS::Search(int start_idx) {
 
    // (1) 시작 위치에 방문 표시
    v[start_idx].visit = true;
    // (2) 시작 위치 출력
    printf("%d ", start_idx);
 
    // (3) 이웃의 정점을 정렬
    std::sort(v[start_idx].neighbor.begin(), v[start_idx].neighbor.end());
 
    // (4) 값이 작은 이웃부터 반복
    for (int i = 0; i < v[start_idx].neighbor.size(); i++) {
        // (4) 다음 위치 계산
        int next = v[start_idx].neighbor[i];
 
        // (4-1) 다음 위치를 방문하지 않았으면
        if (!v[next].visit) {
            // (4-1) DFS 함수 재귀
            DFS::Search(next);
        }
    }
}
 
 
///////////////////////////////////////////
// BFS (Breadth First Search)
class BFS {
private:
    Vertex v[MAX_N_LEN+1];
public:
    void InputNei(int insert_idx, int insert_val);
    void Search(int start_idx);
};
 
// 입력 값(insert_val)을 해당 위치(insert_idx)에 넣는 함수
void BFS::InputNei(int insert_idx, int insert_val) {
    v[insert_idx].visit = false;
    v[insert_idx].neighbor.push_back(insert_val);
}
 
// 시작 위치(start_idx)부터 BFS 하는 함수
void BFS::Search(int start_idx) {
 
    // (1) 탐색할 위치들을 저장하는 queue 변수 설정
    std::queue<int> q;
    // (2) 시작 위치 queue 변수에 삽입
    q.push(start_idx);
    // (3) 시작 위치 방문 표시
    v[start_idx].visit = true;
 
    // (4) 탐색할 위치들을 저장하는 queue가 비어있지 않는 동안 반복
    while (!q.empty()) {
 
        // (4-1) queue의 처음 값을 저장
        int cur = q.front();
        // (4-1) queue에서 제거
        q.pop();
        // (4-1) queue의 처음 값 출력
        printf("%d ", cur);
 
        // (4-2) queue의 처음 값의 이웃들을 정렬
        std::sort(v[cur].neighbor.begin(), v[cur].neighbor.end());
        // (4-3) 값이 작은 이웃부터 반복
        for (int i = 0; i < v[cur].neighbor.size(); i++) {
            // (4-3-1) 다음에 방문할 위치 찾기
            int next = v[cur].neighbor[i];
 
            // (4-3-2) 다음에 방문할 위치를 방문하지 않았을 경우
            if (!v[next].visit) {
                // (4-3-2) 해당 위치를 방문 표시
                v[next].visit = true;
                // (4-3-2) 해당 위치를 queue에 삽입
                q.push(next);
            }
        }
    }
}
 
 
int main() {
    
    // 변수 선언
    // n: 정점의 개수
    // m: 간선의 개수
    // v: 탐색을 시작할 정점 번호
    // lv: 입력받은 왼쪽 정점
    // rv: 입력받은 오른쪽 정점
    int n, m, v, lv, rv;
    DFS dfs;
    BFS bfs;
 
    scanf("%d %d %d"&n, &m, &v);
    for (int i = 0; i < m; i++) {
        scanf("%d %d"&lv, &rv);
        dfs.InputNei(lv, rv);
        dfs.InputNei(rv, lv);
        bfs.InputNei(lv, rv);
        bfs.InputNei(rv, lv);
    }
    
    dfs.Search(v);
    printf("\n");
    bfs.Search(v);
    printf("\n");
 
 
    return 0;
}
 
 
cs

 

한줄평:

 -> 탐색의 두가지를 이해하고 코드로 작성해봄으로써 더욱 잘 이해할 수 있다.

 

'알고리즘 > 백준' 카테고리의 다른 글

1753번 - 최단경로  (0) 2021.07.01
2667번 - 단지번호붙이기  (0) 2021.04.13
1655번 - 가운데를 말해요  (0) 2021.04.08
11286번 - 절댓값 힙  (0) 2021.04.06
1927번 - 최소 힙  (0) 2021.04.05