알고리즘

다익스트라, 벨만-포드, 플로이드-워셜

Themion 2022. 1. 22. 15:08

그래프 문제를 보다 보면, 특정 노드 a에서 노드 b까지의 경로를 구하는 문제가 자주 보인다. 이런 그래프에서 경로를 구하는 문제에 주로 사용하는 알고리즘이 다익스트라, 벨만-포드, 플로이드-워셜 알고리즘이다.

다익스트라는 그래프에 음의 가중치를 갖는 에지가 없을 때 주로 사용한다. 시작 노드에서 특정 경로를 이동해 노드 n에 도착했을 때의 비용을 cost[n]으로 나타낼 때, n에 연결된 i번째 에지 e_i와 e_i에 연결된 노드 n_i에 대해, n_i를 방문하지 않았다면 cost[n_i]가 cost[n] + e_i보다 작을 때 기존 경로를 시작 노드에서 n까지의 경로 + e_i로 갱신하면 더 적은 비용으로 n_i까지 이동할 수 있다. 이 과정을 알려진 경로가 존재하지 않을 때부터 시작해서 더 이상 갱신할 수 있는 경로가 없을 때까지 반복한다면 시작 노드부터 모든 노드로의 최단 경로와 그 비용을 알아낼 수 있다.

typedef pair<int, int> edge;

#define _cost first
#define _node second

int dijkstra(int s, int e) {
    int cost[MAX_N + 1];
    priority_queue<edge, vector<edge>, greater<edge>> q;

    fill_n(cost, node + 1, INF);
    cost[s] = 0;

    q.push({0, s});

    while (!q.empty()) {
        edge e = q.top();
        q.pop();
        
        if (e._cost > cost[e._node]) continue;

        for (auto e_ : graph[e._node]) if (cost[e_._node] > e._cost + e_._cost)
            q.push({cost[e_._node] = e._cost + e_._cost, e_._node});
    }

    return cost[e];
}

다익스트라를 사용할 경우 그래프에 음의 가중치를 가진 에지가 존재한다면, 해당 에지를 이용할 때마다 비용이 줄어드므로 무한 루프에 빠질 가능성이 있다. 이 때문에 고안된 것이 벨만-포드 알고리즘으로, 노드의 개수가 N개일 때 모든 노드를 각각 한 번씩 방문한다고 하면 에지를 N - 1개 사용하게 되므로 에지를 최대 N - 1개를 사용해 얻을 수 있는 최단 경로와 그 비용을 구한다. 이 이후 다시 한번 에지를 사용해 갱신 가능한 비용이 존재한다면, 그 경로는 무한히 비용 갱신이 가능하고 언젠가 비용이 음수로 변할 것이므로 그 이상 탐색하지 않는다.

typedef pair<int, int> edge;

#define _cost first
#define _node second

bool push(int node) {
    bool ret = false;
    int cost_;

    q.pop();

    for (edge e : v[node]) {
        cost_ = cost[node] + e._cost;

        if (cost[e._node] <= cost_) continue;

        cost[e._node] = cost_;
        q.push(e._node);

        ret = true;
    }

    return ret;
}

bool bellman_ford() {
    int size = q.size();

    for (int i = 1; i < N; i++) {
        while (size--) push(q.front());
        size = q.size();
    }

    while (size--) if (push(q.front())) return true;
    return false;
}

다익스트라와 벨만-포드가 한 노드에서 모든 노드로의 경로를 계산한다면, 플로이드-워셜 알고리즘은 모든 노드에서 모든 노드로의 비용을 계산한다. 플로이드-워셜에선 노드 i에서 노드 j로의 비용 cost[i][j]을 경유지 k를 이용해 갱신하는데, 만일 costi][j]가 cost[i][k] + cost[k][j]보다 작다면 노드 i에서 j로 갈 때 기존 경로가 아닌, i에서 k까지 이동한뒤 다시 k에서 j까지 이동하는 것이다. 이 과정을 i, j, k 세 값에 대해 for 루프를 돌리는데, 이 때 중요한 건 i -> j -> k 순으로 루프를 돌리는 것이 아니라 k -> i -> j 순으로 루프를 돌려야 알고리즘이 정상적으로 작동하게 된다.

for (k = 0; k < n; k++)
    for (i = 0; i < n; i++) for (j = 0; j < n; j++)
        graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]);

https://www.acmicpc.net/problem/1753

 

1753번: 최단경로

첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1 ≤ V ≤ 20,000, 1 ≤ E ≤ 300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1 ≤ K ≤ V)가

www.acmicpc.net

https://www.acmicpc.net/problem/1865

 

1865번: 웜홀

첫 번째 줄에는 테스트케이스의 개수 TC(1 ≤ TC ≤ 5)가 주어진다. 그리고 두 번째 줄부터 TC개의 테스트케이스가 차례로 주어지는데 각 테스트케이스의 첫 번째 줄에는 지점의 수 N(1 ≤ N ≤ 500),

www.acmicpc.net

https://www.acmicpc.net/problem/11404

 

11404번: 플로이드

첫째 줄에 도시의 개수 n이 주어지고 둘째 줄에는 버스의 개수 m이 주어진다. 그리고 셋째 줄부터 m+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그 버스의 출발 도시의 번호가

www.acmicpc.net

'알고리즘' 카테고리의 다른 글

최소 스패닝 트리  (0) 2022.01.22
트리  (0) 2022.01.22
투 포인터  (0) 2022.01.22
이분 탐색  (0) 2022.01.22
그리디 알고리즘  (0) 2022.01.22