BFS에서 visited를 push할 때 찍어야 하는 이유

AlgorithmBFS

결론

  • BFS: visitedpush할 때 찍는다
  • DFS: visited재귀 진입 시 찍는다

이유는 큐에 넣는 시점과 꺼내는 시점 사이의 시간 차 때문이다.


BFS: pop할 때 찍으면 생기는 문제

A와 B가 큐에 들어있고, 둘 다 C의 이웃이라고 하자.

큐: [A, B]

1. pop A → C 미방문 → push C       큐: [B, C]
2. pop B → C 아직 미방문 → push C   큐: [C, C]  ← 중복

A가 C를 push했지만 visited를 안 찍었으므로, B가 C를 볼 때도 미방문이다. C가 큐에 두 번 들어간다.

격자에서는 한 칸을 최대 4방향 이웃이 동시에 발견할 수 있다. 중복 push는 큐 크기를 불필요하게 키우고, 같은 칸을 여러 번 처리하여 시간 초과를 유발한다.

BFS: push할 때 찍으면

큐: [A, B]

1. pop A → C 미방문 → visited[C]=true, push C   큐: [B, C]
2. pop B → C 이미 방문 → skip                    큐: [C]

C는 큐에 한 번만 들어간다.


DFS: 재귀 진입 시 찍어도 되는 이유

dfs(A)
  → visited[A] = true
  → dfs(C)               ← 발견 즉시 재귀 진입
       → visited[C] = true
       → ...
  → (돌아옴) C 이미 방문 → skip

DFS는 C를 발견하면 즉시 재귀로 들어가서 visited[C] = true를 찍는다. “발견 → 처리” 사이에 다른 노드가 끼어들 틈이 없다. 호출 스택 자체가 한 노드에 하나의 진입만 보장한다.

BFS는 큐에 넣고 나중에 꺼내므로, 그 사이에 다른 노드가 같은 칸을 또 발견할 수 있다. 이것이 근본적인 차이다.


코드 비교

BFS (push 시 마킹)

queue<Node> q;
q.push({0, 0, 1});
visited[0][0] = true;       // push와 동시에

while (!q.empty()) {
    auto [x, y, dist] = q.front();
    q.pop();

    for (int d = 0; d < 4; d++) {
        int nx = x + dx[d], ny = y + dy[d];
        if (/* 범위 밖 */) continue;
        if (visited[nx][ny] || maps[nx][ny] == 0) continue;

        visited[nx][ny] = true;  // push 전에 마킹
        q.push({nx, ny, dist + 1});
    }
}

DFS (진입 시 마킹)

void dfs(int node, vector<vector<int>>& adj, vector<bool>& visited) {
    visited[node] = true;        // 진입과 동시에

    for (int next : adj[node]) {
        if (!visited[next])
            dfs(next, adj, visited);
    }
}

정리

BFSDFS
visited 시점push할 때재귀 진입 시
이유push-pop 사이 시간 차로 중복 가능발견 즉시 진입, 끼어들 틈 없음
안 지키면중복 push → 시간 초과문제 없음 (구조적으로 중복 불가)