Programming/알고리즘 & 코테

[DFS/BFS] 알고리즘 개념

kk_______yy 2023. 12. 18. 15:01

DFS (깊이 우선 탐색 알고리즘)

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

 

* 그래프

  • 노드(Node)(=정점) 와 간선(Edge) 으로 이루어져 있음
  • 그래프 탐색 : 하나의 노드를 시작으로 다수의 노드를 방문하는 것
  • '두 노드는 인접하다' : 두 노드가 간선으로 연결되어 있음

 

그래프의 표현 방식

인접 행렬(Adjacency Matrix)

: 2차원 배열에 각 노드가 연결된 형태를 기록하는 방식

  • 파이썬에서는 2차원 리스트로 구현
  • 연결되지 않은 노드끼리는 무한(Inf) 비용으로 작성
  • 차이점 : 모든 관계를 저장, 노드 개수 많을수록 메모리 불필요하게 낭비, 노드 연결  정보 얻는 속도 빠름

ex) 인접 행렬 방식 예제

INF = 999999999    # 무한의 비용 선언

# 2차원 리스트를 이용해 인접 행렬 표현
graph = [
    [0, 7, 5],
    [7, 0, INF],
    [5, INF, 0]
]

print(graph)
# [[0, 7, 5], [7, 0, INF], [5, INF, 0]]

 

 

인접 리스트(Adjacency List)

: 리스트로 그래프의 연결 관계를 표현하는 방식

  • '연결 리스트' 자료구조 이용, 파이썬은 기본 자료형이 append()와 메소드 제공
  • 차이점 : 연결된 정보만을 저장하므로 메모리 효율적 사용, 노드 연결 정보 얻는 속도 느림

ex) 인접 리스트 방식 예제

# 행(Row)이 3개인 2차원 리스트로 인저 리스트 표현
graph = [[] for _ in range(3)]    # [[], [], []] 형태

# 노드 0에 연결된 노드 정보 저장(노드, 거리)
graph[0].append((1, 7))
graph[0].append((2, 5))

# 노드 1에 연결된 노드 정보 저장(노드, 거리)
graph[1].append((0, 7))

# 노드 2에 연결된 노드 정보 저장(노드, 거리)
graph[2].append((0, 5))

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

 

동작 과정

최대한 깊숙이 들어가서 노드를 방문한 후, 다시 돌아가 다른 경로로 탐색

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

* 방문 처리 : 스택에 한 번 삽입되어 처리된 노드가 다시 삽입되지 않게 체크. 각 노드 한 번씩만!

* 일반적으로 방문하지 않은 노드 중 번호 낮은 순서부터 처리

 

  • DFS는 스택 자료구조에 기초하므로 구현이 간단
  • N개 데이터에 대해 O(N)의 시간 소요
  • 실제 구현은 재귀 함수 이용하면 매우 간결
# DFS 예제

# DFS 메서드 정의
# 변수 : graph 각 노드 정보(아래 정의), v 현재 노드(함수 호출시)d 방문 정보(아래 정의)
def dfs(graph, v, visited):
    # 현재 노드를 방문 처리
    visited[v] = True
    print(v, end=' ')
    # 현재 노드와 연결된 다른 노드를 재귀적으로 방문
    for i in graph[v]:
        if not visited[i]:    # 현 v노드와 연결된 것 중, 방문하지 않은 노드 탐색(반복)
            dfs(graph, i, visited)
            
# 각 노드가 연결된 정보를 리스트 자료형으로 표현(2차원 리스트)
# 인덱스 번호(9행) == 노드 번호
graph = [
    [],
    [2, 3, 8],
    [1, 7],
    [1, 4, 5],
    [3, 5],
    [3, 4],
    [7],
    [2, 6, 8],
    [1, 7]
]

# 9개 노드의 방문 정보를 리스트 자료형으로 표현(1차원 리스트)
visited = [False] * 9

# 정의된 DFS 함수 호출
dfs(graph, 1, visited)

# 1 2 7 6 8 3 4 5

 

 

BFS (너비 우선 탐색 알고리즘)

: 너비 우선 탐색, 가까운 노드부터 탐색하는 알고리즘

 

* 큐 자료구조 이용

  • 인접한 노드를 반복적으로 큐에 넣도록 알고리즘 작성
  • 자연스럽게 먼저 들어온 것이 먼저 나가게 됨
  • 가까운 노드부터 탐색 가능

 

동작 과정

  1. 탐색 시작 노드를 에 삽입하고 방문 처리
  2. 큐에서 노드를 꺼내, 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리
  3. 2의 과정을 더 이상 수행할 수 없을 때까지 반복
  • BFS는 큐 자료구조에 기초하므로 구현이 간단
  • deque 라이브러리를 사용하는 것이 좋음
  • N개 데이터에 대해 O(N)의 시간 소요

 

* 코테에서는 보통 BFS(깊이 우선)이 DFS(너비 우선)보다 조금 더 빠르다

 

# DFS 예제

from collections import deque

# BFS 메서드 정의
# 변수 : graph 각 노드 정보(아래 정의), start 시작 노드(함수 호출시), d 방문 정보(아래 정의),
#        queue 넣었다뺐다 큐, v 막 방문처리한(큐에서 막 꺼낸) 노드
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차원 리스트)
# 인덱스 번호(9행) == 노드 번호
graph = [
    [],
    [2, 3, 8],
    [1, 7],
    [1, 4, 5],
    [3, 5],
    [3, 4],
    [7],
    [2, 6, 8],
    [1, 7]
]

# 9개 노드의 방문 정보를 리스트 자료형으로 표현(1차원 리스트)
visited = [False] * 9

# 정의된 BFS 함수 호출
bfs(graph, 1, visited)

# 1 2 3 8 7 4 5 6

 

 

DFS  vs DFS

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

 

배열 ↔ 그래프

ex)

3 x 3 형태의 2차원 배열, 각 데이터 좌표라고 하자.

좌표의 상하좌우로만 이동할 수 있다면, 좌표의 형태를 다음의 그래프 형태로 바꿔 생각할 수 있다.

 

* 코테의 2차원 배열에서 탐색 문제그래프 형태로 바꿔 생각하면 풀기 쉽다.