티스토리 뷰

알고리즘

최단 경로 알고리즘

killog 2021. 1. 10. 16:31
반응형

목차

  • 최단 경로 문제
  • 다익스트라 알고리즘
  • 플루이드 와샬알고리즘
  • 벨만포드 알고리즘
  • 참고 문헌

최단 경로 문제

  • 최단 경로 알고리즘은 가장 짧은 경로를 찾는 알고리즘이다.
  • 다양한 문제 상황
    • 한 지점에서 다른 한 지점까지의 최단경로
    • 한 지점에서 다른 모든 지점까지 최단경로
    • 모든 지점에서 다른 모든 지점까지의 최단경로
  • 각 지점은 그래프에서 노드로 표현한다.
  • 지점 간 연결된 도로는 그래프에서 간선으로 표현한다.

다익스트라 최단 알고리즘

개요

  • 특정 노드에서 출발해, 다른 모든 노드로 가는 최단 경로를 계산한다.
  • 다익스트라 최단 경로 알고리즘은 음의 간선이 없을 때, 정상적으로 동작한다.
    • 현실 세계의 도로, 간선은 음의 간선으로 표현되지 않는다.
  • 다익스트라 최단경로 알고리즘은 그리디 알고리즘으로 분류된다.
    • 매 상황에서 가장 비용이 적은 노드를 선택해 임의의 과정을 반복한다.

알고리즘 동작과정

  1. 출발 노드를 설정한다.
  2. 최단 거리 테이블을 초기화한다.
  3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택한다.
  4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산해, 최단 거리 테이블을 갱신한다.
  5. 위의 과정 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로 가는 거리가 더 짧은지 검사합니다.
  • 점화식은 다음과 같습니다.
  • image-20210110155551569

코드 구현

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) 입니다.

벨만포드 알고리즘

음수 간선에 관해 최단 경로문제는 다음과 같이 분류할 수 있습니다.

  1. 모든 간선이 양수인경우,
  2. 음수 간선이 있는 경우
    1. 음수 간선 순환은 없는 경우
    2. 음수 간선 순환이 있는 경우
  • 벨만포드 최단 경로 알고리즘은 음의 간선이 포함된 상황에서 사용할 수 있습니다. 또한, 음수 간선의 순환을 감지할 수 있습니다. 벨만 포드의 기본 시간 복잡도는 O(VE) 로 다익스트라 알고리즘에 비해 느립니다.

벨만포드 알고리즘 동작원리

  1. 출발 노드를 설정합니다
  2. 최단 거리 테이블을 초기화합니다.
  3. 다음 과정을 N-1 번 반복합니다.
    1. 전체 간선 E 개를 하나씩 확인합니다.
    2. 각 간선을 거쳐 다른 노드로 가는 비용을 계산해 최단 거리 테이블을 갱신합니다.
  • 만약 음수 간선 순환이 발생하는지 체크하고 싶다면 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

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/02   »
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
글 보관함