❓그래프의 탐색 알고리즘
그래프의 모든 정점들을 특정한 순서에 따라 방문하는 알고리즘
DFS와 BFS는 분명히 차이가 있다! (DFS=좁고 깊게, BFS=넓고 얕게)
[프로그래머스 미로탈출] 사전순으로 가장 빠른 경로로 탈출해야 하는 경우, DFS를 이용해야 한다
import sys
sys.setrecursionlimit(10000) # RecursionError 방지
adj = [[0] * (n + 1) for _ in range(n+1)] # 인접행렬
for _ in range(m):
a, b = map(int, input().split())
adj[a][b] = 1
adj[b][a] = 1
visited = [False for _ in range(n+1)]
def dfs(v):
visited[v] = True
for i in range(1, n+1):
if (not visited[i]) and adj[v][i] == 1:
dfs(i)
# 한 번에 모든 정점을 다 볼 수 없는 경우
# 그래프가 몇 개의 컴포넌트로 나뉘어 있는지 알 수 있음
def dfsAll():
for i in range(1, n+1):
if not visited[i]:
dfs(i)
RecursionError가 발생한다면, 방문 체크를 안해준 게 원인일 것
가중치가 없는 그래프에서 두 지점 사이의 거리를 찾는 문제는 BFS
가중치가 있다면 플로이드나 다익스트라
## BFS의 대표적인 풀이방법 1
adj = [[0] * (n + 1) for _ in range(n+1)]
for i in range(m):
adj[a][b] = 1
adj[b][a] = 1
visited = [False for _ in range(n+1)]
dist = [0 for _ in range(n+1)]
# v 노드부터 탐색
def bfs(v):
q = deque()
q.append(v) # v를 방문함
visited[v] = True
while q:
here = q.popleft()
for i in range(1, n+1):
if not visited[i] and adj[here][i] == 1:
q.append(i) # 처음 보는 정점이면 방문 목록에 집어넣음
visited[i] = True
dist[i] = dist[here] + 1 # 탐색 레벨 구하기
## BFS의 대표적인 풀이방법 2
# 3차원 배열의 경우 -> dx, dy, dx = [0, 0, -1, 1, 0, 0], [-1, 1, 0, 0, 0, 0], [0, 0, 0, 0, -1, 1]
from collections import deque
dx = [0, 0, -1, 1]
dy = [-1, 1, 0, 0]
visited = [[0] * m for _ in range(n)]
def bfs(y, x):
q = deque()
q.append([y, x])
visited[y][x] = 1
while q:
y, x = q.popleft()
if y == (n - 1) and x == (m - 1):
break
for i in range(4):
nx = x + dx[i]
ny = y + dy[i]
if 0 <= nx < m and 0 <= ny < n and maze[ny][nx] == 1 and visited[ny][nx] == 0:
maze[ny][nx] = maze[y][x] + 1 # 최단거리 구하기
visited[ny][nx] = 1
q.append([ny, nx])
공통 원소가 없는 두 집합
무방향 그래프 내에서 사이클을 판별할 때 사용할 수 있음 (방향 그래프는 DFS 사용)
서로소 집합 자료구조는 union과 find 2개의 연산으로 조작할 수 있음
💡 기본적인 서로소 집합 알고리즘 코드
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
# 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if parent[x] != x:
return find_parent(parent, parent[x])
return parent[x]
# 두 원소가 속한 집합을 합치기
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a # parent[2] = 1
else:
parent[a] = b
# 노드의 개수와 간선의 개수 입력받기
v, e = map(int, input().split())
parent = [0] * (v + 1) # 부모 테이블 초기화
# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, v+1):
parent[i] = i
# union 연산을 각각 수행
for i in range(e):
a, b = map(int, input().split())
union_parent(parent, a, b)
# 각 원소가 속한 집합 출력
for i in range(1, v+1):
print(find_parent(parent, i), end=' ') # 1 1 1 1 5 5
💡 서로소 집합을 활용한 사이클 판별 코드
def find_parent(parent, x):
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
def union_parent(parent, a, b):
a = find_parent(parent, a)
b = find_parent(parent, b)
if a < b:
parent[b] = a
else:
parent[a] = b
v, e = map(int, input().split())
parent = [0] * (v + 1)
for i in range(1, v+1):
parent[i] = i
cycle = False
for i in range(e):
a, b = map(int, input().split())
if find_parent(parent, a) == find_parent(parent, b):
cycle = True
break
else:
union_parent(parent, a, b)
if cycle:
print('사이클이 발생했습니다')
else:
print('사이클이 발생하지 않았습니다')
# 벽을 K개까지 부수고 이동해도 됨
q.append([0, 0, 0])
visited = [[[0] * (k + 1) for _ in range(m)] for _ in range(n)]
# (ny, nx)가 벽이 아닐 때
if visited[ny][nx][z] == 0 and maps[ny][nx] == 0:
q.append([ny, nx, z])
visited[ny][nx][z] = visited[y][x][z] + 1
# (ny, nx)는 벽이지만 아직 벽을 부술 기회가 남아 있을 때
if z < k and maps[ny][nx] == 1 and visited[ny][nx][z+1] == 0:
q.append([ny, nx, z+1])
visited[ny][nx][z+1] = visited[y][x][z] + 1
벽/장애물에 부딪칠 때까지 한 방향으로 이동할 때
q = deque()
q.append([sy, sx])
dict = [[-1] * m for _ in range(n)]
dict[sy][sx] = 0
while q:
y, x = q.popleft()
if y == ey and x == ex:
answer = dict[y][x]
break
for i in range(4):
ny, nx = y, x
while True:
ny += dy[i]
nx += dx[i]
if ny < 0 or ny >= n or nx < 0 or nx >= m:
ny -= dy[i]
nx -= dx[i]
break
if board[ny][nx] == 'D':
ny -= dy[i]
nx -= dx[i]
break
# dist 배열을 사용하지 않으면, 방문한 곳의 위치를 계속 큐에 넣게 됨 -> 무한루프
if dict[ny][nx] == -1:
q.append([ny, nx])
dict[ny][nx] = dict[y][x] + 1