0247

개발

[백준] 2644번 - 촌수계산

문제 링크 : https://www.acmicpc.net/problem/2644

문제 탐색하기

1) 문제 분석

  • line1 : 전체 사람의 수 : n

  • line2 : 촌수 계산 인원 : a, b

  • line3 : 부모자식 관계의 수 : m

  • line4 : 부모 번호, 자식 번호

  • 각 사람의 부모는 최대 한 명

  • 친척 관계가 없는 경우 -1을 출력

2) 가능한 시간복잡도

(1) 입력 처리

  • 첫 번째 줄 ~ 세 번째 줄 입력 처리 : O(1) * 3 = O(1)

  • 네 번째 줄 처리 : O(m)

(2) 부모자식관계 추출 -> O(m)

(3) 방문 여부 기록, 인접 리스트 배열 선언 -> O(n)

(4) 인접 리스트 배열 삽입 -> O(m)

(5) 탐색 -> O(n + m)

  • 최악의 경우, 노드 수 n개의 노드를 모두 방문하고,

  • 각 노드에서 연결된 간선을 모두 확인 (총 간선 m개 확인)

(6) 출력 -> O(1)

(7) 전체 시간복잡도

  • 입력 처리 : O(1)

  • 부모자식관계 추출 : O(m)

  • 배열 선언 : O(m)

  • 배열 삽입 : O(m)

  • 탐색 : O(n + m)

  • 출력 : O(1)-> 총합 : O(n + m)

3) 알고리즘 선택 이유

  • 이 문제는 두 사람 사이의 촌수(최단 거리)를 계산해야 한다. 사람들 사이의 관계는 부모-자식 관계로 구성된 그래프 형태이며, 특정 두 노드(사람) 사이의 가장 짧은 경로를 찾아야 한다.

  • 따라서

    • 사람들 간의 관계는 무방향 그래프로 구성하고,

    • BFS(너비 우선 탐색)을 사용하여 두 사람 사이의 최단 경로(촌수)를 계산하는 방식이 가장 적합하다.

  • BFS는 그래프 내에서 최단 거리를 구할 때 가장 효율적인 방법이다.

  • 관계의 개수(m)와 사람 수(n)가 비교적 작고 트리 구조가 아닌 일반적인 그래프 형태이기 때문에,

  • BFS 탐색 O(n + m)으로 충분히 빠르게 문제를 해결할 수 있다.
    -> 즉, 이 문제는 BFS(너비 우선 탐색) 기반으로 해결하는 것이 가장 직관적이고 효율적인 선택이다.

잠깐! 그래프, BFS/DFS에 대한 간단한 설명, 그리고 관련 예시 문제는 여기를 참고하자.

코드 설계하기

1. 실행 구조

1) 사용자 입력

  • 전체 사람 수(n), 촌수를 계산할 두 사람(a, b), 부모-자식 관계 수(m), 그리고 m개의 부모-자식 관계 데이터를 입력받는다.

  • 각 데이터를 공백 기준으로 나눈 후 숫자로 변환하여 사용한다.

2) 그래프 초기화

  • 사람들 사이의 부모-자식 관계를 양방향으로 연결하여 인접 리스트 형태의 그래프를 생성한다.

  • 방문 여부를 확인하기 위한 visited 배열도 함께 초기화한다.

3) BFS 탐색 함수 구현

  • BFS(너비 우선 탐색) 방식을 사용하여 시작 사람(a)으로부터 목표 사람(b)까지 이동하며 촌수를 계산한다.

  • 큐(queue)를 이용하여 현재 사람과 촌수를 함께 저장하고, 큐에서 꺼낼 때마다 목표 사람인지 확인한다.

  • 부모, 자식으로 연결된 사람들을 모두 탐색하며, 이미 방문한 사람은 다시 방문하지 않도록 한다.

4) 탐색 및 출력

  • BFS를 실행하여 두 사람 사이의 촌수를 계산하고, 결과를 출력한다.

  • 만약 두 사람 사이에 연결된 경로가 없다면 -1을 출력한다.

2. 고민이 되었던 부분

: 두 사람이 서로 얼마나 가까운지(촌수)를 구하기 위해 어떤 알고리즘을 사용할지 고민하게 되었다. 결국 이 문제는 사람들 간의 관계를 그래프로 구성한 후, 두 사람 사이의 최단 경로를 찾는 문제라는 점을 인식했다. 따라서 다음 두 가지 전제를 세웠다.

첫 번째, 부모-자식 관계는 한 방향이 아니라 양방향으로 연결된 관계로 이해해야 했다. 부모에서 자식으로만 이동할 수 있는 게 아니라, 자식에서 부모로도 이동할 수 있기 때문에 인접 리스트를 무조건 양방향으로 구성해야 했다.

