알고리즘/알고리즘 이론

단일 시작점 최단 경로 알고리즘 (다익스트라, 벨만 포드)

GaeunLee 2022. 3. 2. 10:05

가중치 그래프에서 시작 정점 s에서 다른 정점들 사이의 최단 경로를 구해보자!

 

일단, 시작 정점 s에서 어떤 정점 v사이의 거리를 배열(distance[v])로 나타내 보자.

그러면 distance[s] = 0이라고 표현할 수 있다.

 

*최단 경로를 구하기 위한 완화 작업(Relax)이란?*

시작 정점 s에서 v로 가는 경로에서, 간선 u->v를 사용하는 것이 이득인지 판단하는 검사이다.

즉, 시작 정점 s에서 v로 가는 기존 경로와 시작 정점 s에서 정점 u를 지나 v로 가는 경로 중 더 짧은 거리를 선택한다는 의미이다. (더 짧아지는 경우 갱신한다.)

distance[v] = min(distance[v], distance[u] + weight(u->v))

//u를 거치는 방법으로 갱신이 일어난 경우 "완화했다"라고 표현한다.

 

다익스트라 알고리즘

가중치 그래프에서 시작 정점과 다른 정점들 사이의 최단 경로를 구할 수 있는 다익스트라 알고리즘을 알아보자. 

우선순위 큐를 이용해서 이웃한 정점 중 가장 거리가 적은 정점(cost가 적은 정점)부터 탐욕적으로 방문하면서 최단거리를 갱신하는 방법이다.

 

우선순위큐 pq에는 정점의 정보 pair{distance[u](s와 정점 사이의 거리), u(정점의 번호}를 넣는다. 

 

 

1. 시작 정점을 제외한 모든 정점의 경로(거리) distance[x]를 무한대로 초기화해준다. (방문 안 함)

 

2. 시작 정점을 pq에 넣어준다. -> (0, s)

 

3. pq에서 꺼낸 어떤 정점 u에서, u에 대한 모든 이웃 정점 v에 대해 relax를 시도한다.
    이때, relax가 일어나면 v에 대한 새로운 정보를 pq에 저장한다.
    (**방문되지 않았던 점들을 방문할 때는 항상 relax 된다.)

 

4. pq에 아무것도 없을 때까지 pq에서 가장 적은 cost를 가진 정점을 꺼내 반복한다.
    (**긴 거리는 pq에서 나중에 삭제된다.)

 

 

코드는 다음과 같다.

void dijkstra(int s) {

	fill(dis, dis + V + 1, INF);
	priority_queue<pair<int, int>> pq; //{s와 정점 사이의 거리, 정점의 번호}
	distance[s] = 0;
	pq.push({ 0,s });

	while (!pq.empty()) {
		int d = -pq.top().first; // s와 정점 사이의 거리 
		int u = pq.top().second; // 정점
		pq.pop();
        
		if (d > dis[u]) //d가 (시작~정점)보다 더 크면 가지 않는다(느긋한 삭제)
			continue;
            
		for (int i = 0; i < adj[u].size();i++) {
			int v = adj[u][i].first; // 이웃 정점
			int c = adj[u][i].second; //(u->v cost)
			
			if (distance[v] > distance[u] + c) { // relax
				distance[v] = distance[u] + c;
				pq.push({ - distance[v], v });
			}
		}
	}
}

 

만약 주어진 그래프에 가중치가 음수인 간선이 존재한다면, 다익스트라 알고리즘으로는 최단 경로를 구할 수 없다. 

그렇다면 어떻게 최단 경로를 찾을 수 있을까?

 

벨만-포드 알고리즘

음수 사이클이 존재하는 그래프에 대해 단일 시작점 최단 경로를 구하는 알고리즘이다. 

-> 음수 사이클이란, 사이클 내에 음수인 간선이 존재하는 것을 말한다.

//음수사이클그래프

다익스트라에서 음수사이클이 존재하면 안되는 이유

-> 음수 사이클이 존재하면, 사이클내에서 최단거리를 갱신할 때 계속 음의 무한대로 발산한다.

 

- 모든 E개의 간선에 대해 V-1번** 완화 작업을 수행하면, 시작 정점으로부터 모든 정점의 최단경로를 구할 수 있다.

    ** 최단경로는 항상 V-1개의 간선을 가진다.

 

만일 V-1번의 완화 작업 후에 아직 완화할 수 있는 간선이 있다면, 음수 사이클이 존재한다는 뜻이다.

 

 

(+) 벨만 포드 알고리즘은 그래프의 모든 간선을 살펴보기 때문에, 다익스트라보다는 시간이 오래걸린다.

      하지만 음수 사이클이 존재하더라도 절대 무한 루프에 빠지지 않는다는 것을 보장한다!

 

 

코드는 다음과 같다.

bool Bellman_Ford(int s) {
	fill(dist, dist + v + 1, INF);
	dist[s] = 0;

	for (int i = 0;i < v-1;i++) {
		for (int j = 0;j < edge.size();j++) {

			int u = edge[j].second.first;
			int v = edge[j].second.second;
			long long cost = edge[j].first;

			//relax
			if (dist[u] != INF && dist[v] > dist[u] + cost) {
				dist[v] = dist[u] + cost;
			}
		}
	}
	
	//음의 사이클 확인
	for (int j = 0;j < edge.size();j++) {

		int u = edge[j].second.first;
		int v = edge[j].second.second;
		long long cost = edge[j].first;

		if (dist[u] != INF && dist[v] > dist[u] + cost) { // 아직도 relax가 가능하면 음수사이클
			dist[v] = -INF;
			return false;
		}
	}
	return true;
}