포스트

DFS, BFS, 인접행렬, 인접리스트

#깊이우선탐색(DFS) #너비우선탐색(BFS) #인접행렬 #인접리스트

  • 인접 행렬(Adjacency Matrix): 2차원 배열로 그래프의 연결 관계를 표현하는 방식
  • 인접 리스트(Adjacency List): 리스트로 그래프의 연결 관계를 표현하는 방식

인접행렬

 012
0075
170무한
25무한0
1
2
3
4
5
6
7
8
9
10
11
12
# Python code
# 인접행렬

INF = 99999999

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

pring(graph) # -> [[0, 7, 5], [7, 0, 99999999], [5, 99999999, 0]]

인접 리스트

파이썬에서 인접 리스트를 이용해 그래프를 표현할 때는 단순히 2차원 리스트를 이용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
# Python code

graph = [[] for _ in range(3)]

graph[0].append((1,7))
graph[0].append((2,5))

graph[1].append((0,7))

graph[2].append((0,5))

print(graph) # -> [[(1,7), (2,5)], [(0,7)], [(0,5)]]

인접행렬 vs. 인접리스트

**메모리 측면: **인접행렬 ** 인접행렬 방식은 모든 관계를 저장하므로 노드 개수가 많을수록 메모리가 불필요하게 낭비됨. 반면에 인접 리스트 방식은 연결된 정보만을 저장하기 때문에 메모리가 효율적으로 사용됨.

**속도 측면: 인접행렬 > **인접리스트 특정한 두 노드가 연결되어 있는지에 대한 정보를 얻기 위해 인접리스트 방식에서는 연결된 데이터를 하나씩 확인해야 하므로 더 느림. 반면에 인접행렬 방식은 바로 접근이 가능함.


DFS, BFS


DFS(Depth-First Search)는 '깊이 우선 탐색'이라고도 부르며, 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘이다. DFS는 스택 자료구조에 기초하지만 실제로는 스택을 쓰지 않아도 구현이 된다. 바로 재귀 함수를 이용했을 때 매우 간결하게 구현할 수 있다. 그리고 DFS는 데이터의 개수가 N개인 경우 O(N)의 시간이 소요된다.

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
# Python code
# DFS 메서드 정의
def dfs(graph, v, visited):
  #현재 노드를 방문 처리
  visited[v] = True
  print(v, end = ' ')
  #현재 노드와 연결된 다른 노드를 재귀적으로 방문
  for i in graph[v]:
    if not visited[i]:
      dfs(graph, i, visited)

#각 노드가 연결된 정보를 인접리스트 자료형으로 표현(2차원 리스트)
graph = [
    [],
    [2, 3, 4],
    [1, 7],
    [1, 9],
    [1, 5, 6],
    [4, 6],
    [4, 5],
    [2, 8],
    [7, 9, 10],
    [3, 8, 11],
    [8, 11],
    [9, 10]
]

#각 노드가 방문된 정보를 (인접)리스트 자료형으로 표현(1차원 리스트). 0 ~ 11
visited = [False] *  12

# 정의된 DFS 함수 호출
dfs(graph, 1, visited) # -> 1 2 7 8 9 3 11 10 4 5 6 

일반적으로 인접한 노드 중에서 방문하지 않은 노드가 여러 개 있으면 번호가 낮은 순서부터 처리한다. 기능상 차이는 없으나 코딩테스트에서는 관행적으로 낮은 번호 순으로 처리하는 편이라고 한다.


BFS(Breadth-First Search)는 '너비 우선 탐색'이라고도 부르며, 가까운 노드부터 우선적으로 탐색하는 알고리즘이다. DFS가 최대한 멀리 있는 노드를 우선으로 탐색하는 것과는 반대이다. 보이는 가까운 것부터 청소하는 방식이 연상되는 것 같다.

BFS 구현은 선입선출 방식인 큐 자료구조를 이용하는 것이 정석이다. 또한 deque라이브러리를 사용하는 것이 좋으며 O(N)의 시간이 소요되고, 일반적인 경우 실제 수행 시간은 DFS보다 좋은 편이라고 한다.

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
40
41
42
# Python code

from collections import deque

# BFS 메서드 정의
def bfs(graph, start, visited):
  # 큐(Queue) 구현을 위해 deque 라이브러리 사용
  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

# 각 노드가 연결된 정보를 인접리스트 자료형으로 표현(2차원 리스트)
graph = [
    [],
    [2, 3, 4],
    [1, 7],
    [1, 9],
    [1, 5, 6],
    [4, 6],
    [4, 5],
    [2, 8],
    [7, 9, 10],
    [3, 8, 11],
    [8, 11],
    [9, 10]
]

# 각 노드가 방문된 정보를 리스트 자료형으로 표현(1차원 리스트). 0 ~ 11
visited = [False] * 12

# 정의된 BFS 함수 호출
bfs(graph, 1, visited) # -> 1 2 3 4 7 9 5 6 8 11 10 

참고)

그래프는 게임 맵과도 연관이 있다. 예를 들어 게임 맵이 4 x 4 형태의 2차원 배열이고 각 데이터를 좌표라고 생각해보자. 이 때 각 좌표를 상하좌우로만 이동할 수 있다면 다음과 같은 그래프의 형태로 바꿔서 생각할 수 있다.

(1, 1)(1, 2)(1, 3)(1, 4)
(2, 1)(2, 2)(2, 3)(2, 4)
(3, 1)(3, 2)(3, 3)(3, 4)
(4, 1)(4, 2)(4, 3)(4, 4)


이 글의 내용이 많은 도움이 된다면 아래 책을 사서 읽어보고 공부하는 것을 추천한다.

이것이 취업을 위한 코딩 테스트다 with 파이썬

1
2
3
4
5
6
                                            저자
                                            나동빈
                                            출판
                                            한빛미디어
                                            발매
                                            2020.08.05.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.