탐색이란 많은 양의 데이터 중에서 원하는 데이터를 찾는 과정이다.
그 중 DFS/BFS를 대표적인 알고리즘으로 꼽을 수 있는데 이를 이해하려면 스택, 큐, 재귀함수를 알아야한다.
스택: 후입선출
*파이썬의 append()와 pop()함수로 삽입, 삭제
큐: 선입선출
from collection import deque queue = deque() queue.append(5) queue.append(6) queue.popleft()
👉파이썬에서는 collection 모듈에서 제공하는 deque 자료구조를 활용하자.
재귀함수 자기 자신을 다시 호출하는 함수이다. 이때 재귀함수 없이 해결할 수 있는 정료 조건을 꼭 명시해야 한다. 무한 루프에 빠지지 않게 하기 위해서이다.
✨재귀함수는 내부적으로 스택 자료구조와 동일하다. 따라서 스택 자료구조를 활용해야하는 상당수 알고리즘은 재귀 함수를 이용해서 표현할 수 있다.
- 코드가 점화식을 그대로 표현하여 직관적이다.
- 무한루프, 스택오버플로우를 주의해야 한다.
프로그래밍에서 그래프는 인접 행렬, 인접 리스트 2가지 방식으로 표현할 수 있다.
🎉인접 행렬: 2차원 배열로 각 노드가 연결된 형태를 기록. 연결이 되어 있지 않은 노드끼리는 무한의 비용이라고 작성.
INF = 999999999 #무한의 비용 선언 graph = [ [0, 7, 5] [7, 0, INF] [5, INF, 0] ] print(graph)
🎉인접 리스트: 리스트로 그래프의 연결 관계를 표현.
- C++, JAVA의 경우 별도의 라이브러리를 제공하지만 Python에서는 기본 리스트 자료형이 append()와 메소드를 제공. 즉 단순히 2차원 리스트를 이용.
# 행이 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[1].append((0, 5)) print(graph)
메모리 측면에서 인접 행렬 방식은 모든 관계를 저장, 인접 리스트는 연결된 정보만 저장
👉 특정한 노드의 연결 유무: 인접 행렬이 더 빠름
👉 특정한 노드와 연결된 모든 인접 노드를 순회: 인접 리스트가 더 빠름
그래프에서 깊은 부분을 먼저 탐색하는 알고리즘
#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)
그래프에서 가까운 노드부터 탐색하는 알고리즘
from collections import deque
#BFS정의
def bfs(graph, start, visited):
#큐 구현을 위해 덱 라이브러리 사용
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
#정의된 DFS 함수 호출
bfs(graph, 1, visited)
n, m = map(int, input().split())
ice_graph = []
for _ in range(n):
ice_graph.append(map(int, input()))
def dfs(x, y):
#범위를 넘어가면 종료
if x <= -1 or x >= n or y <= -1 or y >= m:
return False
if ice_graph[x][y] == 0: #방문하지 않았다면
ice_graph[x][y] = 1 #방문 처리하고 상하좌우 재귀호출
dfs(x+1, y)
dfs(x-1, y)
dfs(x, y+1)
dfs(x, y-1)
return False #이미 1이라면 종료
cnt = 0
#dfs 반복문으로 실행
for i in range(n):
for j in range(m):
if dfs(n, m) == True:
cnt += 1
print(cnt)
모든 노드를 방문해야 하니 깊이 우선 탐색을 사용했다.
재귀 호출로 상하좌우를 탐색하고 모든 조건을 통과해 True를 리턴 받으면 cnt에 1을 더하여 마지막으로 총 cnt 값을 출력하는 코드이다.
from collections import deque
n, m = map(int, input().split())
graph = []
for _ in range(n):
graph.append(list(map(int, input())))
#4방향 정의
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
#BFS 소스코드
def bfs(x, y):
queue = deque()
queue.append((x, y)) #시작점 인큐
#큐가 빌 때까지 반복
while queue:
x, y = queue.popleft() #x, y에 각각 1 대입
for i in range(4): #4방향에 반복
nx = x + dx[i]
ny = y + dy[i]
if nx < 0 or ny > 0 or nx >= n or ny >= m: #범위 밖이면 무시
continue
if graph[x][y] == 0: #0이면 무시
continue
if graph[x][y] == 1:
graph[nx][ny] = graph[x][y] +1 #기존 x,y 좌표의 값에 1을 더한 값을 대입
queue.append((nx, ny)) #인큐
return graph[n-1][m-1] #반복문이 끝나면 n,m 좌표의 값을 리턴(인덱스는 0부터 시작이므로 -1)
print(bfs(0, 0))
이 문제는 최단 거리를 구해야 하므로 bfs 너비 우선 탐색을 사용했다.
다음 노드로 이동할 때 그 노드의 데이터 값을 +1하며 마지막 종점에 달했을 때 데이터 값을 리턴하면 이동한 횟수를 리턴하게 되는 것이다.