반응형
목차
- 최단 경로 문제
- 다익스트라 알고리즘
- 플루이드 와샬알고리즘
- 벨만포드 알고리즘
- 참고 문헌
최단 경로 문제
- 최단 경로 알고리즘은 가장 짧은 경로를 찾는 알고리즘이다.
- 다양한 문제 상황
- 한 지점에서 다른 한 지점까지의 최단경로
- 한 지점에서 다른 모든 지점까지 최단경로
- 모든 지점에서 다른 모든 지점까지의 최단경로
- 각 지점은 그래프에서 노드로 표현한다.
- 지점 간 연결된 도로는 그래프에서 간선으로 표현한다.
다익스트라 최단 알고리즘
개요
- 특정 노드에서 출발해, 다른 모든 노드로 가는 최단 경로를 계산한다.
- 다익스트라 최단 경로 알고리즘은 음의 간선이 없을 때, 정상적으로 동작한다.
- 현실 세계의 도로, 간선은 음의 간선으로 표현되지 않는다.
- 다익스트라 최단경로 알고리즘은 그리디 알고리즘으로 분류된다.
- 매 상황에서 가장 비용이 적은 노드를 선택해 임의의 과정을 반복한다.
알고리즘 동작과정
- 출발 노드를 설정한다.
- 최단 거리 테이블을 초기화한다.
- 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택한다.
- 해당 노드를 거쳐 다른 노드로 가는 비용을 계산해, 최단 거리 테이블을 갱신한다.
- 위의 과정 3과 4를 반복한다.
우선순위 큐를 이용한 구현
import heapq
import sys
input = sys.stdin.readline
# 노드의 개수, 간선의 개수 입력 받기
n,m = map(int, input().split())
# 시작 노드 번호 입력받기
start = int(input())
# 각 노드에 연결되어 있는 노드에 대한 정보를 담는 리스트 만들기
graph = [[] for i in range(n+1)]
# 최단 거리 테이블을 모두 무한으로 초기화
distance = [INF]*(n+1)
# 모든 간선 정보를 입력받기
for _ in range(m):
a,b,c =map(int, input().split())
# a 번 노드에서 b 번 노드로 가는 비용이 c 란 의미
graph[a].append((b,c))
def dijkstra(start):
q=[]
# 시작노드로 가기 위한 최단 거린 0으로 설정하여 큐에 삽입한다.
heapq.heappush(1,(0,start))
distance[start] =9
while q: # 큐가 비오잇지 않다면
# 가장 최단 거리가 짧은 노드에 대한 정보 꺼내기
dist, now = heapq.heappop(q)
# 현재 노드가 이미 처리된 적 있는 노드라면 무시
if distance[now].dist:
continue
# 현재 노드와 연결된 다른 인접한 노드들을 확인
for i inr graph[now]:
cost = dist+i[1]
# 현재 노드를 거쳐서, 다른 노드로 이동하는 거리가 더 짧은 경우
if cost< distance[i[0]]:
distance[i[0]]=cost
heapq.heappush(q,(cost, i[0]))
# 다익스트라 알고리즘을 수행.
dijkstra(start)
# 모든 노드로 가기위한 최단거리를 출력
for i in range(1, n+1):
# 도달할 수 없는 경우, 무한이라 출력
if distance[i] ==INF:
print("무한")
else:
print(distance[i])
플루이드 와샬 알고리즘
개요
- 모든 노드에서 다른 모든 노드까지의 최단 경로를 모두 계산합니다.
- 플로이드 와샬 알고리즘은 다익스트라 알고리즘과 마찬가지로 단계별로 거쳐가는 노드를 기준으로 알고리즘을 수행합니다.
- 다만, 매 단계마다 방문하지 않은 노드 중에 최단 거리를 갖는 노드를 찾는 과정이 필요하지 않습니다.
- 플로이드 와샬은 2차원 테이블에 최단 거리 정보를 저장합니다.
- 플로이드 와샬 알고리즘은 다이나믹 프로그래밍 유형에 속합니다.
플로이드 와샬 알고리즘
- 각 단계마다 특정한 노드 k 를 거쳐가는 경우를 확인합니다.
- a 에서 b 로 가는 최단 거리보다 a 에서 k 를 거쳐 b로 가는 거리가 더 짧은지 검사합니다.
- 점화식은 다음과 같습니다.
코드 구현
INF = int(1e9) # 무한을 의미하는 값으로 10억 설정
# 노드의 개수 및 간선의 개수 입력받이
n = int(input())
m = int(input())
# 2차원 리스트(그래프 표현)를 만들고, 무한으로 초기화
graph=[[INF] *(n+1) for _ in range(n+1)]
# 자기 자신에서 자기 자신으로 가는 비용은 0으로 초기화
for a in range(1, n+1):
for b in range(1, n+!):
if a==b:
graph[a][b]=0
# 각 간선에 대한 정보를 입력받아, 그 값으로 초기화
for _ in range(m):
# A 에서 B로 가는 비용은 C라고 설정
a,b,c = map(int, input().split())
graph[a][b] = c
# 점화식에 따라 플로이드 와샬 알고리즘을 수행
for k in range(1,n+1):
for a in range(1,n+1):
for b in range(1, n+1):
graph[a][b] = min(graph[a][b], graph[a][k] + graph[k][b])
# 수행된 결과를 출력
for a in range(1, n+1):
for b in range(1, n+1):
# 도달할 수 없는 경우 무한으로 출력
if graph[a][b] ==INF:
print("무한", end=' ')
else:
print(graph[a][b], end=' ')
print()
플로이드 와샬 알고리즘 성능 분석
- 노드의 개수가 N개 일때, 알고리즘 상으로 N 번의 단계를 수행합니다.
- 각 단계마다
O(n**2)
의 연산을 통해, 현재 노드가 거쳐가는 모든 경로를 고혀합니다.
- 각 단계마다
- 따라서 총 시간 복잡도는
O(n**3)
입니다.
벨만포드 알고리즘
음수 간선에 관해 최단 경로문제는 다음과 같이 분류할 수 있습니다.
- 모든 간선이 양수인경우,
- 음수 간선이 있는 경우
- 음수 간선 순환은 없는 경우
- 음수 간선 순환이 있는 경우
- 벨만포드 최단 경로 알고리즘은 음의 간선이 포함된 상황에서 사용할 수 있습니다. 또한, 음수 간선의 순환을 감지할 수 있습니다. 벨만 포드의 기본 시간 복잡도는
O(VE)
로 다익스트라 알고리즘에 비해 느립니다.
벨만포드 알고리즘 동작원리
- 출발 노드를 설정합니다
- 최단 거리 테이블을 초기화합니다.
- 다음 과정을
N-1
번 반복합니다.- 전체 간선 E 개를 하나씩 확인합니다.
- 각 간선을 거쳐 다른 노드로 가는 비용을 계산해 최단 거리 테이블을 갱신합니다.
- 만약 음수 간선 순환이 발생하는지 체크하고 싶다면 3번의 과정을 한 번 더 수행합니다.
- 이때 최단 거리 테이블이 갱신된다면, 음수 간선 순환이 존재하는 것입니다.
벨만 포드 알고리즘 vs 다익스트라 알고리즘
- 다익스트라 알고리즘
- 매번 방문하지 않은 노드 중에 최단 거리가 가장 짧은 노드를 선택합니다.
- 음수 간선이 없다면, 최적의 해를 찾을 수 있습니다.
- 벨만 포드 알고리즘
- 매번 모든 간선을 전부 확인합니다.
- 따라서 다익스트라 알고리즘에서의 최적의 해를 항상 포함합니다.
- 다익스트라 알고리즘에 비해 시간이 오래 걸리지만, 음수 간선 순환을 탐지 할 수 있습니다.
벨만 포드 알고리즘의 구현
import sys
input = sys.stdin.readline
INF = int(1e9) # 무한을 의미하는 값으로 10억을 설정
def bf(start):
# 시작 노드에 대해서 초기화
dist[start]=0
# 전체 n번의 라운드 반복
for j in range(m):
cur = edges[j][0]
next_node = edges[j][1]
cost = edges[j][2]
# 현재 간선을 거쳐서 다른 노드로 이동하는 거리가 더 짧은 겨웅
if dist[cur] != INF and dist[next_node]> dist[cur]+cost:
dist[next_node] = dist[cur]+cost
# n번째 라운드에서도 값이 갠신 된다면, 음수 순환이 존재.
if i==n-1:
return True
return False
# 노드의 개수, 간선의 개수 입력받기
n,m = map(int, input().split())
# 모든 간선에 대한 정보를 담는 리스트 만들기
edges =[]
# 최단 거리 테이블을 모두 무한으로 초기화
dist=[INF]*(n+1)
# 모든 간선의 정보를 입력받기
for _ in range(m):
a,b,c = map(int, input().split())
# a번 노드에서 b번 노드로 가는 비용이 c 라는 것을 의미
edges.append((a,b,c))
# 벨만포드 알고리즘을 수행
negatie_cycle = bf(1) #1 노드가 시작 노드
if negative_cycle:
print(-1)
else:
#1번 노드를 제외한 다른 모든 노드로 가기 위한 최단거리 출력
for i in range(2,n+1);
# 도달할 수 없는 경우, -1 출력
if dist[i] ==INF:
print("-1")
# 도달할 수 있는 경우, 거리를 출력
else:
print(dist[i])
참고 문헌
동빈나 이코테2021
https://www.youtube.com/watch?v=Ppimbaxm8d8&list=PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC&index=13
반응형
'알고리즘' 카테고리의 다른 글
백준 13458번 시험감독 파이썬 브론즈2 (0) | 2021.01.14 |
---|---|
백준 1717번 집합의 표현 파이썬 유니온 파인드 골드4 (0) | 2021.01.10 |
백준 2178번 미로 탐색 파이썬 실버1 BFS (0) | 2021.01.08 |
백준 1644번 소수의 연속합 파이썬 골드3 투포인터 (0) | 2021.01.08 |
백준 1806번 부분합 파이썬 투 포인터 골드 4 (0) | 2021.01.07 |