[알고리즘] DFS, BFS 이론 및 구현

Bini by Bini·2023년 2월 2일
0

알고리즘

목록 보기
1/4
post-thumbnail

들어가기 전에 ..

그래프는 노드(Node)간선(Edge)으로 표현된다.
이때 노드 = 정점(Vertex).

두 노드가 간선으로 연결되어 있다 = 두 노드가 인접하다.(Adjacent)

알고리즘을 구현할 때 영어로 변수명을 많이 표현하므로 영어단어에 익숙해지는 것이 좋다.

그래프는 2가지 방식으로 표현이 가능하다.

  1. 인접행렬(Adjacent Matrix)
    2차원 배열로 그래프의 연결 관계를 표현하는 방식

    -> 노드끼리 연결되어 있을 경우 1(또는 거리(비용)) / 연결되어 있지 않은 경우 무한(Infinity)의 비용
  2. 인접리스트(Adjacent List)
    리스트로 그래프의 연결 관계를 표현하는 방식

    -> 인접리스트에서는 모든 노드에 연결된 노드에 대한 정보를 차례대로 연결하여 저장
    	✔ 파이썬에서는 리스트를 통해 구현한다.

그래서 정리하면 ?

메모리 측면과 속도 측면에서 1. 인접행렬 방식과 2. 인접리스트 방식을 비교하면 ..

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

속도 측면에서는 메모리 측면에서 분석한 특징으로 인접 리스트 방식은 인접 행렬 방식에 비해 특정 두 노드가 연결되어 있는지에 대한 정보를 얻는 속도가 느리다고 할 수 있다.

-> 한 그래프에서 노드 1과 노드 7이 연결되어 있는지 확인이 필요한 경우
	인접 행렬 방식 : graph[1][7] 확인
    인접 리스트 방식 : 노드 1에 대한 인접 리스트를 앞에서부터 확인

DFS

Depth First Search, 깊이 우선 탐색
그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘
스택 자료구조를 이용하여 동작한다.
데이터의 개수가 N개인 경우 O(N)의 시간이 소요된다.
재귀함수를 통해 구현한다.

동작과정은 아래와 같다.

1️⃣ 탐색 시작 노드를 스택에 삽입하고 방문 처리
2️⃣ "스택의 최상단 노드"에 방문하지 않은 인접 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리, "스택의 최상단 노드"에 방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드를 꺼냄
3️⃣ 2번 과정을 더 이상 수행할 수 없을 때까지 반복

❗ 그래프 탐색은 코딩 테스트에서 종종 번호가 낮은 순서부터 처리하도록 명시하는 경우가 있어 관행적으로 번호가 낮은 순서부터 처리하도록 구현한다.

BFS

Breath First Search, 너비 우선 탐색
그래프에서 가까운 노드부터 탐색하는 알고리즘
큐 자료구조(선입선출)를 이용하여 동작한다. (deque 라이브러리 이용)
데이터의 개수가 N개인 경우 O(N)의 시간이 소요된다.

일반적인 경우 실제 수행시간이 DFS보다 좋은 편이다 .. 라고 알아두자!

동작과정은 아래와 같다.

1️⃣ 탐색 시작 노드를 큐에 삽입하고 방문 처리
2️⃣ 큐에서 노드를 꺼내 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리
3️⃣ 2번 과정을 더 이상 수행할 수 없을 때까지 반복

구현

아래 그래프에 대해 DFS, BFS를 구현해보자.


from collections import deque

# dfs - 재귀로 구현 (스택 원리)
def dfs(graph, v, visited_dfs):
  visited_dfs[v] = 1 # 방문처리
  print(v, end=" ")
  for i in graph[v]: # 현재 노드와 연결된 다른 노드 확인
    if not visited_dfs[i]: # 그 노드가 아직 방문되지 않았다면
      dfs(graph, i, visited_dfs) # 재귀호출을 통해 방문 (더 깊게)

# bfs - 큐로 구현 (큐 원리)
def bfs(graph, start, visited_bfs):
  queue = deque([start]) # 큐 구현을 위해 dequeue 라이브러리 이용
  visited_bfs[start] = 1 # 시작노드 방문처리
  while queue: # 큐가 빌때까지
    v = queue.popleft() # 큐에서 가장 먼저 들어간 노드 꺼내고
    print(v, end=" ")
    for i in graph[v]: # 현재 꺼내진 노드와 연결된 다른 노드 확인
      if not visited_bfs[i]: # 그 노드가 아직 방문되지 않았다면
        queue.append(i) # 큐에 삽입
        visited_bfs[i] = 1 # 삽입 후 방문처리
    

# 노드 0 ~ 8까지
graph = [
  [],
  [2, 3, 8],
  [1, 7],
  [1, 4, 5],
  [3, 5],
  [3, 4],
  [7],
  [2, 6, 8],
  [1, 7]
]
visited_dfs = [0] * (8+1)
visited_bfs = [0] * (8+1)


dfs(graph, 1, visited_dfs)
print()
bfs(graph, 1, visited_bfs)

정리

동작원리 / 구현방법
DFS 스택 / 재귀함수
BFS 큐 / 큐 자료구조

profile
My Precious Records

0개의 댓글