您的位置:首页 > 其它

「关于单源最短路径的三种算法的思考(不包括计算多点路径的Floyd算法)」

2018-02-13 00:55 323 查看
从Bellman-Ford算法(可以处理负值,检测负权回路,但复杂度过高)开始,Bellman-ford算法是求含负权图的单源最短路径算法;效率很低,但代码很容易写;复杂度很高:O(VE) v为顶点数,e为边数。思想:进行不停地松弛,每次松弛把每条边都更新一下,直到一次遍历所有边的松弛操作无法更新结点间的最短距离。若n-1次松弛后还能更新,则说明图中有负环(因为最短路不会同时经过同一个点两次,也就是说最多通过(n-1)条边),无法得出结果,否则就成功完成。算法描述:对每条边进行|V|-1次松弛操作;如果存在(u,v)∈E使得dis[u]+w<dis[v],则存在负权回路;否则dis[v]即为s到v的最短距离,pre[v]为前驱。void relax(int u, int v, int weight){ // 松弛计算
if(dist[v] > dist[u] + weight)
dist[v] = dist[u] + weight;}
bool Bellman_Ford(){
for(int i=1; i<=nodenum-1; ++i)
for(int j=1; j<=edgenum; ++j)
relax(edge[j].u, edge[j].v, edge[j].weight);
bool flag = 1;
for(int i=1; i<=edgenum; ++i) // 判断是否有负环路
if(dist[edge[i].v] > dist[edge[i].u] + edge[i].weight){    //如果在nodenum-1次循环后还是可以进行松弛操作,则说明有负权回路
flag = 0; break;
} return flag;
}Dijkstra则是对Bellman-Ford算法的一种优化,是一种解决单源最短路径的贪心算法。
解决的问题:    带权重的有向图上单源最短路径问题。且权重都为非负值同时适用于无向图和有向图。如果采用的实现方法合适,Dijkstra运行时间要低于Bellman-Ford算法和Spfa,因为竞赛中可能会造卡SPFA的数据,故我们通常采用Dijkstra算法。
其核心为:1.找到最短距离已经确定的点,并将其放入一个集合中,从它出发更新相邻顶点之间的距离。
                    2.此后不需再关心已经被放入集合中的点,当所有点都被放入集合中时,源点到所有点的最短距离则已被得出。
基本思想: 首先设置一个集合S;用数组dist[]来记录v到S中各点的目前最短路径长度。然后不断地用贪心选择来扩充这个集合,并同时记录或修订数组dist[];直至S包含所有V中顶点。
解题过程图示:https://61mon.com/index.php/archives/194/comment-page-2#comment-509 
其复杂度为O(N^2),如果用堆优化则可达到O(|E|log|V|)。
此外,Dijkstra还可以很方便的给出最短路径的具体表示(代码下次完善时再写)
下面给出代码:void dijkstra(int start)//从start点开始
{
int i,j,k;
memset(vis,0,sizeof(vis));//标记是否访问过
for(i=1; i<=n; i++)//n为总点数
{
if(i==start)
dis[i]=0;
else
dis[i]=INF;
}
for(i=1; i<=n; i++)
{
int r;
int min=INF;
for(j=1; j<=n; j++)
if(!vis[j]&&dis[j]<min)
{
min=dis[j];
r=j;
}
vis[r]=1;
for(k=1; k<=n; k++)//对所有从r出发的边进行松弛
if(dis[k]<(dis[r]+g[r][k]))
dis[k]=dis[k];
else
dis[k]=dis[r]+g[r][k];
}
return;
}使用优先队列优化代码:struct edge{int to,cost;};
typedef pair<int,int>P;
int v;
vector<edge>G[MAX_V];
int d[MAX_V];
void dijkstra(int s)
{
priority_queue<P,vector<P>,greater<P> >que; //通过指定greater<P>参数,堆按照从小到大的顺序取出值。
/*当所有边的权值相同时,这种情况下,Dijkstra算法使用优先队列与普通队列的的效果是相同的,但算法复杂的会发生变化。需要注意。*/
fill(d,d+V,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));
}
}
}
}

