어디까지 갈 수 있을까?

chapter 5. DFS/BFS 본문

알고리즘/이것이취업을위한코딩테스트다

chapter 5. DFS/BFS

_Min 2021. 2. 13. 20:53
DFS - 재귀호출
- 백트래킹
- 조건 많은 그래프
BFS - 큐
- 최단 거리, 최소 비용 문제
- 모든 간선의 비용 1

DFS/BFS 둘 다 visited 배열 만들어서 방문처리를 해줘야 한다

반복문이 재귀함수보다 빠르게 동작하는 경우가 많기 때문에 DFS, BFS 둘 다로 구현 가능하면 BFS를 사용하는 게 낫다

 

1. 스택

선입후출 구조로 한쪽에서만 입력과 출력이 일어나기 때문에박스 쌓기에 비유된다

1
2
3
4
5
6
7
8
9
10
11
stack=[]
stack.append(5#5
stack.append(2#5 2
stack.append(3#5 2 3
stack.append(7#5 2 3 7
stack.pop() #5 2 3
stack.append(1#5 2 3 1
stack.append(4#5 2 3 1 4
stack.pop() #5 2 3 1
 
print(stack[::-1]) #1 3 2 5
cs

파이썬의 append()와 pop() 메서드를 이용해 스택 자료구조와 동일하게 동작할 수 있다

 

 

2. 큐

선입선출 구조로 한쪽에서는 입력이, 한쪽에서는 출력이 일어나게 된다. 대기줄에 비유된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from collections import deque
 
queue=deque()
 
queue.append(5# 5
queue.append(2# 5 2
queue.append(3# 5 2 3
queue.append(7# 5 2 3 7
queue.popleft() # 2 3 7
queue.append(1# 2 3 7 1
queue.append(4# 2 3 7 1 4
queue.popleft() # 3 7 1 4
 
print(queue) # deque([3, 7, 1, 4])
queue.reverse() #
print(queue) # deque([4, 1, 7, 3])
 
print(list(queue)) # [4, 1, 7, 3]
cs

deque는 내부적으로 연결 리스트로 구현돼 있기 때문에 리스트에 비해 데이터를 넣고 빼는 속도가 빨라 큐를 구현할 때 사용한다

list(queue)를 통해 리스트를 큐로 변경할 수 있다

 

 

 

3. 재귀함수

자기자신을 호출하는 함수로 반복문과 같이 종료조건을 명시해줘야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 반복적으로 구현한 n!
def factorial_iterative(n):
    result=1
    for i in range(2, n+1):
        result*=i
    return result
 
# 재귀적으로 구현한 n!
def factorial_recursive(n):
    if n<=1:
        return 1
    return n*factorial_recursive(n-1)
 
print('반복적', factorial_iterative(5)) #120
print('재귀적', factorial_recursive(5)) #120
cs

실행결과는 동일하지만 재귀함수의 코드가 더 간결하다

 

4. DFS

Depth-First Search, 깊이 우선 탐색이다

 

 

그래프는 2가지 방식으로 표현할 수 있다

  • 인접 행렬 : 2차원 배열로 그래프의 연결 관계를 표현
  • 인접 리스트 : 리스트로 그래프의 연결 관계를 표현

 

 

 

① 인접 행렬 방식

1
2
3
4
5
6
7
8
9
INF=999999999 #무한
 
graph = [
    [075],
    [70, INF],
    [5, INF, 0]
]
 
print(graph) # [[0, 7, 5], [7, 0, 999999999], [5, 999999999, 0]]
cs

자기자신은 0으로 서로 연결 돼 있지 않은 노드끼리는 INF(무한, infinity)로 초기화한다

 

 

 

② 인접 리스트 방식

1
2
3
4
5
6
7
8
graph=[[] for _ in range(3)] # [[], [], []]
 
graph[0].append((17))
graph[0].append((25))
graph[1].append((07))
graph[2].append((05))
 
print(graph) # [[(1, 7), (2, 5)], [(0, 7)], [(0, 5)]]
cs

인접 리스트 방식은 인접 행렬 방식에 비해 두 노드가 연결돼 있는지 정보를 얻는 속도가 느리다.

 

 

DFS는 깊이 우선으로 번호가 낮은 순서부터 처리한다, 재귀호출한다

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def dfs(graph, v, visited):
    #현재 노드 방문 처리
    visited[v]=True
    print(v, end=' ')
 
    #현재 노드와 연결된 다른 노드를 재귀적으로 방문
    for i in graph[v]:
        if visited[i]==False:
            dfs(graph, i, visited)
 
graph=[[], #0번 노드 없음
       [238],
       [17],
       [145],
       [35],
       [43],
       [7],
       [268],
       [17]]
 
visited=[False]*9
 
dfs(graph, 1, visited) #1 2 7 6 8 3 4 5
cs

 

 

5. BFS

Breadth-Frist-Search 너비우선 탐색이다

가장 가까운 노드부터 탐색한다. 큐를 사용한다

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from collections import deque
 
def bfs(graph, start, visited):
    queue=deque([start])
    visited[start]=True
 
    while queue:
        v=queue.popleft()
        print(v, end=' ')
 
        #해당 원소와 연결된, 아직 방문하지 않은 원소들을 큐에 삽입
        for i in graph[v]:
            if visited[i]==False:
                queue.append(i)
                visited[i]=True
 
graph=[[], #0번 노드 없음
       [238],
       [17],
       [145],
       [35],
       [43],
       [7],
       [268],
       [17]]
 
visited=[False]*9
 
bfs(graph, 1, visited) #1 2 3 8 7 4 5 6 
 
cs

 

 

코딩테스트에서 2차원 배열에서의 탐색 문제를 만나면 그래프 형태로 바꿔서 생각해보자

 

 

 

① 음료수 얼려 먹기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import sys
input=sys.stdin.readline
 
n,m = map(int,input().split())
graph=[]
for _ in range(n):
    graph.append(list(map(int, input().rstrip())))
 
def dfs(a, b):
    #종료 조건 지정 잘 해주기
    # 범위 넘어가면 종료
    if a<0 or a>n-1 or b<0 or b>m-1:
        return False
    # 칸막이거나 방문했으면 종료
    if graph[a][b]==1:
        return False
 
    graph[a][b]=1
 
    dfs(a, b+1#동
    dfs(a, b-1#서
    dfs(a+1, b) #남
    dfs(a-1, b) #북
 
    return True
 
 
result=0
for i in range(n):
    for j in range(m):
        if dfs(i, j):
            result+=1
 
print(result)
cs

 

입력 예시 출력 예시
4 5
00110
00011
11111
00000
3
15 14
00000111100000
11111101111110
11011101101110
11011101100000
11011111111111
11011111111100
11000000011111
01111111111111
00000000011111
01111111111000
00011111111000
11111111110011
11100011111111
11100011111111
8

처음에 띄어쓰기 없이 입력했는데 어떻게 배열로 입력됐는지 궁금했는데 문자열 형식이 iterable한 형식이라 

map을 이용해 문자 하나하나 int로 변경하는게 가능한 거였다.

재귀호출은 좀 더 공부해야 적용 가능할 듯 하다

 

 

② 미로 탈출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import sys
from collections import deque
input=sys.stdin.readline
 
# 행, 열
n, m=map(int, input().split())
graph=[]
for _ in range(n):
    graph.append(list(map(int, input().rstrip())))
 
# 0무조건 피하고, 최대한 오른쪽으로, 최대한 아래로
def bfs(a, b):
    queue=deque()
    queue.append((a, b))
    #동서남북
    dc=[001-1]
    dd=[1-100]
 
    while queue:
        c, d=queue.popleft()
 
        for i in range(4):
            #탐색하고자 하는 인덱스
            nc, nd=c+dc[i], d+dd[i]
 
            if nc>n-1 or nc<0 or nd>m-1 or nd<0:
                continue
 
            if graph[nc][nd]==1:
                graph[nc][nd]=graph[c][d]+1
                queue.append((nc, nd))
 
    # 도착
    return graph[-1][-1]
 
 
# bfs 호출
print(bfs(00))
print(graph)
cs

 

입력 예시 출력 예시
5 6
101010
111111
000001
111111
111111
10

 

 

 

 

출처 : 이것이 취업을 위한 코딩 테스트다 with 파이썬 (나동빈 저)

728x90

'알고리즘 > 이것이취업을위한코딩테스트다' 카테고리의 다른 글

chapter 6. 정렬  (0) 2021.02.15
DFS/BFS 문제  (0) 2021.02.15
그리디, 구현 문제  (0) 2021.02.13
chapter 4. 구현  (0) 2021.02.13
chapter 3. 그리디  (0) 2021.02.09
Comments