알고리즘

[이코테] DFS/BFS

바랄 희 2022. 8. 14. 17:17

DFS / BFS 에 필요한 자료구조

 

1. 스택

stack = []
stack.append(5)
stack.append(4)
stack.append(3)
stack.pop()

print(stack) # 5, 4
print(stack[::-1]) # 4, 5

 

2. 큐

from collections import deque

queue = deque()

queue.append(5)
queue.append(4)
queue.append(3)
queue.popleft()

print(queue) # 4, 3
queue.reverse() #역순으로 바꾸기
print(queue) # 3, 4

 

재귀함수

종료조건

재귀함수가 언제 끝날지 종료 조건을 반드시 명시해야 함

수행에 스택 자료구조를 사용함 (마지막에 호출한 함수가 종료되어야 그 앞의 함수 호출이 종료되기 때문)

 

재귀함수를 사용했을 때의 이점

점화식을 그대로 옮겨놨기 때문에 가독성이 좋고 간결하다 (팩토리얼의 경우)

 

 

DFS vs BFS

 

DFS BFS
최대한 멀리 있는 노드부터 가장 가까운 노드부터
스택
재귀 이용 큐 이용

 

DFS 

깊이우선 탐색

그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘

그래프는 노드와 간선으로 이루어지고 노드를 정점 (vertex) 라고 하기도 함

최대한 멀리 있는 노드를 우선적으로 탐색

 

DFS 를 표현하는 두가지 방법

 

1. 인접 행렬 방식

INF = 99999999

graph = [
[0, 7, 5],
[7, 0, INF],
[5, INF, 0]
]

 

연결되어 있지 않은 노드의 경우에는 INF(무한)

 

2. 인접 리스트 방식

자료구조 연결리스트를 이용하여 구현

모든 노드에 연결된 노드에 대한 정보를 차례대로 연결해서 저장

ex)

0 - 1 - 2

1 - 0

2 - 0

이라면 2는 1과 연결되지 않고, 1은 2와 연결되지 않은 것.

 

두 방식의 비교

 

인접 행렬 방식의 단점

- 모든 관계를 저장하기 때문에 노드의 개수가 많을수록 비효율적

 

인접 행렬 방식의 장점

- 특정한 두 노드가 연결되어 있는지 빠르게 확인 가능

ex) graph[1][7] 을 통해서 1과 7이 연결되어 있는지 확인할 수 있음

반면 인접리스트방식의 경우에는 앞에서부터 차례대로 확인해야 함

 

인접 리스트 방식의 장점

- 연결된 정보만을 저장하기 때문에 메모리를 효율적으로 사용

 

DFS 의 동작 방식

 

1. 시작 노드를 스택에 삽입, 방문 처리

2. 스택의 최상단 노드에 연결된 방문하지 않은 인접한 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리

방문하지 않은 인접한 노드가 없으면 해당 노드를 스택에서 꺼내기

3. 2번의 과정을 더 이상 수행할 수 없을 때까지 반복하기

 

def dfs(graph, v, visited):
	visited[v] = True #방문 처리
    print(v, end=' ')
    for i in graph[v]:
    	if not visited[i]: #해당 노드를 방문하지 않았다면 재귀
        	dfs(graph, i, visited)
            
 dfs(graph, 1, visited)

 

BFS

너비 우선 탐색

가까운 노드부터 탐색

인접한 노드를 반복적으로 큐에 넣도록

 

BFS 의 동작 방식

1. 탐색 시작 노드를 큐에 삽입 / 방문처리

2. 큐에서 노드를 꺼내서 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입

3. 2번의 과정을 반복

 

from collections import deque

def dfs(graph, start, visited):
	queue = deque([start])
    visited[start] = True
    
    #큐가 비어있지 않은 동안
    while queue:
    	v = queue.popleft()
        print(v, end=' ')
        for i in graph[v]:
        	if not visited[i]:
        		queue.append(i)
                visited[i] = True

dfs(graph, 1, visited)

 

* 2차원 배열의 탐색 문제를 만났을 때 그래프로 바꿔서 생각하면 더 쉽게 풀이할 수 있음

실전 문제

1. 음료수 얼려 먹기

 

# p.149 실전문제 3 음료수 얼려 먹기

n,m = map(int,input().split())
graph = [
    list(map(int,input()))
    for _ in range(n)
]
visited = [
    [False for _ in range(m)]
    for _ in range(n)
]
cnt = 0

def in_range(x,y):
    return x<n and y<m and x>=0 and y>=0


def bfs(x, y):
    if not in_range(x,y): #범위를 넘어가면 false 반환
        return False

    if not visited[x][y] and graph[x][y]==0:
        visited[x][y] = True
        #연결된 노드 전부, 즉 상하좌우로 방문
        bfs(x-1,y)
        bfs(x,y-1)
        bfs(x,y+1)
        bfs(x+1,y)
        return True
    return False

for i in range(n):
    for j in range(m):
        if bfs(i,j)==True:
            cnt+=1

print(cnt)

 

우선, 얼음틀을 넘어가면 False 를 반환하도록 했다.

연결된 노드를 전부 방문해야 하는데 얼음틀의 경우에는 연결된 노드가 인접한 상하좌우이기 때문에 해당 노드를 전부 방문하도록 했다. 그러나 해당 부분을 

1. 이미 방문했거나,

2. 막혀있는 경우에는 

더 이상 갈 수 없기 때문에 해당 경우에는 False 를 반환하도록 했다.

 

상하좌우를 방문하며 방문 할 수 있는 노드에는 visited 배열에 True 로 표시될 것이기 때문에 각 노드를 시작점으로 잡고, True 인 경우의 개수를 세면 최종적으로 아이스크림의 개수를 구할 수 있다.

 

2. 미로 탈출

from collections import deque

n,m = map(int,input().split())
graph = []

for _ in range(n):
    graph.append(list(map(int,input())))

# 1로만 움직일 수 있음
# 움직여야 하는 칸의 개수

dxs = [-1, 1, 0, 0]
dys = [0, 0, -1, 1]

def in_range(x,y):
    return x<n and y>=0 and x>=0 and y<m

def bfs(x,y):
    queue = deque() #방문한 곳들

    queue.append((x,y)) #시작 위치 우선 큐에 넣고 시작

    while queue:
        x,y = queue.popleft() #인접한 노드의 위치

        for i in range(4):
            new_x = x+dxs[i]
            new_y = y+dys[i]
            if in_range(new_x,new_y) and graph[new_x][new_y]!=0 and graph[new_x][new_y]==1:
                graph[new_x][new_y] = graph[x][y]+1
                queue.append((new_x,new_y))

bfs(0,0)
print(graph[n-1][m-1])

너비 우선 탐색을 이용한 문제다.

범위 내에 있고, 괴물이 없으면 이동할 수 있다.

또한, 새로 방문한 경우에만 거리를 업데이트 해준다.

새로 방문한 경우가 아닌 경우에 거리를 업데이트 해주게 되면 최단 거리를 업데이트 하는 것이 아니기 때문이다.