Recent Posts
Recent Comments
Archives
반응형
250x250
«   2024/05   »
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
Today
Yesterday

Total
05-04 00:00
관리 메뉴

Hey Tech

[Python] BFS 알고리즘 개념 및 실습 본문

알고리즘/이론

[Python] BFS 알고리즘 개념 및 실습

Tony Park 2021. 3. 9. 10:16
728x90
반응형

본 포스팅에서는 너비 우선 탐색이라고 불리는 BFS(Breadth-First Search)에 대해 알아봅니다.

📚 목차

1. BFS 알고리즘이란?
2. BFS 알고리즘 동작 과정
3. BFS 파이썬 구현
    3.1. 소스코드 설명
    3.2. 전체 소스코드  

1. BFS 알고리즘이란?

BFS(Breadth-First Search)너비 우선 탐색이라고도 불리며 그래프에서 시작 노드에 인접한 노드부터 탐색하는 알고리즘입니다. BFS 알고리즘은 언제 사용하면 좋을까요? BFS 알고리즘은 주로 그래프에서 모든 간선의 비용이 동일한 조건에서 최단 거리를 구하는 문제를 효과적으로 해결할 수 있는 알고리즘입니다. 그리고 "미로를 빠져나가는 최단 거리(경로)"를 구하는 문제 등에서 효과적으로 활용할 수 있는 알고리즘입니다.

2. BFS 동작 과정

BFS 알고리즘의 동작 과정을 이해하기 위해서는 먼저 그래프 자료구조와 큐 자료구조에 대해 이해할 필요가 있습니다.

 

[자료구조] 그래프 자료구조에 대해 알아보자!(노드, 간선, 루트 노드, 깊이, 높이, 차수)

안녕하세요, 오늘은 그래프(graph) 자료구조에 대해 알아보겠습니다. 그래프 자료구조의 구성 그래프는 그림 1 과 같이 노드(Node)와 간선(Edge)으로 표현됩니다. 이때 노드는 정점(Vertext)이라고도

heytech.tistory.com

 

자료구조#2: 큐(Queue)에 대해 알아보자(+ Python)

안녕하세요, 오늘은 *자료구조 두 번째 포스팅으로서 큐(Queue)에 대해 알아보겠습니다. *자료구조란 데이터를 표현, 관리, 처리하기 위한 구조를 말합니다. 그럼 바로 시작하죠! 목차 1. 큐(Queue)

heytech.tistory.com

BFS 알고리즘은 그래프에서 가까운 노드부터 우선적으로 탐색한다는 점에서, 선입선출 방식의 큐(Queue) 자료구조를 활용합니다. 즉, BFS는 인접한 노드를 반복적으로 큐에 삽입하고 먼저 삽입된 노드부터 차례로 큐에서 꺼내도록 알고리즘을 작성하면 됩니다. BFS 알고리즘의 구체적인 동작 과정은 아래와 같습니다.

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

여기서 *방문 처리란 탐색한 노드를 재방문하지 않도록 구분하는 것입니다. 즉, 큐에 한 번이라도 삽입된 노드를 다시 삽입하지 않도록 체크하는 것이죠.

 

이제 아래 그림 1 과 같은 그래프 예시를 통해 BFS 동작 과정을 알아보겠습니다. 노드 1을 시작 노드로 설정하겠습니다. 일반적으로 인접한 노드가 2개 이상인 경우에는 해당 노드들 중 번호가 낮은 노드부터 처리합니다.

그림 1. BFS 동작 설명을 위한 그래프 예시


편의상 현재 큐에서 꺼내 처리 중인 노드파란색으로, 방문 처리한 노드회색으로 표시하겠습니다.

(1) 시작 노드인 노드 1을 큐에 삽입하고 방문 처리합니다.

(2) 큐에서 노드 1을 꺼내고 노드 1과 인접한 노드 2, 3을 큐에 삽입하고 방문 처리합니다.

(3) 큐에서 노드 2를 꺼내고 노드 2와 인접한 노드 8을 큐에 삽입하고 방문 처리합니다.

(4) 큐에서 노드 3을 꺼내고 노드 3과 인접한 노드 4, 5를 큐에 삽입하고 방문 처리합니다.

(5) 큐에서 노드 8을 꺼내고 노드 8과 인접한 노드 6, 7을 큐에 삽입하고 방문 처리합니다.

(6) 그래프 내 노드에서 방문하지 않은 노드가 없으므로 큐에서 모든 노드를 차례대로 꺼냅니다.

결과적으로 노드의 탐색 순서, 즉 큐에 삽입한 순서는 다음과 같습니다.
1 -> 2 -> 3 -> 8 -> 4 -> 5 -> 6 -> 7

