하효닝
log(hahyun ^ B)
하효닝
전체 방문자
오늘
어제
  • 분류 전체보기 (140)
    • Diary (0)
    • Web (7)
    • Frontend (8)
    • Python (44)
      • Python (1)
      • Algorithm (13)
      • Coding Test (30)
    • Django (3)
      • Django (2)
      • Django Rest (1)
    • Java (14)
      • Java (10)
      • Java Tuning (4)
    • Spring (34)
      • Spring (7)
      • Spring MVC (5)
      • DB 접근기술 (1)
      • JPA (10)
      • Spring Security (3)
      • Rest API (8)
    • Computer Science (26)
      • Operating System (8)
      • Linux (2)
      • Network (2)
      • Database (9)
      • SQL Tuning (5)
    • AWS (2)
    • Git (0)
    • etc (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
하효닝

log(hahyun ^ B)

Python/Algorithm

최단 경로 알고리즘

2022. 2. 25. 18:56

최단 경로 문제

  • 최단 경로 문제는 각 간선의 가중치 합이 최소가 되는 두 정점 사이의 경로를 찾는 문제이다.
  • 이때 가중치는 거리나 시간과 같은 이동 비용에 해당한다.

 

BFS

  • 간선의 가중치가 모두 1인 두 노드 사이의 최단 경로를 찾는데 사용한다.
  • 인접 리스트로 구현 시 시간 복잡도는 O(V+E)이고, 인접 행렬로 구현 시 시간 복잡도는 O(V^2)이다.

 

구현 (경로 추적 포함)

from collections import deque
import sys
sys.setrecursionlimit(200000)

n, k = map(int, input().split())
time = [-1] * 200001
time[n] = 0
path = [-1] * 200001
path[n] = n

q = deque()
q.append(n)

while q:
    now = q.popleft()
    for next in [now + 1, now - 1, 2 * now]:
        if 0 <= next <= 200000 and time[next] == -1:
            time[next] = time[now] + 1
            path[next] = now
            q.append(next)

print(time[k])

def find_path(n, k):
    if n != k:
        find_path(n, path[k])
    print(k, end=" ")

find_path(n, k)

 

다익스트라 (Dijkstra)

  • 간선의 가중치가 양수인 방향 그래프에서 한 정점으로부터 다른 모든 정점 사이의 최단거리를 구하는 알고리즘
  • 항상 노드 주변의 최단 경로만을 택하는 대표적인 그리디 알고리즘 중 하나로, 노드 주변을 탐색할 때 BFS를 이용한다.
  • 프림 알고리즘과 비슷하게 임의의 정점을 출발 집합에 더할 때, 그 정점까지의 최단거리는 계산이 끝났다는 확신을 가지고 더한다.
  • 그래프 내에 음의 가중치를 갖는 간선이 없는 경우에만 사용가능하다.

 

시간 복잡도

  • 배열로 구현하면 전체 수행시간은 O(V^2)
  • 우선순위 큐를 최소힙으로 사용해서 구현하면 전체 수행시간은 O((V+E)*logV)
  • 만약 그래프에 간선이 아주 많다면 배열로 구현하는 것이 낫지만, 그래프에 간선이 많지 않다면 최소힙을 사용한 방법이 더 좋다.

 

구현 (경로 추적 포함)

import heapq

n = int(input())
m = int(input())
graph = [[] for _ in range(n + 1)]
for _ in range(m):
    a, b, c = map(int, input().split())
    graph[a].append((b, c))

dist = [-1] * (n + 1)
trace = [[] for _ in range(n + 1)]
start, end = map(int, input().split())

q = []
heapq.heappush(q, (0, start))
dist[start] = 0
trace[start].append(start)
while q:
    cost, now = heapq.heappop(q)
    if dist[now] != -1 and dist[now] < cost:
        continue

    for nxt, nxt_cost in graph[now]:
        nxt_cost += cost
        if dist[nxt] == -1 or nxt_cost < dist[nxt]:
            dist[nxt] = nxt_cost
            heapq.heappush(q, (nxt_cost, nxt))
            # 경로 갱신
            trace[nxt] = []
            for p in trace[now]:
                trace[nxt].append(p)
            trace[nxt].append(nxt)

print(dist[end])
print(len(trace[end]))
print(*trace[end])

 

플로이드-워셜

  • 그래프에서 모든 정점 사이의 최단 거리를 한 번에 찾는 알고리즘으로, 가중치가 음수인 간선은 가질 수 있으나 가중치의 합이 음수가 되는 사이클이 존재하면 안된다.
  • 먼저 한 정점에서 다른 정점으로 이동하는 간선 가중치를 인접 행렬에 채워넣고 중간 경유지를 만들어, 바로 가는 것보다 중간 경유지를 거쳐서 가는 것이 빠르다면 최단거리를 갱신한다. (DP)
  • 시간 복잡도는 O(V^3)

 

구현 (경로 추적 포함)

import sys
sys.setrecursionlimit(10 ** 6)
input = sys.stdin.readline
INF = sys.maxsize

def find_path(i, j):
    if trace[i][j] == 0:
        return []

    k = trace[i][j]
    return find_path(i, k) + [k] + find_path(k, j)

n = int(input())
m = int(input())
dist = [[INF] * (n + 1) for _ in range(n + 1)]
trace = [[0] * (n + 1) for _ in range(n + 1)]

for i in range(1, n + 1):
    dist[i][i] = 0
for _ in range(m):
    a, b, c = map(int, input().split())
    dist[a][b] = min(dist[a][b], c)

for k in range(1, n + 1):
    for i in range(1, n + 1):
        for j in range(1, n + 1):
            if dist[i][j] > dist[i][k] + dist[k][j]:
                dist[i][j] = dist[i][k] + dist[k][j]
                trace[i][j] = k

for i in range(1, n + 1):
    for j in range(1, n + 1):
        if dist[i][j] != INF:
            print(dist[i][j], end=" ")
        else:
            print(0, end=" ")
    print()

for i in range(1, n + 1):
    for j in range(1, n + 1):
        if dist[i][j] == 0 or dist[i][j] == INF:
            print(0)
            continue

        path = [i] + find_path(i, j) + [j]
        print(len(path), end=" ")
        print(*path)

 

벨만-포드 알고리즘

  • 간선의 가중치가 음수 혹은 양수인 가중 방향 그래프가 주어졌을 때, 특정 노드에서 다른 모든 노드까지의 최단 경로를 찾는 알고리즘
  • V-1 번의 반복동안 시작점에서 각 정점까지의 최단 경로의 오차를 점차 줄여나가며, 이 과정에서 DP를 이용해 사용했던 데이터를 재사용한다.
  • 구성 간선들의 가중치 총합이 음수인 사이클의 포함 여부를 판단할 수 있다.
  • 음수 사이클 탐지를 포함한 V번의 순회와, 각 순회마다 그래프 내의 간선을 모두 순회하므로 시간 복잡도는 O(V*E)

 

음수 사이클 탐지

INF = int(1e9)

def bellman_ford(start):
    dist[start] = 0

    # 간선 개수만큼 반복
    for i in range(v):
        for j in range(e):
            node = edges[j][0]
            nxt = edges[j][1]
            cost = edges[j][2]
            if dist[node] != INF and dist[nxt] > dist[node] + cost:
                dist[nxt] = dist[node] + cost
                if i == v - 1:
                    return True
    return False

v, e = map(int, input().split())
edges = []
dist = [INF] * (v + 1)

for _ in range(e):
    a, b, c = map(int, input().split())
    edges.append((a, b, c))

cycle = bellman_ford(1)
if cycle:
    print(-1)
else:
    for i in range(2, v + 1):
        if dist[i] == INF:
            print(-1)
        else:
            print(dist[i])

'Python > Algorithm' 카테고리의 다른 글

힙과 우선순위 큐  (0) 2022.02.27
트리의 종류와 트리 순회  (0) 2022.02.26
그래프와 그래프 탐색  (0) 2022.02.25
해시 테이블  (0) 2022.02.24
스택과 큐  (0) 2022.02.23
    'Python/Algorithm' 카테고리의 다른 글
    • 힙과 우선순위 큐
    • 트리의 종류와 트리 순회
    • 그래프와 그래프 탐색
    • 해시 테이블
    하효닝
    하효닝

    티스토리툴바