한 지점에서 다른 모든 지점까지의 최단경로 구하기 (단일 시작점으로 부터 각 정점에 이르는 최단 경로)
모든 지점에서 모든 지점까지의 최단경로 구하기 (모든 정점 쌍 사이의 최단경로를 모두 구하기)
그리디와 동적 계획법이 합쳐진 형태입니다.
그래프에서 특정 노드에서 출발하여 다른 모든 노드로 가는 각각의 최단 경로를 구해주는 알고리즘입니다.
단, 모든 링크의 가중치(길이)는 양의 정수 값이어야 합니다.
특정 노드에서 다른 노드들의 최단 거리를 구하는 문제가 주어졌을 때 다익스트라 알고리즘을 사용하면 문제를 해결할 수 있습니다.
다익스트라 알고리즘은 출발 노드와 그 외 노드간의 최단 거리를 구하는 알고리즘입니다.
엣지는 항상 양수여야 한다는 제약이 있습니다.
다익스트라는 출발 노드와 도착 노드간의 최단 거리를 구하는 알고리즘이 아니라,
출발 노드와 이외의 모든 노드 간의 최단 거리를 표현하는 알고리즘 입니다.
https://www.acmicpc.net/problem/1753
#include <stdio.h>
#include <vector>
#include <queue>
#define INF 987654321
using namespace std;
vector<pair<int,int> > node[20005];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >pq;
int value[20005];
int main(){
int n,e,k;
int u,v,w;
scanf("%d %d",&n,&e);
scanf("%d",&k);
for(int i=0;i<e;i++){
scanf("%d %d %d",&u,&v,&w);
node[u].push_back(make_pair(v,w));
}
for(int i=1;i<=n;i++)
value[i] = INF;
value[k] = 0;
pq.push(make_pair(0,k));
while(!pq.empty()){
int x = pq.top().first;
int U = pq.top().second;
pq.pop();
for(int i=0;i<node[U].size();i++){
int V = node[U][i].first;
int W = node[U][i].second;
if(x+W<value[V]){
value[V] = x+W;
pq.push(make_pair(x+W,V));
}
}
}
for(int i=1;i<=n;i++)
if(value[i]==INF) printf("INF\n");
else printf("%d\n",value[i]);
return 0;
}
public class P1753_최단경로 {
public static int V, E, K;
public static int distance[];
public static boolean visited[];
public static ArrayList<Edge_1753> list[];
public static PriorityQueue<Edge_1753> q = new PriorityQueue<Edge_1753>();
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
st = new StringTokenizer(br.readLine());
V = Integer.parseInt(st.nextToken());
E = Integer.parseInt(st.nextToken());
K = Integer.parseInt(br.readLine());
distance = new int[V + 1];
visited = new boolean[V + 1];
list = new ArrayList[V + 1];
for (int i = 1; i <= V; i++) {
list[i] = new ArrayList<Edge_1753>();
}
for (int i = 0; i <= V; i++) {
distance[i] = Integer.MAX_VALUE;
}
for (int i = 0; i < E; i++) { // 가중치가 있는 인접 리스트 초기화
st = new StringTokenizer(br.readLine());
int u = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
int w = Integer.parseInt(st.nextToken());
list[u].add(new Edge_1753(v, w));
}
q.add(new Edge_1753(K, 0)); // K를 시작점으로 설정
distance[K] = 0;
while (!q.isEmpty()) {
Edge_1753 current = q.poll();
int c_v = current.vertex;
if (visited[c_v]) {
continue; // 기 방문 노드는 다시 큐에 넣지 않습니다.
}
visited[c_v] = true;
for (int i = 0; i < list[c_v].size(); i++) {
Edge_1753 tmp = list[c_v].get(i);
int next = tmp.vertex;
int value = tmp.value;
if (distance[next] > distance[c_v] + value) { // 최소 거리로 업데이트
distance[next] = value + distance[c_v];
q.add(new Edge_1753(next, distance[next]));
}
}
}
for (int i = 1; i <= V; i++) { // 거리 배열 출력
if (visited[i]) {
System.out.println(distance[i]);
} else {
System.out.println("INF");
}
}
}
}
class Edge_1753 implements Comparable<Edge_1753> {
int vertex, value;
Edge_1753(int vertex, int value) {
this.vertex = vertex;
this.value = value;
}
public int compareTo(Edge_1753 e) {
if (this.value > e.value) {
return 1;
} else {
return -1;
}
}
}
특징
이러한 문제점을 해결하기 위해 나온 알고리즘이 벨만 포드 입니다.
기본적으로 다익스트라와 동일하지만 핵심 차이점은 간선의 가중치가 음일 때도 최소 비용을 구할 수 있습니다.
다만 시간복잡도가 늘어나기 때문에 가중치가 모두 양수일 경우 다익스트라를 사용하는 것이 좋습니다.
시간 복잡도가 늘어나는 이유는 그리디하게 최소 비용 경로를 찾아가는 다익스트라와 달리, 벨만 포드는 모든 경우의 수를 고려하는 동적 계획법이 사용되기 때문입니다.
그렇다면 모든 경우의 수를 어떻게 고려할까?
그 방법은 매 단계 마다 모든 간선을 전부 확인하는 것입니다.
다익스트라는 출발 노드에서만 연결된 노드를 반복적으로 탐색하며 다른 모든 노드까지의 최소 거리를 구했습니다.
하지만 벨만 포드는 모든 노드가 한번씩 출발점
이 되어 다른 노드까지의 최소 비용을 구합니다.
D[s] != 무한대 이며 D[e] > D[s] + w 일 때 D[e] = D[s] + w
로 배열 업데이트벨만 포드 알고리즘을 사용해 최단 거리를 구하는 문제보다 음수 사이클을 판별하는 문제가 더 빈번하게 출제됩니다. 따라서 마지막에 한 번 더 모든 엣지를 사용해 업데이트 되는 노드가 존재하는 지 확인해야 합니다.
https://www.acmicpc.net/problem/11657
import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;
public class P11657_타임머신 {
private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
private static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
static int N, M;
static long distance[];
static Edge edges[];
public static void main(String[] args) throws IOException {
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
edges = new Edge[M + 1];
distance = new long[N + 1];
Arrays.fill(distance, Integer.MAX_VALUE); // 최단거리 배열 초기화
for (int i = 0; i < M; i++) { // 간선 리스트에 데이터 저장하기
st = new StringTokenizer(br.readLine());
int start = Integer.parseInt(st.nextToken());
int end = Integer.parseInt(st.nextToken());
int time = Integer.parseInt(st.nextToken());
edges[i] = new Edge(start, end, time);
}
// 벨만포드 알고리즘 수행
distance[1] = 0;
for (int i = 1; i < N; i++) { //N보다 하나 적은 수만큼 반복
for (int j = 0; j < M; j++) {
Edge edge = edges[j];
// 더 작은 최단거리 가 있는 경우 갱신
if (distance[edge.start] != Integer.MAX_VALUE
&& distance[edge.end] > distance[edge.start] + edge.time) {
distance[edge.end] = distance[edge.start] + edge.time;
}
}
}
boolean mCycle = false;
for (int i = 0; i < M; i++) { // 음수 cycle 확인
Edge edge = edges[i];
if (distance[edge.start] != Integer.MAX_VALUE
&& distance[edge.end] > distance[edge.start] + edge.time) {
mCycle = true;
}
}
if (!mCycle) { // 음의 cycle이 없는 경우
for (int i = 2; i <= N; i++) {
if (distance[i] == Integer.MAX_VALUE) {
System.out.println("-1");
} else {
System.out.println(distance[i]);
}
}
} else { // 음의 cycle이 있는 경우
System.out.println("-1");
}
}
}
class Edge { // 간선리스트를 편하게 다루기 위해 클래스로 별도 구현
int start, end, time; // 시작점, 도착점, 걸리는 시간
public Edge(int start, int end, int time) {
this.start = start;
this.end = end;
this.time = time;
}
}
플로이드 워셜 알고리즘은 모든 노드 간의 최단거리를 구할 수 있습니디.
플로이드 워셜 알고리즘은 그래프 상에서 음의 가중치가 있더라도 최단 경로를 구할 수 있습니다.
하지만 음의 가중치와 별개로 음의 사이클이 존재한다면 벨만 포드 알고리즘을 사용해야 합니다.
플로이드 워셜 알고리즘은 모든 노드 간의 최단거리를 구하고 이 때, 점화식이 사용되기에 동적 계획법에 해당합니다. 동적 계획법에 해당하므로 최단 거리를 업데이트할 테이블이 필요합니다.
이 때 모든 노드간의 최단 거리이므로 2차원 배열이 사용됩니다.
특정 한 노드에서 출발하는 다익스트라가 1차원 배열을 사용하는 것과 차이점이 있습니다.
가장 핵심적인 원리는 A 노드에서 B 노드까지 최단 경로를 구했다고 가정했을 때, 최단 경로 위에 K 노드가 존재한다면 그것을 이루는 부분 경로 역시 최단 경로라는 것 입니다.
즉, 전체 경로의 최단 경로는 부분 경로의 최단 경로의 조합으로 이루어진다는 의미입니다.
(즉, 정점 A와 정점 B 사이의 최단 경로는 A-B이거나 A-K-B 입니다. 만약 경유지(K)를 거친다면 최단 경로를 이루는 부분 경로 역시 최단 경로입니다. 다시 말해, 만약 A-B의 최단 경로가 A-K-B라면 A-K와 K-B도 각각 최단 경로입니다.)
다음과 같은 점화식을 도출할 수 있습니다.
D[S][E] = Min(D[S][E] , D[S][K] + D[K][E])
for k in range(1, V+1): # via
graph[k][k] = 0 # 사이클 없으므로 자기 자신 0
for i in range(1, V+1): # src
for j in range(1, V+1): # dst
graph[i][j] = min(graph[i][j], graph[i][k]+graph[k][j])
https://www.acmicpc.net/problem/11404
import java.io.*;
import java.util.StringTokenizer;
public class P11404_플로이드 {
private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
private static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
static int N, M;
static int distance[][];
public static void main(String[] args) throws IOException {
N = Integer.parseInt(br.readLine());
M = Integer.parseInt(br.readLine());
distance = new int[N + 1][N + 1];
for (int i = 1; i <= N; i++) { // 인접 행렬 초기화
for (int j = 1; j <= N; j++) {
if (i == j) {
distance[i][j] = 0;
} else {
distance[i][j] = 10000001; // 충분히 큰수로 저장
}
}
}
for (int i = 0; i < M; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
int s = Integer.parseInt(st.nextToken());
int e = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
if (distance[s][e] > v) {
distance[s][e] = v;
}
}
for (int k = 1; k <= N; k++) { // 플로이드 워셜 알고리즘 수행
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
if (distance[i][j] > distance[i][k] + distance[k][j]) {
distance[i][j] = distance[i][k] + distance[k][j];
}
}
}
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
if (distance[i][j] == 10000001) {
System.out.print("0 ");
} else {
System.out.print(distance[i][j] + " ");
}
}
System.out.println();
}
}
}
고생하셨습니당~~