3. BFS 파이썬 구현

이제 앞서 살펴본 예시 그래프를 기반으로 BFS 알고리즘을 통해 탐색하는 과정을 파이썬을 통해 실제로 구현해 보겠습니다. BFS 알고리즘은 큐 자료구조에 기초하기 때문에 deque 라이브러리를 활용하여 구현하며 시간 복잡도는 O(N) 의 시간이 소요됩니다. 참고로 일반적인 경우에 실제 수행 시간은 DFS 보다 BFS가 좋은 편입니다.

3.1. 소스코드 설명

BFS 예제 구현 코드를 차례로 설명해 드립니다. 전체 소스코드를 보고싶으신 분들은 이어지는 섹션 "3.2. 전체 소스코드"을 참고해 주세요!

# deque 라이브러리 불러오기
from collections import deque

먼저 collections 모듈에서 deque 라이브러리를 불러옵니다.

# BFS 메서드 정의
def bfs (graph, node, visited):
    # 큐 구현을 위한 deque 라이브러리 활용
    queue = deque([node])
    # 현재 노드를 방문 처리
    visited[node] = True
    
    # 큐가 완전히 빌 때까지 반복
    while queue:
        # 큐에 삽입된 순서대로 노드 하나 꺼내기
        v = queue.popleft()
        # 탐색 순서 출력
        print(v, end = ' ')
        # 현재 처리 중인 노드에서 방문하지 않은 인접 노드를 모두 큐에 삽입
        for i in graph[v]:
            if not (visited[i]):
                queue.append(i)
                visited[i] = True

위와 같이 BFS 메서드를 정의해 줍니다.

graph = [
    [],
    [2, 3],
    [1, 8],
    [1, 4, 5],
    [3, 5],
    [3, 4],
    [7, 8],
    [6, 8],
    [2, 6, 7]
]

노드 간의 연결 정보는 위와 같이 2차원 배열을 통해 표현할 수 있습니다. 즉, 리스트 내 인덱스는 노드 번호를 의미하고 각 인덱스에 해당하는 원소에 해당 노드에 인접한 노드 번호가 담겨 있습니다.

# 노드별로 방문 정보를 리스트로 표현
visited = [False] * 9

잠깐, 왜 실제 그래프 내 노드 개수보다 2차원 배열에 원소 개수는 1개 더 많나요?

노드 번호가 1부터 시작한다는 점에서, 리스트 내 원소의 인덱스와 노드 번호를 일치시키기 위해, 인덱스 0에 빈 리스트를 넣어줌으로써 기존 그래프 내 노드 개수보다 방문 정보를 담은 리스트 내 원소 개수를 1개 더 많게 세팅하였습니다. 이처럼 인덱스와 노드 번호를 일치시켜 줌으로써 보다 직관적으로 노드 간의 연결 및 방문 정보를 파악할 수 있도록 세팅하였습니다.

# 정의한 BFS 메서드 호출(노드 1을 탐색 시작 노드로 설정)
bfs(graph, 1, visited)

정의한 BFS 메서드를 호출해 주면 아래와 같이 노드의 탐색 순서를 확인하실 수 있습니다.

1 2 3 8 4 5 6 7

3.2. 전체 소스코드

# deque 라이브러리 불러오기
from collections import deque

# BFS 메서드 정의
def bfs (graph, node, visited):
    # 큐 구현을 위한 deque 라이브러리 활용
    queue = deque([node])
    # 현재 노드를 방문 처리
    visited[node] = True
    
    # 큐가 완전히 빌 때까지 반복
    while queue:
        # 큐에 삽입된 순서대로 노드 하나 꺼내기
        v = queue.popleft()
        # 탐색 순서 출력
        print(v, end = ' ')
        # 현재 처리 중인 노드에서 방문하지 않은 인접 노드를 모두 큐에 삽입
        for i in graph[v]:
            if not (visited[i]):
                queue.append(i)
                visited[i] = True
                
graph = [
    [],
    [2, 3],
    [1, 8],
    [1, 4, 5],
    [3, 5],
    [3, 4],
    [7, 8],
    [6, 8],
    [2, 6, 7]
]

# 노드별로 방문 정보를 리스트로 표현
visited = [False] * 9

# 정의한 BFS 메서드 호출(노드 1을 탐색 시작 노드로 설정)
bfs(graph, 1, visited)

실행결과

1 2 3 8 4 5 6 7

포스팅 내용에 오류가 있을 경우 댓글 남겨주시면 감사드리겠습니다.
그럼 오늘도 즐거운 하루 보내시길 바랍니다 :)
고맙습니다😊

728x90
반응형
Comments