다익스트라 알고리즘 같은 경우에는 하나의 정점에서 출발해서 모든 정점으로의 최단거리를 구할 수 있는 반면에,
플로이드-와샬 알고리즘은 모든 정점에서 모든 정점으로의 최단거리를 구할 수 있다.
이 알고리즘의 핵심은 거쳐가는 정점을 기준으로 최단거리를 구한다는 점이다.
다익스트라에 비해서 구현 난이도가 훨씬 쉽다! 와!! 삼중 반복문!!!
아래 코드는 파이썬으로 구현한 플로이드-와샬 알고리즘 예시이다.
백준 11404번 플로이드)
import sys
INF = sys.maxsize
input = sys.stdin.readline
n, m = int(input()), int(input())
dis = [[INF]*n for _ in range(n)]
for _ in range(m):
a, b, c = map(int, input().split())
if c < dis[a-1][b-1]:
dis[a-1][b-1] = c
for k in range(n):
for a in range(n):
for b in range(n):
if a != b:
dis[a][b] = min(dis[a][b], dis[a][k]+dis[k][b])
for i in range(n):
for j in range(n):
if dis[i][j] == INF:
dis[i][j] = 0
for t in dis:
print(*t)
다익스트라 알고리즘은 음의 가중치가 없는 그래프의 한 정점에서 모든 정점까지의 최단거리를 구하는 알고리즘이다.
간선에 가중치가 없다면 너비 우선 탐색(BFS)으로도 충분히 풀 수 있지만, 간선에 가중치가 있다면 풀기가 매우
힘들어진다. 간선에 가중치가 있다면 다익스트라 알고리즘을 사용해서 풀도록 하자!!
다익스트라 알고리즘은 동작 과정은 아래와 같다.
1. 출발 노드 지정
2. 최단 거리 테이블 초기화
3. 방문한 적 없는 노드 중에서 최단 거리가 제일 짧은 노드를 선택
4. 해당 노드를 거쳐 다른 노드로 가는 거리를 구하여 최단 거리 테이블을 업데이트
5. 위 3, 4번 과정을 모든 노드를 방문할 때까지 반복
아래 코드는 파이썬으로 구현한 다익스트라 알고리즘 예시이다.
백준 1753번 최단경로)
import heapq
import sys
input = sys.stdin.readline
def dijkstra(start):
q = []
heapq.heappush(q, (0, start))
dis[start] = 0
while q:
d, now = heapq.heappop(q)
if dis[now] < d:
continue
for v, w in graph[now]:
cost = d + w
if cost < dis[v]:
dis[v] = cost
heapq.heappush(q, (cost, v))
V, E = map(int, input().split())
start = int(input())
INF = 10**9
dis = [INF]*(V+1)
graph = [[] for _ in range(V+1)]
for _ in range(E):
u, v, w = map(int, input().split())
graph[u].append((v, w))
dijkstra(start)
for n in dis[1:]:
print(n if n != INF else "INF")
우선순위 큐는 다익스트라 알고리즘이나, K번 째 최댓값을 찾을 때 등등에 사용할 수 있다.
아래는 대표적인 우선순위 큐 문제 풀이이다.
백준 11279번 최대 힙)
import heapq
import sys
input = sys.stdin.readline
N = int(input())
q = []
for _ in range(N):
n = int(input())
if n != 0:
heapq.heappush(q, -n)
else:
print(-heapq.heappop(q) if q else 0)
백준 1927번 최소 힙)
import heapq
import sys
input = sys.stdin.readline
N = int(input())
q = []
for _ in range(N):
n = int(input())
if n != 0:
heapq.heappush(q, n)
else:
print(heapq.heappop(q) if q else 0)
def check(n):
for i in range(n):
if row[n] == row[i] or abs(row[n]-row[i]) == n-i:
return 0
return 1
def dfs(n):
global res
if n == N:
res += 1
else:
for i in range(N):
row[n] = i
if check(n):
dfs(n+1)
N = int(input())
row = [0]*N
res = 0
dfs(0)
print(res)
너비 우선 탐색(BFS)은 시작 정점을 방문한 후 시작 정점에 인접한 모든 정점들을 먼저 탐색하는 방법이다.
가까운 정점을 전부저장해놓고 순서대로 방문해야 하기 때문에 스택으로는 구현이 어렵기에 큐(Queue)를 사용한다.
최단거리를 구하는 문제에 많이 사용되는 알고리즘이다. 아래 코드는 BFS코드 예시이다.
BFS 경로 탐색(인접 리스트, 1260번 DFS와 BFS)
from collections import deque
def bfs(start):
queue = deque([start])
while queue:
node = queue.popleft()
print(node, end=' ')
check[node] = 1
for n in sorted(li[node]):
if check[n] == 0:
queue.append(n)
check[n] = 1
N, M, V = map(int, input().split())
li = [[] for _ in range(N+1)]
for _ in range(M):
a, b = map(int, input().split())
li[a].append(b)
li[b].append(a)
check = [0]*(N+1)
bfs(V)
BFS 문제 풀이(인접 행렬, 1012번 유기농 배추)
from collections import deque
def bfs(y, x):
queue = deque()
queue.append((y, x))
d = [(-1, 0), (1, 0), (0, -1), (0, 1)]
while queue:
y, x = queue.popleft()
for dy, dx in d:
Y, X = y+dy, x+dx
if (0 <= Y < N) and (0 <= X < M) and graph[Y][X] == 1:
queue.append((Y, X))
graph[Y][X] = 0
for case in range(int(input())):
M, N, K = map(int, input().split())
graph = [[0]*M for _ in range(N)]
for _ in range(K):
x, y = map(int, input().split())
graph[y][x] = 1
res = 0
for i in range(N):
for j in range(M):
if graph[i][j] == 1:
graph[i][j] = 0
bfs(i, j)
res += 1
print(res)
깊이 우선 탐색(DFS)은현재 정점과 인접한 간선들을 하나씩 검사하다가, 아직 방문하지 않은 정점으로 통하는 간선이
있다면 그 간선을 방문하고, 이 과정에서 더 이상 방문할 곳이 없다면,마지막에 방문했던간선을 따라 뒤로 돌아가는 방
식이다. 동굴을 탐험한다고 생각하면 쉽다.
마지막에 방문했던 곳으로 되돌아간다는 점에서 스택과 비슷해서 그런지, DFS는 스택 또는 재귀 함수로 구현할 수 있다.
아래 코드는 DFS코드 예시이다.
DFS 경로 탐색(인접 리스트, 1260번 DFS와 BFS)
def dfs(node):
if check[node] == 0:
print(node, end=' ')
check[node] = 1
for n in sorted(li[node]):
dfs(n)
N, M, V = map(int, input().split())
li = [[] for _ in range(N+1)]
for _ in range(M):
a, b = map(int, input().split())
li[a].append(b)
li[b].append(a)
check = [0]*(N+1)
dfs(V)
print()
DFS 문제 풀이(인접 행렬, 1012번 유기농 배추)
import sys
sys.setrecursionlimit(10000)
def dfs(y, x):
graph[y][x] = 0
d = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for dy, dx in d:
Y, X = y+dy, x+dx
if (0 <= Y < N) and (0 <= X < M) and graph[Y][X] == 1:
dfs(Y, X)
for case in range(int(input())):
M, N, K = map(int, input().split())
graph = [[0]*M for _ in range(N)]
for _ in range(K):
x, y = map(int, input().split())
graph[y][x] = 1
res = 0
for i in range(N):
for j in range(M):
if graph[i][j] == 1:
dfs(i, j)
res += 1
print(res)
스택은 LIFO구조로 가장 나중에 집어넣은 데이터를 가장 먼저 뺄 수 있는 데이터 구조이다.
먼저 집어 넣은 데이터가 먼저 나오는 큐(Queue)와는 반대되는 개념이다.
아래 코드들은 스택을 활용하여 만들어 본 계산기이다.
1. infix 수식 -> postfix 수식 코드
tokens = input().split()
stack, output = [], []
op = {')': 3, '*': 2, '/': 2, '+': 1, '-': 1, '(': 0}
for t in tokens:
if t not in op:
output.append(int(t))
elif t == '(':
stack.append(t)
elif t == ')':
while stack != [] and stack[-1] != '(':
output.append(stack.pop())
stack.pop()
else:
while stack != [] and op[stack[-1]] >= op[t] :
output.append(stack.pop())
stack.append(t)
while stack:
output.append(stack.pop())
print(output)
예를 들어 입력이 "( 1 + 3 ) * 2 * 4 - 1"이라면 결과는 [1, 3, '+', 2, '*', 4, '*', 1, '-']이 된다.
2. 계산 코드
nums = []
for t in output:
if t not in op:
nums.append(t)
else:
a, b = nums.pop(), nums.pop()
if t == '+':
nums.append(b+a)
elif t == '-':
nums.append(b-a)
elif t == '*':
nums.append(b*a)
elif t == '/':
nums.append(b/a)
print(nums)
리스트에서 어떤 특정 값(X)을 찾을 때, 리스트의 가운데 값(M)을 기준으로 비교하면서 탐색하는 방식인데
X가 M보다 크다면 리스트의 오른쪽 부분, X가 M보다 작다면 리스트의 왼쪽 부분을 사용
-> 다시 가운데 값을 정의 -> 앞의 두 과정을 X == M일 때까지 반복하는 식이다.
def binarySearch(li, n):
# s: 맨 앞 인덱스, e: 맨 뒤 인덱스
s, e = 0, len(li)-1
while s <= e:
# m:가운데 인덱스
m = (s + e) // 2
if li[m] == n:
return 1
if n > li[m]:
s = m + 1
else:
e = m - 1
return 0
위 코드는 리스트(li)에 특정 값(n)이 있는지를 확인하는 이분 탐색 함수인데, 아래와 같은 방식으로 돌아간다.
[1,2,3,4,5,6] 리스트에서 5를 찾는 상황인데
첫 번째 중앙값보다 n이 더 크므로 리스트의 오른쪽 부분을 사용해서 다시 비교한다.
두 번째 중앙값은 5와 같으므로 반복문이 끝이 나는 식이다.
아래는 위의 이미지 같은 이분 탐색 과정을 출력해주는 코드이다.
def binarySearch(li, n):
s, e = 0, len(li)-1
i = 1
while s <= e:
m = (s + e) // 2
print("%d번째 반복" % (i))
print("s: %d e: %d m: %d" % (s, e, m))
print("n: %d li[m]: %d\n" % (n, li[m]))
i += 1
if li[m] == n:
print("목표 찾음!")
return 1
if n > li[m]:
s = m + 1
else:
e = m - 1
print("목표 못찾음!")
return 0
binarySearch([1, 2, 3, 4, 5, 6], 5)
참고로 python에는 bisect라는 이분 탐색 라이브러리가 존재해서 아래와 같은 방식으로 쉽게 사용할 수 있다!