728x90
728x170
존슨 알고리즘(Johnson's Algorithm)
- 벨만-포드 알고리즘 과 다익스트라 알고리즘 의 방법을 결합하여 모든 정점 사이의 최단 경로를 구하는 알고리즘
- 다익스트라 알고리즘의 효율성을 활용함과 동시에 음수 가중치를 갖는 그래프에 대해서도 올바른 결과를 생성할 수 있음.
- 상당히 새로운 접근 방법을 사용함.
- 음수 가중치에 대한 다익스트라 알고리즘의 한계를 극복하기 위해 전체 에지 가중치를 음수가 아닌 형태로 변환 함.
- 벨만-포드 알고리즘 과 적절한 수학 논리 를 결합하여 이루어짐.
- 음수 가중치에 대한 다익스트라 알고리즘의 한계를 극복하기 위해 전체 에지 가중치를 음수가 아닌 형태로 변환 함.
동작 방식
- 그래프에 새로운 더미(Dummy) 정점 을 추가함.
- 그리고 이 더미 정점과 나머지 모든 정점 사이에 가중치가
0
인 에지를 연결함.
- 그리고 이 더미 정점과 나머지 모든 정점 사이에 가중치가
- 이후 벨만-포드 알고리즘 을 이용하여 더미 정점과 나머지 정점들 사이의 최단 경로를 찾고, 나중에 사용하기 위해 각 정점까지의 최단 거리를 기록함.
더미 정점 추가
- 더미 정점과 나머지 모든 정점 사이에 가중치가
0
인 에지를 연결했기 때문에 모든 최단 거리 값은0
보다 클 수는 없음. - 또한 그래프의 모든 정점에 대한 연결을 통해 거리 값이 모든 가능한 순회 경로에서 일정한 관계를 유지할 수 있음.
- 에지 가중치와 최단 거리의 합 연산이 간단해질 수 있음.
- 이동 경로상의 연속한 정점에 대해서 거리 값 연산이 서로 상쇄되어, 결국 전체 합 은 첫 번째 정점과 마지막 정점의 거리 값 차 와 같음.
- 에지 가중치와 최단 거리의 합 연산이 간단해질 수 있음.
예 : 음수 가중치가 있는 그래프
A
부터E
까지의 정점이 있는 그래프에 더미 정점S
를 추가한 형태- 설명
- 검은색 에지 옆 괄호 안에 적힌 숫자 : 에지 가중치
- 정점 아래 빨간색 글자 :
S
에서 각 정점까지의 최단 경로와 최단 거리 - 빨간색 실선 화살표 :
S
로부터 출발하는 최단 경로 - 점선 화살표 : 최단 경로에서 사용되지 않은 가중치가
0
인 에지
- 그래프를
A
->B
->C
->A
->D
->E
순서로 탐색할 경우, 각 정점의 최단 거리
- 각 정점의 거리 사이에 초기 에지 가중치를 추가하면 다음과 같음.
- 각 에지 가중치에 다음 수식을 적용함.
w(uv)
: 정점u
와v
사이의 초기 에지 가중치d[s, u]
:s
정점과u
정점 사이의 최단 경로 거리d[s, v]
:s
정점과v
정점 사이의 최단 경로 거리W(uv)
: 변환된 에지 가중치
W(uv) = w(uv) + d[s, u] - d[s, v]
- 위 수식을 실제로 적용한 결과는 다음과 같음.
AB -> (-7) + 0 - '(-7)' = 0
BC -> (-2) + '(-7)' - '(-9)' = 0
CA -> 10 + '(-9)' - '0' = 1
AD -> (-5) + '0' - '(-5)' = 0
DE -> 4 + '(-5)' - (-1) = 0
- 위 수식을 연속적으로 적용할 경우, 각 줄의 세 번째 항 은 다음 줄의 가운데 항 에 의해 상쇄되는 것을 발견할 수 있음.
- 이를 망원(Telescoping) 속성이라고 함.
- 수학 용어 중 망원 급수(Telescoping Series) 와 관련된 단어
- 길이 조절이 되는 옛날 망원경처럼 덧셈항과 뺄셈항이 서로 상쇄되어 수식이 짧아진다는 의미
- 이를 망원(Telescoping) 속성이라고 함.
- 이 속성으로 인해
A
정점에서E
정점까지의 최소 거리를 나타내는 다음 두 수식은 완전히 같음.(w(AB) + d[s, A] - d[s, B]) + (w(BC) + d[s, B] - d[s, C]) + ... + (w(DE) + d[s, D] - d[s, E])
(w(AB) + w(BC) + w(CA) + w(AD) + w(DE)) + d[s, A] - d[s, E]
- 즉, 두 정점 사이를 연결하는 경로에 더해지는 값은 어느 경로를 선택하든 항상 같음.
- 벨만-포드 알고리즘 이 반환하는 거리 배열
d
에 대해, 항상d[s, u] + weight(u, v) >= d[s, v]
조건을 만족하기 때문에 변환된 가중치는 음수가 될 수 없음.w(u, v) + d[s, u] - d[s, v]
값은 무조건0
보다 같거나 큰 수임.
- 이러한 가중치 변환 수식을 이용하면, 그래프에서 모든 최단 경로상에 존재하는 에지의 가중치가
0
으로 변경됨.- 원래의 최단 경로 순서는 그대로 유지한 채, 음이 아닌 가중치로 구성된 그래프가 만들어짐.
- 이제 새로운 가중치를 갖는 그래프의 각 정점에서 다익스트라 알고리즘 을 적용하여 모든 정점 쌍 사이의 최단 거리를 구할 수 있음.
코드
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
struct Edge {
int src;
int dst;
int weight;
};
const int UNKNOWN = INT_MAX;
bool HasNegativeCycle(const vector<Edge>& edges, vector<int> distance) {
for (auto& e : edges) {
if (distance[e.src] == UNKNOWN) {
continue;
}
if (distance[e.src] + e.weight < distance[e.dst]) {
return true;
}
}
return false;
}
vector<int> BellmanFord(vector<Edge> edges, int V) {
vector<int> distance(V + 1, UNKNOWN);
int s = V;
for (int i = 0; i < V; i++) {
edges.push_back(Edge {s, i, 0});
}
distance[s] = 0;
// 정점 개수가 V + 1개이므로 V번 반복
for (int i = 0; i < V; i++) {
for(auto& e : edges) {
// 에지의 시작 정점의 거리 값이 UNKNOWN이면 스킵
if (distance[e.src] == UNKNOWN) {
continue;
}
// 인접한 정점의 거리 값이 새로운 경로에 의한 거리 값보다 크면
// 거리 값을 업데이트함.
if (distance[e.dst] > distance[e.src] + e.weight) {
distance[e.dst] = distance[e.src] + e.weight;
}
}
}
// 음수 가중치 사이클이 있는지 검사
if (HasNegativeCycle(edges, distance)) {
cout << "음수 가중치 사이클 발견!" << endl;
return {};
}
return distance;
}
int GetMinDistance(vector<int>& distance, vector<bool>& visited) {
int minDistance = UNKNOWN;
int minIndex = -1;
for (int i = 0; i < distance.size(); i++) {
if (!visited[i] && distance[i] <= minDistance) {
minDistance = distance[i];
minIndex = i;
}
}
return minIndex;
}
vector<int> Dijkstra(vector<Edge> edges, int V, int start) {
vector<int> distance(V, UNKNOWN);
vector<bool> visited(V, false);
distance[start] = 0;
for (int i = 0; i < V - 1; i++) {
// 방문하지 않은 정점 중에서 최소 거리 정점을 찾음.
int curr = GetMinDistance(distance, visited);
visited[curr] = true;
for (auto& e : edges) {
// 인접한 정점만 고려
if (e.src != curr) {
continue;
}
// 이미 방문했으면 무시
if (visited[e.dst]) {
continue;
}
if (distance[curr] != UNKNOWN && distance[e.dst] > distance[curr] + e.weight) {
distance[e.dst] = distance[curr] + e.weight;
}
}
}
return distance;
}
void Johnson(vector<Edge> edges, int V) {
// 더미 정점을 추가한 그래프에서 최단 거리를 계산
vector<int> h = BellmanFord(edges, V);
if (h.empty()) {
return;
}
// 에지 가중치 재설정
for (auto& e : edges) {
e.weight += (h[e.src] - h[e.dst]);
}
// 모든 정점들 사이의 최단 거리를 저장
vector<vector<int>> shortest(V);
for (int i = 0; i < V; i++) {
shortest[i] = Dijkstra(edges, V, i);
}
// 가중치 변환 수식을 역으로 적용하여 최단 거리를 출력
for (int i = 0; i < V; i++) {
cout << i << ":\n";
for (int j = 0; j < V; j++) {
if (shortest[i][j] != UNKNOWN) {
shortest[i][j] += h[j] - h[i];
cout << "\t" << j << ": " << shortest[i][j] << endl;
}
}
}
}
int main() {
int V = 5; // 정점 개수
vector<Edge> edges; // 에지 포인터의 벡터
vector<vector<int>> edge_map { // {시작 정점, 목표 정점, 가중치}
{0, 1, -7},
{1, 2, -2},
{2, 0, 10},
{0, 3, -5},
{0, 4, 2},
{3, 4, 4}
};
for (auto& e : edge_map) {
edges.emplace_back(Edge {e[0], e[1], e[2]});
}
Johnson(edges, V);
return 0;
}
결과
0:
0: 0
1: -7
2: -9
3: -5
4: -1
1:
0: 8
1: 0
2: -2
3: 3
4: 7
2:
0: 10
1: 3
2: 0
3: 5
4: 9
3:
3: 0
4: 4
4:
4: 0
728x90
그리드형(광고전용)
'Computer Science > Algorithm' 카테고리의 다른 글
가우스 계산법(Gaussian Calculation) (0) | 2022.08.31 |
---|---|
부분집합의 합 문제(Subset Sum Problem) (0) | 2021.07.22 |
플로이드-워셜 알고리즘(Floyd-Warshall Algorithm) (0) | 2021.07.22 |
코사라주 알고리즘(Kosaraju's Algorithm) (0) | 2021.07.04 |
벨만-포드 알고리즘(Bellman-Ford Algorithm) (0) | 2021.07.02 |
다익스트라 최단 경로 알고리즘(Dijkstra Shortest Path Algorithm) (0) | 2021.06.30 |
프림의 최소 신장 트리 알고리즘(Prim's Minimum Spanning Tree Algorithm) (0) | 2021.06.30 |
깊이 우선 탐색(DFS, Depth-First Search) (0) | 2021.06.30 |