본문 바로가기
Algorithm_PYTHON

[알고리즘] CH05 탐색 알고리즘 DFS, BFS

by 코리니덕 2021. 8. 22.

※ 모든 경로 탐색시엔 DFS/BFS 둘 다 사용 가능

BUT, 최단 거리 문제는 BFS가 더 효과적

단, 모든 간선의 비용이 동일할 때만!

만약, 간선의 비용이 다르다면 다익스트라 사용

 

그래프

: 노드(=정점: Vertex)와 간선(Edge)으로 표현

 

그래프 탐색

: 하나의 노드를 시작으로 다수의 노드를 방문하는 것

- 두 노드가 간선으로 연결되어 있다 => '두 노드는 인접하다'고 표현

- 노드는 도시, 간선은 도시들을 연결하는 도로라고 생각

 

그래프의 2가지 표현방법

1. 인접 행렬(Adjacency Metrix)

: 2차원 배열로 그래프의 연결관계를 표현하는 방식

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

- 연결된 그래프를 인접 행렬로 표현할 때, 2차원 리스트로 구현 가능

- 연결 되어 있지 않은 노드끼리는 무한(Infinity)의 비용으로 작성

  0 1 2
0 0 7 5
1 7 0 INF
2 5 INF 0
INF = 999999999 # 무한비용 선언

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

print(graph)

 

2. 인접 리스트(Adjacency List)

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

- 모든 노드에 연결된 노드에 대한 정보를 차례대로 연결하여 저장

- '연결 리스트'라는 자료구조를 이용해 구현

- 기본 자료형인 리스트 자료형이 append()와 메소드를 제공

- 결국, 2차원 리스트를 이용하면 됨

(- 자바에서는 연결 리스트 기능을 위한 표준 라이브러리 제공)

0 1 2
1 0 -
2 0 -
# 행이 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)

 

인접 행렬 방식과 인접 리스트 방식의 차이

- 인접 행렬 방식 : 모든 관계 저장하므로 노드개수가 많을수록 메모리가 불필요하게 낭비

- 인접 리스트 방식 : 연결된 정보만 저장하므로 메모리의 효율적 사용 BUT. 인접 행렬 방식에 비해 특정 두 노드가 연결되어 있는지에 대한 정보를 얻는 속도가 느림(연결된 데이터를 하나씩 확인해야 하기 때문)

- 따라서, 특정 노드와 연결된 모든 인접 노드를 순회하는 경우, 인접리스트 방식이 메모리 공간의 낭비가 적고, 모든 관계를 알 필요가 있을 땐, 인접 행렬 방식 사용

 

 

DFS(Depth-First Search) = 깊이 우선 탐색

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

- 스택 자료구조 or 재귀함수 사용

- 실제로 스택을 쓰지 않아도 됨

- 데이터의 개수가 N개인 경우 O(N)시간 소요

- 재귀함수 사용시 더욱 간단

 

DFS 동작 과정

1. 탐색 시작 노드를 스택에 삽입하고 방문 처리

2. 스택의 최상단 노드에 방문하지 않은 인접 노드가 있으면 그 인접 노드를 스택에 넣고 방문 처리(번호 낮은순이 관례)

3. 방문하지 않은 인접노드가 없으면 스택에서 최상단 노드를 꺼낸다.

4. 2-3번의 과정을 더 이상 수행할 수 없을 때까지 반복한다.

# 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=[
    [], # 시작 노드가 1부터 시작할 때, 0번째 인덱스는 비움
    [2,3,8], # 1번 노드는 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)

 

BFS(Breath-First Search) = 너비 우선 탐색

: 가까운 노드부터 탐색하는 알고리즘

- 큐 자료구조 이용

- 인접한 노드를 반복적으로 큐에 넣도록 하면 선입선출 됨

- 특정 상황에 대해 최단 거리를 찾는 알고리즘으로 사용(가까운 노드부터 탐색 진행)

- 탐색 시, O(N)의 시간 소요

- 일반적인 경우, 실제 수행 시간은 DFS보다 좋은 편(따라서 코테에선 주로 BFS를 사용하는 편이 좋음)

BFS 동작과정

1. 탐색 시작 노드를 큐에 삽입하고 방문처리

2. 큐에서 노드를 꺼내 해당 노드의 인접 노드 중에서 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리

3. 2번의 과정을 더이상 수행할 수 없을 때까지 반복

from collections import deque

# BFS메서드 정의
def bfs(graph, start, visited):
    # 큐 구현을 위해 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=[
    [], # 시작 노드가 1부터 시작할 때, 0번째 인덱스는 비움
    [2,3,8], # 1번 노드는 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)

 

 

 TIP

- 코테 중 2차원 배열에서 탐색 문제를 만나면 그래프 형태로 바꿔서 생각해 풀어보기

- 코테에서 탐색 문제를 보면 그래프 형태로 표현한 다음 풀이법을 고민하기