오늘의 문제 - DFS/BFS
백준 2644 - 촌수계산
https://www.acmicpc.net/problem/2644
내 풀이 및 접근 방식
1. BFS 이용
촌수는 곧 간선의 개수와 같다. 본인과 간선으로 직접 연결되어 있는 모든 사람은 1촌이고, 이후 1촌으로부터 파생된 1촌은 2촌이 되고, 2촌에서 파생된 1촌은 3촌이 된다. 그 과정에서 가까운 노드부터 차례대로 탐색하는 BFS 방식이 떠올랐다.
from collections import deque
import sys
n = int(sys.stdin.readline()) # 사람 수
a, b = map(int, sys.stdin.readline().split())
m = int(sys.stdin.readline()) # 부모자식 관계 수
# 부모 자식 간 그래프
m_list = [[] for _ in range(n+1)]
for _ in range(m):
x, y = map(int, sys.stdin.readline().split())
m_list[x].append(y)
m_list[y].append(x)
visited = [False for _ in range(n+1)] # 방문 처리
def bfs(n1, n2):
cnt = 0 # 촌수
visited[n1] = True
queue = deque([(n1, cnt)])
while queue:
node, cnt = queue.popleft()
for cn in m_list[node]: # node(사람)의 1촌 탐색
if not visited[cn]: # 방문 안 한 사람인 경우
visited[cn] = True # 방문 처리
queue.append((cn, cnt+1)) # 큐에 (사람번호, n1과의 촌수) 삽입
if cn == n2: # n2이면 촌수 반환하고 반복문 종료
return cnt+1
return -1
print(bfs(a,b))
풀이
- 입력 받은 부모 자식 관계를 바탕으로 이중 리스트를 이용한 그래프를 만든다.
이때 부모 자식 관계는 양방향 간선으로 표현하기 위해 양쪽에다가 요소를 추가한다.
예제 입력 1의 결과를 이중 리스트로 표현하면 아래와 같다.
graph = [[], [2, 3], [1, 7, 8, 9], [1], [5, 6], [4], [4], [2], [2], [2]]
이 때, 1의 1촌은 인덱스1 의 값으로 [2,3]이 되는 것이고, 2의 1촌은 인덱스2 값으로 [1, 7, 8, 9]가 되는 것이다. - bfs: n1과 n2의 촌수 계산
- 먼저 n1과 cnt(n1과의 촌수= 0)를 나타내는 cnt를 큐에 넣고 n1은 방문처리한다.
- 큐에서 가장 첫번째 요소인 사람을 pop하여 1촌을 탐색하고, 1촌 중에 방문하지 않은 사람의 경우에만 촌수를 1씩 더해서 큐에 삽입한다.
- 만약 탐색하는 1촌 중에 n2를 발견하면 촌수를 반환한다.
위와 같은 과정을 큐에 사람이 남아있을 때까지 반복한다. while 문이 종료될 때까지 촌수를 구하지 못한 경우에는 -1을 반환한다.
2. DFS 이용
이전에 풀었던 풀이를 보니까 DFS를 이용했었다.
import sys
n = int(sys.stdin.readline())
a, b = map(int, sys.stdin.readline().split())
m = int(sys.stdin.readline())
graph = [[] for _ in range(n+1)]
for _ in range(m):
x, y = map(int, sys.stdin.readline().split())
graph[x].append(y)
graph[y].append(x)
v = [False for _ in range(n+1)]
ans = -1
def dfs(t, cnt):
global ans
if t == b:
ans = cnt
for nt in graph[t]:
if not v[nt]:
v[nt] = True
dfs(nt, cnt+1)
v[a] = True # a 방문처리
dfs(a, 0)
if ans == -1:
print(-1)
else:
print(ans)
풀이
- 입력 받은 부모 자식 관계를 바탕으로 이중 리스트를 이용한 그래프를 만든다.
- dfs: n1과 n2의 촌수 계산
bfs와 다른 부분은 1촌 탐색을 하고 큐에 넣는 것이 아니라 다시 dfs 함수를 호출한다.
회고
DFS(깊이 우선 탐색)
- 경로를 따라 깊이 들어가며 탐색하는 방식
- 재귀적 접근을 사용
- 백트래킹을 통해 모든 경로를 확인할 수 있다.
- 특정 경로를 찾거나, 경로의 끝에 도달하는 문제에 적합하다.
- 최단 경로를 찾는 데는 비효율적
BFS(너비 우선 탐색)
- 동일한 레밸의 노드를 먼저 탐색하는 방식
- 큐를 사용하고, 가장 가까운 노드부터 탐색을 확장해나간다.
- 최단 경로를 찾는 데 효율적
- 각 단계에서 현재 노드와 연결된 모든 노드를 검사하기 때문에 최단 경로 보장
백트래킹 개념
- 재귀적 탐색: 문제를 해결하기 위해 재귀적으로 탐색 수행
- 경로 포기: 특정 경로가 목표 상태에 도달할 수 없다고 판단되면, 해당 경로 포기하고 다른 경로 탐색
- 유망성 검사: 현재 경로가 목표 상태로 이어질 가능성이 있는지 검사하여, 유망하지 않은 경로는 일찍 포기
백트래킹 관련 예제 - N-Queens
N x N 체스판에 N개의 퀸을 서로 공격하지 못하도록 배치하는 문제
def is_safe(board, row, col, n):
# Check this column on upper side
for i in range(row):
if board[i][col] == 1:
return False
# Check upper diagonal on left side
for i, j in zip(range(row, -1, -1), range(col, -1, -1)):
if board[i][j] == 1:
return False
# Check upper diagonal on right side
for i, j in zip(range(row, -1, -1), range(col, n)):
if board[i][j] == 1:
return False
return True
def solve_nqueens(board, row, n):
if row >= n:
return True
for col in range(n):
if is_safe(board, row, col, n):
board[row][col] = 1
if solve_nqueens(board, row + 1, n):
return True
board[row][col] = 0 # Backtrack
return False
def print_board(board, n):
for row in board:
print(' '.join('Q' if x else '.' for x in row))
print()
n = 4
board = [[0] * n for _ in range(n)]
if solve_nqueens(board, 0, n):
print_board(board, n)
else:
print("Solution does not exist")
N-Queens 예제 설명
- is_safe 함수: 현재 위치에 퀸을 놓을 수 있는지 확인합니다.
- solve_nqueens 함수: 재귀적으로 퀸을 놓고, 다음 행으로 이동하며 가능한 모든 배치를 시도합니다.
- 백트래킹: 특정 위치에 퀸을 놓고, 그 결과가 실패하면 퀸을 제거하고 다음 위치를 시도합니다.
'코테 스터디 99클럽' 카테고리의 다른 글
99클럽 코테 스터디 19일차 TIL + 탐욕법(Greedy) (0) | 2024.08.10 |
---|---|
99클럽 코테 스터디 18일차 TIL + DFS/BFS (0) | 2024.08.09 |
99클럽 코테 스터디 16일차 TIL + 완전탐색 (0) | 2024.08.07 |
99클럽 코테 스터디 15일차 TIL + 완전탐색 (1) | 2024.08.06 |
99클럽 코테 스터디 14일차 TIL + 이분탐색 (0) | 2024.08.05 |