두 번째, 최단 거리 탐색을 위해 BFS(너비 우선 탐색)를 선택했다. BFS는 시작 노드로부터 인접한 노드를 모두 순서대로 탐색하면서 최단 경로를 찾는 데 가장 적합한 알고리즘이다. 따라서 큐를 이용한 BFS를 통해 촌수를 자연스럽게 계산할 수 있도록 구현했다. 큐에는 현재 사람 번호와 촌수를 함께 저장해 촌수를 따로 관리할 수 있도록 구성했다.

이렇게 함으로써, 두 사람이 서로 연결된 경우 최단 촌수를 바로 구할 수 있고, 연결되지 않은 경우 -1을 반환할 수 있는 구조를 완성할 수 있었다.



정답 코드

JavaScript
// 사용 언어 : Javascript

// 입력 처리
const fs = require('fs');
const input = fs.readFileSync('/dev/stdin').toString().trim().split("\n");

// 첫 번째 줄 : n(전체 사람의 수)
const n = Number(input[0]);
input.shift();

// 두 번째 줄 : a, b(촌수 계산 인원)
const [a, b] = input[0].split(" ").map(Number);
input.shift();

// 세 번째 줄 : m(부모자식 관계의 수)
const m = Number(input[0]);
input.shift();

// 부모자식 관계 정보 추출
const edges = [];
input.forEach(edge => edges.push(edge.split(" ").map(Number)));

// 방문 여부 기록 배열
const visited = new Array(n + 1).fill(false);

// 인접 리스트 형태로 그래프 초기화
const graph = Array.from({ length: n + 1 }, () => []);

// 무방향 그래프 : 서로 연결된 관계 추가
for (let [u, v] of edges) {
    graph[u].push(v);
    graph[v].push(u);
}

// BFS 함수 : 시작 노드(start)에서 목표 노드(target)까지 몇 촌인지 계산하는 함수
function bfs(start, target) {

    // 큐(queue)에 시작 노드와 현재 촌수를 넣음
    // 큐에는 [사람 번호, 지금까지 센 촌수] 형태로 저장됨
    const queue = [[start, 0]];

    // 시작 노드는 방문 처리 (자기 자신은 다시 방문하지 않도록)
    visited[start] = true;

    // 큐가 빌 때까지 반복 (탐색 계속)
    while (queue.length) {

        // 큐에서 하나 꺼내기 : 현재 사람과 지금까지의 촌수
        const [current, count] = queue.shift();

        // 만약 목표 사람을 찾았다면? -> 지금까지의 촌수를 바로 반환
        if (current === target) {
            return count;
        }

        // 현재 사람과 연결된 모든 사람을 하나씩 확인
        graph[current].forEach(next => {

            // 아직 방문하지 않은 사람이라면
            if (!visited[next]) {
                visited[next] = true;              // 방문 처리
                queue.push([next, count + 1]);     // 큐에 추가 (촌수 1 증가시켜서)
            }

        });
    }

    // 큐를 다 돌았는데도 목표 사람을 찾지 못했다면 -> 서로 연결되지 않은 상태
    return -1;
}

const result = bfs(a, b);
console.log(result);

마무리하며

어제 따로 공부했던 BFS, DFS, 그리고 그래프 개념이 오늘 이 문제를 풀면서 확실히 도움이 되었다는 걸 실감했다. 단순히 개념으로만 이해했을 때는 그냥 노드와 간선을 돌면서 탐색하는 방식 정도로 생각했는데, 오늘 이 문제를 통해 그게 실제로 ‘사람들의 관계’를 탐색하는 데 적용될 수 있다는 걸 몸으로 느낀 것 같다.

특히 BFS가 왜 ‘최단 거리’를 찾는 데 적합한지, 이론으로는 알았지만 직접 큐를 이용해 촌수를 하나씩 세어가며 구현하면서 훨씬 더 명확하게 이해할 수 있었다. 단순히 깊게 파고들기보단 넓게 펼치면서 탐색하는 과정 속에서 자연스럽게 거리를 계산할 수 있다는 걸 코드로 경험한 게 큰 도움이 되었다.

또한, 부모-자식 관계를 그래프로 표현하고, 그것을 양방향으로 처리하는 것 역시 어제 공부했던 그래프의 구조가 떠올랐기에 바로 적용할 수 있었다. 만약 이런 개념을 미리 공부하지 않았다면 단방향 관계로만 생각해서 문제를 계속 꼬이게 풀었을 것 같다.

이번 문제를 통해 단순히 개념을 아는 것과, 실제 문제에 적용할 수 있는 것은 다르다는 것을 다시 배웠다. 알고리즘을 외우는 게 아니라 ‘언제 써야 하는지’, ‘왜 써야 하는지’를 이해하는 게 중요하다는 걸 실감했다. 앞으로도 문제를 풀 때, 개념이 어떻게 실전에서 쓰이는지를 하나씩 체감해 가고 싶다. 어제 공부한 내용이 단순한 지식이 아니라, 오늘 실전에서 무기가 되어준 느낌이었다.