问题描述
请编写一个程序,求给定加权有向图 G = ( V , E ) G=(V,E) G=(V,E)的单源最短路径的成本。请以G的顶点0为起点,输出0到各顶点v的最短路径上各边权值的总和d[v]。
输入: 第1行输入G的顶点数n。接下来n行按如下格式输入各顶点u的邻接表
u k v1 c1 v2 c2 … vk ck
G中的各顶点编号分别为0至n-1。u代表顶点的编号,k代表u的出度。vi(i = 1, 2, …, k)代表与u相邻顶点的编号,ci代表u到vi的有向边的权值。
输出: 按顺序输出各顶点编号v及距离d[v],相邻数据间用1个空格隔开。
限制:
1 ≤ n ≤ 10000
0 ≤ ci ≤ 100000
|E| < 500000
0到各顶点之间必然存在最短路径。
输入示例
5
0 3 2 3 3 1 1 2
1 2 0 2 3 4
2 3 0 3 3 1 4 1
3 4 2 1 0 1 1 4 4 3
4 2 2 1 3 3
输出示例
0 0
1 2
2 2
3 1
4 3
讲解
单源最短路径1 | Dijkstra狄克斯特拉 | Single Source Shortest Path 1 | C/C++实现中我们实现了一般形式的狄克斯特拉算法。这个算法由邻接矩阵实现,因此需要花费O(|V|)来给顶点u搜索相邻顶点v。此外,选择顶点u添加至最短路径树S的循环需要进行|V|次,所以算法复杂度为O(|V|^2)。即便改用邻接表,复杂度也不会有改观。
其实,只要我们采用邻接表表示法,在对二叉堆加以应用,就可以让狄克斯特拉算法的效率产生质的飞跃。
应用二叉堆实现的狄克斯特拉算法如下:
设图 G = ( V , E ) G=(V,E) G=(V,E)所有顶点的集合为V,起点为s,最短路径树种包含的顶点集合为S。在各计算步骤中,我们将选出最短路径树的边和顶点并将其添加至S。
对于各顶点 i ,设仅经由S内顶点的s到 i 的最短路径成本为d[i],i 在最短路径树中的父结点为p[i]。
1.初始状态下将S置空。初始化s的d[s] = 0;除s以外,所有属于V的顶点 i 的d[i] = 无限大。以d[i]为键值,将V的顶点构建成最小堆H。
2.循环进行下述处理,直至S = V为止
从H中取出d[u]最小的顶点u
将u添加至S,同时将与u相邻且属于V - S的所有顶点v的值按照下述方式更新
if d[u] + w(u, v) < d[v]
d[v] = d[u] + w(u, v)
p[v] = u
以v为起点更新堆H
该算法可通过下述方式实现:
dijkstra(s)
将所有顶点u的color[u]设为WHITE,d[u]初始化为INFTY
d[s] = 0
Heap heap = Heap(n, d)
heap.construct()
while heap.size >= 1
u = heap.extractMin()
color[u] = BLACK
//如果仍存在与u相邻的顶点v
while(v = next(u) ) != NIL
if color[v] != BLACK
if d[u] + M[u][v] < d[v]
d[v] = d[u] + M[u][v]
color[v] = GRAY
heap.update(v)
我们可以像下面这样,用优先级队列代替二叉堆,将候选顶点插入队列:
dijkstra(s)
将所有顶点u的color[u]设为WHITE,d[u]初始化为INFTY
d[s] = 0
PQ.push(Node(s, 0) )//将起点插入优先级队列
//选s作为最开始的u
while PQ 不为空
u = PQ.extractMIN()
color[u] = BLACK
if d[u] < u的成本//取出最小值,如果不是最短路径则忽略
continue
//如果仍存在与u相邻的顶点v
while(v = next(u) ) != NIL
if color[v] != BLACK
if d[u] + M[u][v] < d[v]
d[v] = d[u] + M[u][v]
color[v] = GARY
PQ.push(Node(v, d[v]) )
AC代码如下
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
static const int MAX = 10000;
static const int INFTY = (1<<20);
static const int WHITE = 0;
static const int GRAY = 1;
static const int BLACK = 2;
int n;
vector<pair<int, int> > adj[MAX];//加权有向图的邻接表表示法
void dijkstra(){
priority_queue<pair<int, int> > PQ;
int color[MAX];
int d[MAX];
for(int i = 0; i < n; i++){
d[i] = INFTY;
color[i] = WHITE;
}
d[0] = 0;
PQ.push(make_pair(0, 0));
color[0] = GRAY;
while( !PQ.empty() ){
pair<int, int> f = PQ.top(); PQ.pop();
int u = f.second;
color[u] = BLACK;
//取出最小值,如果不是最短路径则忽略
if(d[u] < f.first * (-1) ) continue;
for(int j = 0; j < adj[u].size(); j++){
int v = adj[u][j].first;
if(color[v] == BLACK) continue;
if(d[v] > d[u] + adj[u][j].second){
d[v] = d[u] + adj[u][j].second;
//priority_queue默认优先较大值,因此要乘以-1
PQ.push(make_pair(d[v] * (-1), v));
color[v] = GRAY;
}
}
}
for(int i = 0; i < n; i++){
cout<<i<<" "<<(d[i] == INFTY ? -1 : d[i])<<endl;
}
}
int main(){
int k, u, v, c;
cin>>n;
for(int i = 0; i < n; i++){
cin>>u>>k;
for(int j = 0; j < k; j++){
cin>>v>>c;
adj[u].push_back(make_pair(v, c));
}
}
dijkstra();
return 0;
}