① DFS(Depth-First Search)
⚡️ 먼저 DFS는 깊이 우선 탐색이라고도 부르며, 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘
⚡️ 스택 자료구조(또는 재귀함수)를 이용하며, 구체적인 동작 과정은 아래와 같다
① 탐색 시작 노드를 스택에 삽입하고 방문 처리를 한다.
② 스택의 최상단 노드에 방문하지 않은 인접한 노드가 하나라도 있으면 그 노드를 스택에 넣고 방문 처리한다. 방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드를 꺼낸다.
③ 더 이상 2번의 과정을 수행할 수 없을 때까지 반복한다.
⚡️ 동작 예시
※ 먼저 방문노드 / 방문기준을 정하는 게 중요
→ 문제마다, 상황마다 방문노드 정하는 기준은 제각각이다
ex) 예시에서는 방문노드는 1, 방문기준은 번호가 낮은 인접노드부터 방문한다고 시작한다.
① [Step 0] 그래프를 준비한다
* 방문기준은 인접한 노드가 여러 개 일때 어떤 노드부터 방문할 지 그 방문기준을 세우는 것을 말한다.
* 간선은 방향이 없는 무방향 간선으로 간주
→ 시작 노드: 1
② [Step 1] 시작 노드인 '1'을 삽입하고 방문처리를 한다.
③ [Step 2] 스택의 최상단 노드인 '1'에 방문하지 않은 인접 노드 '2', '3', '8'이 있다.
→ 이 중에서 가장 작은 노드인 '2'를 스택에 넣고 방문처리 진행
④ [Step 3] 스택의 최상단 노드인 '2'에 방문하지 않은 인접 노드 '7'이 있다.
→ 따라서 7번 노드를 스택에 넣고 방문 처리 진행
⑤ [Step 4] 스택의 최상단 노드인 '7'에 방문하지 않은 인접 노드 '6', '8'이 존재
→ 이 중 가장 작은 노드인 '6'을 스택에 넣고 방문 처리 진행
→ 이렇게 DFS는 깊이 우선 탐색으로 진행하기에 그래프에서 가장 깊은 부분을 우선적으로 탐색하게 됨
⑥ [Step 5] 스택의 최상단 노드인 '6'에 방문하지 않은 인접노드가 없다.
→ 따라서 스택에서 '6'번 노드를 꺼낸다.
⑦ [Step 6] 스택의 최상단 노드인 '7'에 방문하지 않은 인접노드 '8'이 존재한다
→ 따라서 '8'번 노드를 스택에 넣고 방문 처리 진행
⑧ 이러한 과정을 반복하였을 때 전체 노드의 탐색 순서(스택에 들어간 순서)는 다음과 같다
≫ 탐색순서: 1 → 2 → 7 → 6 → 8 → 3 → 4 → 5
※ 탐색 방향을 구체적으로 돌아보면
▶ 1 → 2 → 7 → 6으로 갔다가
▶ 6에서 방문하지 않은 노드가 없으므로 꺼냄
▶ 최상단 노드 7 에서 방문하지 않은 노드 8로 갔다가 (7 → 8)
▶ 8에서도 방문하지 않은 노드가 없으므로 꺼냄
▶ 최상단 노드 7에서도 없으므로 꺼냄
▶ 6에서도 없으므로 꺼냄
▶ 2에서도 없으므로 꺼냄
▶ 1 → 3 → 4 → 5로 갔다가
▶ 최상단 노드 5에서 방문하지 않은 노드가 없으므로 꺼냄 → 역으로 쭈욱
▶ 정리) 1 → 2 → 7 → 6 → 7 → 8 → 7 → 2 → 1 → 3 → 4 → 5 → 4 → 3 → 2 → 1
→ 이렇게 최대한 깊게 들어가는 방향으로 동작하기 때문에 스택 대신 재귀함수로도 코드로 구현이 가능하다
* DFS python
def dfs(graph, v, visited):
#visited current node
visited[v] = True
print(v,end=' ')
#visit recursively near visited nodes
for i in graph[v]:
if not visited[i]:
dfs(graph, i, visited)
graph = [
[],#number 0 - near nodes' numbers
[2,3,8], #number 1 - near nodes' numbers
[1,7], #number 2
[1,4,5], #number 3
[3,5], #number 4
[3,4], #number 5
[7], #number 6
[2,6,8], #number 7
[1,7] #number 8
]
#visited list
visited = [False]*9
#call dfs function (start from node 1)
dfs(graph,1,visited)
⚡️ ① 그래프를 code로 2차원 리스트로 표현할 수 있고, 위 code와 같이 0번에 인접한 노드 번호, 1번에 인접한 노드 번호들을 각각의 리스트에 담아, 최종 2차원 리스트로 만든다.
⚡️ ② 각 노드가 방문된 정보를 1차원 리스트로 만든다. (default False로 구성)
⚡️ ③ dfs 함수 - 그래프, '첫 출발 노드 번호', 방문 여부 리스트 - 이렇게 세 개를 인자로 넣는 함수를 만든다
→ 재귀함수를 표현해서 현재노드와 연결된 다른 노드를 이중 리스트의 원소로 확인하고, 해당 원소인 노드들의 방문여부가 False라면, 그 노드에 dfs를 더하는 재귀 방식으로 함수를 구현
→ 방문했으면 바로 print
⚡️ 정리) 총 3개 - 그래프 2차원 list + 노드 방문 정보 1차원 list + 첫 출발 node 번호
② BFS(Breadth-First Search)
⚡️ BFS는 너비 우선 탐색이라고도 부르며, 그래프에서 가까운 노드부터 우선적으로 탐색하는 알고리즘
⚡️ 큐 자료구조를 이용하며, 구체적인 동작 과정은 아래와 같다
(프로그래밍 언어마다 큐 구현 방법을 정확히)
① 탐색 시작 노드를 큐에 삽입하고 방문 처리를 한다.
② 큐에서 노드를 꺼낸 뒤에 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리
(DFS는 인접한 노드 중 방문하지 않은 노드 1개 1개씩 각각 처리를 진행했으나, BFS는 인접한 노드 중 방문하지 않은 모든 노드를 아예 한번에 큐에 넣어 방문 처리하는 차이점!)
③ 더 이상 2번의 과정을 수행할 수 없을 때까지 반복한다.
⚡️ 동작 예시
※ 먼저 방문노드 / 방문기준을 정하는 게 중요
→ 문제마다, 상황마다 방문노드 정하는 기준은 제각각이다
ex) 예시에서는 방문노드는 1, 방문기준은 번호가 낮은 인접노드부터 방문한다고 시작한다.
① [Step 0] 그래프를 준비한다
* 방문기준은 인접한 노드가 여러 개 일때 어떤 노드부터 방문할 지 그 방문기준을 세우는 것을 말한다.
* 간선은 방향이 없는 무방향 간선으로 간주
→ 시작 노드: 1
② [Step 1] 시작 노드인 '1'을 큐에 삽입하고 방문처리를 한다.
③ [Step 2] 큐에서 노드 '1'을 꺼내 방문하지 않은 인접 노드 '2', '3', '8'이 있다.
→ 인접노드 2번, 3번, 8번 노드 모두 큐에 넣고 방문처리 진행
④ [Step 3] 큐에서 노드 '2'를 꺼내 '2'에 방문하지 않은 인접 노드 '7'이 있다.
→ 따라서 7번 노드 큐에 넣고 방문 처리 진행
⑤ [Step 4] 큐에서 노드 '3'을 꺼내 '3'에 방문하지 않은 인접 노드 '4', '5'가 있다.
→ 이 중 4번, 5번 노드 모두 큐에 넣고 방문 처리 진행
⑥ [Step 5] 큐에서 노드 '8'을 꺼냈지만 '8'에 방문하지 않은 인접 노드가 없으므로 무시
⑦ [Step 6] 이러한 과정을 반복하여 전체 노드의 탐색 순서(큐에 들어간 순서)는 아래와 같다.
≫ 탐색순서: 1 → 2 → 3 → 8 → 7 → 4 → 5 → 6
※ 탐색 방향을 구체적으로 돌아보면
▶ 1에서 인접한 방문하지 않은 노드 2 → 3 → 8 방문처리 했다가
▶ 2에서 인접 방문하지 않은 노드 7 방문처리
▶ 그 다음 3을 꺼내 3에서 인접하지 않은 방문 노드 4 → 5 방문처리
▶ 8에서는 인접 방문하지 않은 노드가 없으므로 꺼내기만 함
▶ 그 다음 7을 꺼내 인접하지 않은 방문 노드 6 방문 처리
▶ 모든 노드가 방문 처리되었으므로 끝!
▶ 정리) 1 → 2 → 3 → 8 → 7 → 4 → 5 → 6
▶ 이렇게 시작 노드 1로부터 거리가 가까운 순서대로 방문 처리되고, 거리가 가장 먼 노드가 제일 나중에 방문 처리
→ 각 간선의 비용이 동일한 상황에서 최단 거리로 정말 많이 사용되는 유형(앞선 DFS 유형보다도 실제 움직이는 거리가 적다는 점) 추후 포스팅 참고
* BFS python
from collections import deque
def bfs(graph, start, visited):
queue = deque([start]) #insert start node in queue
visited[start] = True #visited start node
#until queue is empty
while queue:
#pop one node in queue
v = queue.popleft()
print(v,end=' ')
for i in graph[v]:
if not visited[i]:
queue.append(i) #insert all nearest nodes where not visited
visited[i]=True
#2-dimensional graph
graph = [
[],
[2,3,8], #nearest nodes near node 1
[1,7],
[1,4,5],
[3,5],
[3,4],
[7],
[2,6,8],
[1,7]
]
#visited list
visited = [False] * 9
bfs(graph, 1, visited) #1 2 3 8 7 4 5 6
⚡️ ① DFS와 마찬가지로 그래프, visited 1차원 list, 시작 노드 세 개 준비
⚡️ ② queue에 처음으로 시작 node 넣은 뒤, 재귀가 아니라 queue가 다 빌 때까지 계속 반복 (queue에는 방문한 node만 삽입)
⚡️ ③ queue에 한 개씩 뽑으면서 뽑은 결과 주변 방문 안 한 node queue에 넣고 방문 처리
★ BFS 정리하면 ★ (4개만 정확히 기억하면 끝남)
(1) queue에 start node 넣기, start node 방문 처리
(2) while queue 돌리기
(3) queue.popleft()로 node 한 개 빼기
(4) 한 개 뺀 node의 인접 node for문 돌리기 - 방문 안한 node라면 방문 처리 / queue에 넣기
* DFS / BFS 대표 유형 문제풀이>
Q1. 음료수 얼려 먹기
Q. N X M 크기의 얼음 틀이 있습니다. 구멍이 뚫려 있는 부분은 0, 칸막이가 존재하는 부분은 1로 표시됩니다. 구멍이 뚫려 있는 부분까지 상, 하, 좌, 우로 붙어 있는 경우 서로 연결되어 있는 것으로 간주합니다. 이 때 얼음 틀의 모양이 주어졌을 때 생성되는 총 아이스크림의 개수를 구하는 프로그램을 작성하세요. 다음의 4 x 5 얼음 틀 예시에서는 아이스크림이 총 3개 생성됩니다.
A. 전형적인 graph 문제.
⚡️ 문제 해결 아이디어
※ 0으로 연결된 부분을 쭉 깊게 탐색 - DFS로 0 묶음 개수 아이스크림 개수 출력
① 모든 노드에 대하여 각각 0이면 1로 처리한 뒤, 음료수로 먹을 수 있으므로 카운트
② 그 다음 상/하/좌/우로 DFS를 깊게 탐색하며 연결된 부분 모두 0이면 1로 처리 (이미 카운트 1번 했으므로 1로만 모두 처리)
def dfs(x,y):
if x<=-1 or x>=N or y<=-1 or y>=M:
return False
if graph[x][y] == 0:
graph[x][y] = 1
dfs(x-1,y)
dfs(x,y-1)
dfs(x+1,y)
dfs(x,y+1)
return True
return False
N,M=map(int,input().split())
graph=[]
for i in range(N):
graph.append(list(map(int,input())))
result=0
for i in range(N):
for j in range(M):
if dfs(i,j)==True:
result+=1
print(result)
Q2. 미로 탈출
Q. N X M 크기의 직사각형 형태의 미로에 갇혔습니다. 미로에는 여러 마리의 괴물이 있어 이를 피해 탈출해야 합니다. 강아지의 위치는 (1,1)이며 미로의 출구는 (N,M)의 위치에 존재하며 한 번에 한 칸씩 이동할 수 있습니다. 이 때 괴물이 있는 부분은 0으로, 괴물이 없는 부분은 1로 표시되어 있습니다. 미로는 반드시 탈출할 수 있는 형태로 제시됩니다. 이 때 강아지가 탈출하기 위해 움직여야 하는 최소 칸의 개수를 구하세요. 칸을 셀 때는 시작 칸과 마지막 칸을 모두 포함해서 계산합니다.
A. 출발지 - 목적지가 정해진 경로에서 최단 거리를 구하는 문제
⚡️ 문제 해결 아이디어
※ 매 번 경로 노드를 진행할 때 마다 최단 경로 값을 노드에 저장하며 업데이트 하는 방식
① 처음 (1,1) 위치에서 시작
② 그 다음 BFS로 진행하는 노드마다 최단 경로의 값으로 노드 값을 바꿈
③ 맨 마지막 노드의 값을 출력하면 끝
⚡️ 최단 경로 문제는 무조건 BFS! DFS는 깊이 우선이므로, 모든 경로 각각 감안한 상태로 탐색이 아닌, 한 경로만 먼저 깊게 탐색하므로 최단 경로를 찾기에는 매우 어렵다. 한 번에 전체적으로 탐색해 모든 경로를 감안해 탐색하는 BFS를 <최단 경로> 문제 유형에서는 반드시! 사용하도록 하자
def bfs(x,y):
queue=deque()
queue.append((x,y))
while queue:
x,y=queue.popleft()
for i in range(4):
nx = x + dx[i]
ny = y + dy[i]
if nx<0 or nx>=n or ny<0 or ny>=m:
continue
if graph[nx][ny] == 0:
continue
if graph[nx][ny]==1:
graph[nx][ny] = graph[x][y] + 1
queue.append((nx,ny))
return graph[n-1][m-1]
from collections import deque
n,m=map(int,input().split())
graph=[]
for i in range(n):
graph.append(list(map(int,input())))
dx=[-1,1,0,0]
dy=[0,0,-1,1]
print(bfs(0,0))
- 이코테 2021 출처 -
'Computer Science > Algorithms' 카테고리의 다른 글
👀 Binary Search 👀 (2) | 2022.12.06 |
---|---|
understanding pesudo-code of QuickSort (0) | 2022.12.05 |
🧭Recursion (0) | 2022.09.27 |
Implementation (1) | 2022.09.21 |
Greedy (0) | 2022.09.12 |
댓글