最短路问题三大算法及其优化算法总结+模板
前言
这里给了最短路问题中三大算法及其优化后的算法总结和模板,总结一下,以便后续学习。
Floyd-Warshall
多源最短路,即要求求出图中每两个顶点之间的最短路。虽然Floyed的复杂度是(O(n^3)),但是4行却简单很多,本质上是动态规划算法。
思想:从i号顶点到j号顶点只经过前k号顶点的最短路径。
const int inf=0x3f3f3f3f;
int Floyd()
{//初始化n个顶点
for(i = 1; i <= n; i ++)
for(j = 1; j <= n; j ++)
if(i == j)
e[i][j] = INF;
else
e[i][j] = 0;
for(k = 1; k <= n; k ++)//Floyd-Warshall算法核心语句
for(i = 1; i <= n; i ++)
for(j = 1; j <= n; j ++)
if(e[i][j] > e[i][k]+e[k][j])
e[i][j] = e[i][k]+e[k][j];
}
Bellman-Ford
Bellman-Ford算法能解决存在负权边的单源点最短路径问题。可以检测一个图是否存在负权回路:如果在进行了n-1轮松弛后,仍然可以继续成功松弛,说明存在负权边。
const int inf=0x3f3f3f3f;
struct node{
int from,to,w;
};
node edge[100];
bool Bellman(int s)
{
for(i = 1; i <= n; i ++)
dis[i] = inf;
dis[s]=0;
bool flag;
for(i = 1; i <= n; i ++)
{
flag = false;
for(j = 1; j <= m; j ++)
{
x = edge[j].from ;
y = edge[j].to ;
z = edge[j].w ;
if(dis[y] > dis[x] + z)
{
dis[y] = dis[x] + z;
flag = true;
}
}
if(!flag) //最好加上这句话,很重要
break;
//如果更新到n遍,还能够继续更新dis数组,说明存在负权环
if(flag&&i == n)
return false;//返回false表示这个图存在负权环
}
return true;//返回true表示求最短路成功
}
SPFA 使用队列优化的Bellman-Ford算法
每次实施一次松弛操作后,就会有一些顶点已经求得其最短路,此后这些顶点的最短路的估计值就会一直保持不变,不再受到后面松弛的影响,但是每次还要判断是否需要松弛,浪费了时间。
所以这里每次仅仅对最短路的估计值发生了变化了的顶点的所有出边执行松弛操作
//poj 3169 Layout 题解
const int inf=0x3f3f3f3f;
const int maxn=1e3+7;
struct edge
{
int to, cost;
};
int dis[maxn];
bool inq[maxn]; //判读一个点是否在队列中
int cnt[maxn];
vector<edge> g[maxn];
queue<int> que;
int n, ml, md;
void init()
{
for(int i=1; i<=n; i++)
{
g[i].clear();
inq[i]=false;
dis[i]=inf;
cnt[maxn]=0;
}
while(!que.empty()) que.pop();
}
bool spfa(int s, int n)
{
edge e;
inq[s]=true;
dis[s]=0;
que.push(s);
while(!que.empty())
{
int u=que.front();
que.pop();
inq[u]=false; //出队,不在队列中,赋值为false;
for(int i=0; i<g[u].size(); i++)
{
e=g[u][i];
if(dis[e.to] > dis[u]+e.cost)
{
dis[e.to]=dis[u]+e.cost;
if(!inq[e.to]) //如果之前没有在队列中,那么就可以入队了。
{
inq[e.to]=true;
que.push(e.to);
cnt[e.to]++;
if(cnt[e.to]>=n) return false;//说明有负环
}
}
}
}
return true;//没有负环,并且完成最短路
}
Dijkstra
Dijkstra算法适合不含负权边的单源最短路(单源最短路是指从源点到其余各个顶点的最短路径)。
思想:每次找到离源点最近的一个顶点,然后以该顶点为中心进行拓展,最终得到源点到其余所有点的最短路径
void dij(int s)
{
for(int i=1; i<=n; i++)
dis[i]=road[s][i];
vis[s]=1;
dis[s]=0; //这句话也可以不加
for(int i=1; i<n; i++)
{
int tmp=inf, k;
for(int j=1; j<=n; j++)
{
if(!vis[j] && dis[j] < tmp)
{
tmp=dis[j];
k=j;
}
}
if(tmp==inf) //如果没有更新成功,那么接下来也不会再次更新,可以结束循环了。
break;//这句话以后就必须加上了。
vis[k]=1;
for(int j=1; j<=n; j++)
{
if(!vis[j] && dis[j] > dis[k]+road[k][j])
dis[j]=dis[k]+road[k][j];
}
}
}
使用优先队列+邻接表优化的Dijkstra算法
//邻接表+优先队列优化 题目poj 2387 til the cows come home
const int maxn=1e3+7;
const int inf=0x3f3f3f3f;
struct edge{
int to, cost;
};
struct node{
int d, u;
bool friend operator < (const node a, const node b)
{
return a.d > b.d;
}
};
int dis[maxn];
int vis[maxn];
vector<edge> g[maxn];
priority_queue<node> que;
int t, n;
void init()
{
for(int i=1; i<=n; i++)
{
g[i].clear();
vis[i]=0;
dis[i]=inf;
}
while(!que.empty()) que.pop();
}
void dij(int s)
{
int u;
edge e;
dis[s]=0;
node tmp={0, s}, next;
que.push(tmp);
while(!que.empty())
{
tmp=que.top();
que.pop();
u=tmp.u;
if(vis[u]==1) continue;
vis[u]=1;
for(int i=0; i<g[u].size(); i++)
{
e=g[u][i];
if(!vis[e.to] && dis[e.to] > dis[u]+e.cost)//注意要加判断是否访问
{
dis[e.to]=dis[u]+e.cost;
next.d=dis[e.to];
next.u=e.to;
que.push(next);
}
}
}
}