CS/자료구조와 알고리즘

DFS, BFS

조 수빈 2022. 7. 29. 22:35

🔖 자료구조 기초

탐색이란 많은 양의 데이터 중에서 원하는 데이터를 찾는 과정
자료구조란 '데이터를 표현하고 관리하고 처리하기 위한 구조'

특히 스택과 큐는 자료구조의 기초 개념이며 다음의 두 핵심적인 함수로 구성됨
  - 삽입(Push): 데이터를 삽입함
  - 삭제(Pop): 데이터를 삭제함

이 외에 오버플로와 언더플로도 고민해야 함


☁ 스택

스택은 박스 쌓기에 비유할 수 있음
아래에서부터 위로 차곡차곡 쌓고, 아래의 박스를 치우기 위해선 위의 박스를 먼저 내려야 함
선입후출 구조, 후입선출 구조라고 함

파이썬에서 스택을 이용할 때에는 별도의 라이브러리를 사용할 필요 없음
기본 리스트에서 append()pop() 메서드를 이용하면 됨
append() 메서드는 리스트의 가장 뒤쪽에 데이터 삽입, pop() 메서드는 리스트의 가장 뒤쪽에서 데이터를 꺼냄

☁ 큐

큐는 대기 줄에 비유할 수 있음
나중에 온 사람일수록 나중에 들어감, '공정한' 자료구조, 선입선출 구조라고 함

파이썬으로 큐를 구현할 때는 collections 모듈에서 제공하는 deque 자료구조를 활용하자
deque는 스택과 큐의 장점을 모두 채택한 것 (데이터를 넣고 빼는 속도가 리스트 자료형에 비해 효율적, queue 라이브러리를 이용하는 것보다 더 간단)

참고: deque 객체를 리스트 자료형으로 변경하고자 한다면 list() 메서드를 이용하자

☁ 재귀 함수

DBS와 BFS를 구현하려면 재귀 함수도 이해하고 있어야 함
재귀 함수자기 자신을 다시 호출하는 함수를 의미함

재귀함수를 문제 풀이에서 사용할 때는 재귀 함수가 언제 끝날지, 종료 조건을 꼭 명시해야 함
(함수가 무한 호출될 수 있기 때문에)

재귀 함수는 내부적으로 스택 자료구조와 동일
  => 스택 자료구조를 활용해야 하는 상당수 알고리즘은 재귀 함수를 이용해 간편하게 구현될 수 있음 (ex DFS)

재귀 함수를 이용하는 대표적 예제로는 팩토리얼 문제
재귀 함수는 수학의 점화식(재귀식)을 그대로 소스코드로 옮겼기에 반복문을 이용하는 것과 비교 했을 때 더욱 간결한 형태임


🔎 그래프의 기본 구조

그래프는 노드(Node)간선(Edge)으로 표현되며 이때 노드를 정점(Vertex)이라고도 함

그래프 탐색이란 하나의 노드를 시작으로 다수의 노드를 방문하는 것
두 노드가 간선으로 연결되어 있다면 '두 노드는 인접하다'라고 표현함

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

두 방식의 차이
  - 메모리 측면
    ○ 인접 행렬 방식: 모든 관계를 저장하기에 노드 개수 많을수록 메모리가 불필요하게 낭비됨
    ■ 인접 리스트 방식: 연결된 정보만을 저장하기에 메모리를 효율적으로 사용함
  - 속도 측면
    ○ 인접 행렬 방식: 특정한 두 노드가 연결되어 있는지에 대한 정보를 얻는 속도 빠름
    ■ 인접 리스트 방식: 연결된 데이터를 하나씩 확인해야 하기 때문에 느림


인접 행렬

인접 행렬 방식은 2차원 배열에 각 노드가 연결된 형태를 기록하는 방식
파이썬에서는 2차원 리스트로 구현할 수 있음
연결이 되어 있지 않은 노드끼리는 무한의 비용이라고 작성함

예제)
image

INF = 999999999 # 무한의 비용 선언

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

print(graph)

# 결과
# [[0, 7, 5], [7, 0, 999999999], [5, 999999999, 0]]

인접 리스트

인접 리스트 방식은 모든 노드에 연결된 노드에 대한 정보를 차례대로 연결하여 저장함
파이썬으로 인접 리스트를 이용해 그래프를 표현하고자 할 때에도 단순히 2차원 리스트를 이용하면 됨

예제)
image

# 행(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)]]

🔎 DFS

DFS는 Depth-First Search, 깊이 우선 탐색이라고도 부르며 그래프에서 깊은 부분을 우선적으로 탐색하는 알고리즘

DFS는 스택 자료구조를 이용하며 구체적인 동작 과정은 아래와 같음
  1. 탐색 시작 노드를 스택에 삽입하고 방문 처리를 함
  2. 스택의 최상단 노드에 방문하지 않은 인접 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리함
      방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드를 꺼냄
  3. 2번의 과정을 더 이상 수행할 수 없을 때까지 반복함

# 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, 8],
  [1, 7],
  [1, 4, 5],
  [3, 5],
  [3, 4],
  [7],
  [2, 6, 8],
  [1, 7]
]

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

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

# 결과: 1 2 7 6 8 3 4 5

🔎 BFS

BFS는 Breadth-First Search, 너비 우선 탐색이라고도 부르며 쉽게 말해 가까운 노드부터 탐색하는 알고리즘

BFS 구현에서는 선입선출 방식인 큐 자료구조를 이용하는 것이 정석
DFS는 최대한 멀리 있는 노드를 우선으로 탐색하는 방식인 반면, BFS는 그 반대
일반적인 경우 BFS의 실제 수행 시간은 DFS보다 좋은 편

BFS는 큐 자료구조를 이용하며 구체적인 동작 과정은 아래와 같음
  1. 탐색 시작 노드를 큐에 삽입하고 방문 처리를 함
  2. 큐에서 노드를 꺼내 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리함
  3. 2번의 과정을 더 이상 수행할 수 없을 때까지 반복함

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, 8],
  [1, 7],
  [1, 4, 5],
  [3, 5],
  [3, 4],
  [7],
  [2, 6, 8],
  [1, 7]
]

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

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

📝 정리

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

그래프 탐색문제는 DFS, BFS 활용하되,
경로를 찾는 문제(특징, 조건등이 있는 경로)는 DFS, 최단거리 문제는 BFS를 활용해보자!


참고
📒 이것이 코딩 테스트다

'CS > 자료구조와 알고리즘' 카테고리의 다른 글

트리 순회  (0) 2022.12.10
트리  (0) 2022.12.08
그리디  (0) 2022.10.23