最短路最常用的算法有:
单源最短路: Bellman-Ford 算法,Dijkstra 算法,SPFA 算法。
任意两点间最短路:Floyd算法。
Bellman-Ford 可以处理有负边的情况,也可以处理负圈。最多进行V - 1次迭代操作,如果第V次还进行更新操作,说明存在负圈。
Dijkstra 不仅不能处理负圈,连有负边都不能。
Floyd 也可以处理负边的情况。判断负圈只需检查最dp[i][i]是否存在负数的顶点i即可。
SPFA 可以处理负边的情况,是Bellman-Ford的一种队列实现。判断负圈只需判断顶点入队列次数,超过V次就存在负圈。
Bellman-Ford 算法:时间复杂度:O(VE)
模板:
const int INF = 0x3f3f3f3f;
int d[MAX_V];//最大顶点数,设置为题目中顶点数的最大值
int V, E;//顶点数;边数
struct Edge{
int from;
int to;
int cost;
Edge(int _from = 0, int _to = 0, int _cost = 0) : from(_from), to(_to), cost(_cost){}
}
Edge es[MAX_E];
bool bellman_ford(int s){//true:有负圈 false:没有负圈
for(int i = 1; i <= V; ++i){//定点编号从1开始
d[i] = INF;
}
d[s] = 0;//d[i]为从s到i的最短路
for(int i = 1; i < V; ++i){//最多做V-1次
bool flag = false;
for(int j = 0; j < E; ++j){
Edge e = es[i];
if(d[e.to] > d[e.from] + e.cost){
d[e.to] = d[e.from] + e.cost;
flag = true;
}
}
if(!flag)
return false;
}
for(int i = 0; i < E; ++i){
Edge e = es[i];
if(d[e.to] > d[e.from] + e.cost)
return true;
}
return false;
}
Dijkstra 算法:O(V2)或O(ElogV)
未优化:O(V2)
const int INF = 0x3f3f3f3f;
int cost[MAX_V][MAX_V];
bool used[MAX_V];//使用过的顶点
int d[MAX_V];//最大顶点数,设置为题目中顶点数的最大值
int prev[MAX_V];//最短路上的前驱顶点
int V;//顶点数
void dijkstra(int s){
for(int i = 0; i < V; ++i){
d[i] = INF;
used[i] = false;
pre[i] = -1;
}
d[s] = 0;
for(int i = 1; i < V; ++i){//最多走
int k = -1;
for(int j = 0; j < V; ++j){
if(!used[j] && d[j] < d[k]){
k = j;
}
}
if(k == -1) break;
used[k] = true;
for(int j = 0; j < V; ++j){
if(d[j] > d[k] + cost[k][j]){
d[j] = d[k] + cost[k][j];
prev[j] = k;
}
}
}
}
堆优化:O(ElogV)
const int INF = 0x3f3f3f3f;
int d[MAX_V];//最大顶点数,设置为题目中顶点数的最大值
int V;//顶点数
struct Edge{
int to;
int cost;
Edge(int _v = 0, int _cost = 0) : v(_v), cost(_cost){}
}
typedef pair<int, int> P;
vector<Edge> G[MAX_V];
void dijkstra(int s){
priority_queue<P, vector<P>, greater<P>> que;
for(int i = 0; i < V; ++i){
d[i] = INF;
}
d[s] = 0;
que.push(P(0, s));
while(!que.empty()){
P p = que.top();
que.pop();
int v = p.second;
if(d[v] < p.first) continue;
for(int i = 0; i < G[v].size(); ++i){
Edge e = G[v][i];
if(d[e.to] > d[v] + e.cost){
d[e.to] = d[v] + e.cost;
que.push(P(d[e.to], e.to));
}
}
}
}
void addEdge(int s, int t, int cost){
G[s].push_back(Edge(t, cost));
}
Floyd 算法:O(V3)
Floyd实质上为dp问题。假设dp[i][j]为i与j之间的最短路,然后依次取集合中剩余点k,比较dp[i][j]和dp[i][k] + dp[k][j],即最短路要么不经过点k,要么经过点k。得到递推式:dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])。这是一个O(n3)时间复杂度的算法。
模板:
const int INF = 0x3f3f3f3f;//巧妙设置无穷大值
int dp[MAX_V][MAX_V];//邻接矩阵表
int V;//顶点数
void floyd(){
for(int k = 0; k < V; ++k){
for(int i = 0; i < V; ++i){
for(int j = 0; j < V; ++j){
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}
}
}
}
SPFA 算法:O(KE) K为所有顶点进入队列的平均次数,一般小于等于2
const int INF = 0x3f3f3f3f;
struct Edge{
int to;
int cost;
Edge(int _v = 0, int _cost = 0) : v(_v), cost(_cost){}
}
vector<Edge> G[MAX_V];
int d[MAX_V];//最大顶点数,设置为题目中顶点数的最大值
bool used[MAX_V];
int cnt[MAX_V];
int pre[MAX_V];
int V;//顶点数
bool SPFA(int s){
for(int i = 0; i < V; ++i){
d[i] = INF;
cnt[i] = 0;
used[i] = false;
pre[i] = -1;
}
d[s] = 0;
cnt[i] = 1;
pre[i] = s;
used[s] = true;
queue<int> que;
que.push(s);
while(!que.empty()){
int v = que.front();
que.pop();
used[v] = false;
for(int i = 0; i < G[v].size(); ++i){
Edge e = G[v][i];
if(d[e.to] > d[v] + e.cost){
d[e.to] = d[v] + e.cost;
pre[e.to] = v;
if(!used[v]){
used[v] = true;
que.push(v);
if(++cnt[v] >= V)//入队列次数超过顶点数,则存在负圈
return true;
}
}
}
}
return false;
}
void addEdge(int s, int t, int cost){
G[s].push_back(Edge(t, cost));
}