SPFA算法(可以用来处理负权路径,并检测负权回路)
主要思想:动态逼近法,用一个队列来保存所有待优化的结点,并且每次取出队列首位的点,将其邻接的所有点进行松弛操作,如果所操作的点的最短路径有所调整,且该点不在队中,则该被操作的点入队。直到该队列为空时,则该算法结束。
这个算法,简单的说就是队列优化的bellma
c122
n-ford
,利用了每个点不会更新次数太多的特点发明的此算法。
算法复杂度:O(ke) (e为边数)最坏情况下复杂度为 O(n^2)SPFA 在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。设一个点用来作为迭代点对其它点进行改进的平均次数为k,有办法证明对于通常的情况,k在2左右。实现方法:建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空
上代码:(记不得是哪道题的了。。。但是可以拿来做spfa的模版。。。)#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
const int mod = (int) 1e9+7;

int n,m,dis[205][205],dist[205];
bool inq[205]; //inq[i]代表i是不是在队列中
void spfa(){
queue<int> q;
dist[1]=0;
q.push(1);
inq[1]=1;
while(!q.empty()){
int u=q.front();
q.pop();
inq[u]=0; //u点已经离开队列,就把inq[u]置为0
for(int i=0;i<n;i++){
if(dist[i]-dis[u][i]>dist[u]){
dist[i]=dist[u]+dis[u][i]; //更新i点到起点的距离
if(inq[i])continue; //如果已经在队列里,跳过后续步骤
q.push(i); //把i点放入队列
inq[i]=1; //更新inq[i]
}
}
}
}

int main()
{
while(~scanf("%d%d",&n,&m)){
if(n==0&m==0)
break;
memset(dis,125,sizeof(dis));                //对所有点的最短路径的初始化,很重要
memset(dist,125,sizeof(dist));             //输入边之前的各点距离的初始化,很重要
for(int i=0;i<n;i++)dis[i][i]=0;
int x,y,z;
for(int i=0;i<m;i++){
scanf("%d%d%d",&x,&y,&z);
dis[x][y]=dis[y][x]=min(dis[x][y],z);         //此题为双向图,且两点之间可能不止一条双向的路径。
}
spfa();
printf("%d\n",dist
);
}
return 0;
}再来说一下spfa检测负权回路。。。因为每一个点进入队列的次数最多为n-1次(因为最短路不会同时经过同一个点两次,也就是说最多通过(n-1)条边,故对同一个点进入队列不会超过n-1次),故 
若一个点入队次数超过n,则有负权环。最后说一下最短路径本身怎么输出
    在一个图中,我们仅仅知道结点A到结点E的最短路径长度,有时候意义不大。这个图如果是地图的模型的话,在算出最短路径长度后,我们总要说明“怎么走”才算真正解决了问题。如何在计算过程中记录下来最短路径是怎么走的,并在最后将它输出呢?
    我们定义一个path[]数组,path[i]表示源点s到i的最短路程中,结点i之前的结点的编号(父结点),我们在借助结点u对结点v松弛的同时,标记下path[v]=u,记录的工作就完成了。
    如何输出呢?我们记录的是每个点前面的点是什么,输出却要从最前面到后面输出,这很好办,递归就可以了: 
c++ code:
void printpath(int k){
if (path[k]!=0) printpath(path[k]);
cout << k << ' ';
}


Floyd没什么好说的了。。。查找多点间的最短路径。。。复杂度O(N^3)
for (k=1;k<n;k++)
for (i=1;i<n;i++)
for (j=1;j<n;j++)
if (d[i][j]>d[i][k]+d[k][j])
d[i][j]=d[i][k]+d[k][j];                            //可以在此if语句中加一个path[]数组用以存储路径,简单,在此不赘述。
求最长路:将Bbellman-Ford或者SPFA算法中边的权值改为相反值即为求最长路。得到结果后取相反值即可。(不能用dijkstra)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