最短路径问题是图论研究中的一个经典算法问题, 旨在寻找图(由结点和路径组成的)中两结点之间的最短路径。 算法具体的形式包括:
确定起点的最短路径问题 - 即已知起始结点,求最短路径的问题。
确定终点的最短路径问题 - 与确定起点的问题相反,该问题是已知终结结点,求最短路径的问题。在无向图中该问题与确定起点的问题完全等同,在有向图中该问题等同于把所有路径方向反转的确定起点的问题。
确定起点终点的最短路径问题 - 即已知起点和终点,求两结点之间的最短路径。
全局最短路径问题 - 求图中所有的最短路径。
最常用的路径算法有:
1.Floyd算法。
2.Dijkstra算法。
3.Bellman-ford算法。
4.SPFA算法。
下面逐个来讲解。。。
1.Floyd算法。
求多源、无负权边的最短路。时效性较差,时间复杂度O(V^3)。空间复杂度为O(N^2)。是解决任意两点间的最短路径的一种算法。
核心代码:
- <span style="font-size:14px;">void floyd(int n)
- {
- int i,k,j;
- for(k=0; k<n; k++)//遍历所有的中间点
- for(i=0; i<n; i++)//遍历所有的起点
- for(j=0; j<n; j++)//遍历所有的终点
- if(map[i][j]>map[i][k]+map[k][j])//如果当前i-->j的距离大于i-->k--->j的距离之和
- map[i][j]=map[i][k]+map[k][j];//更新从i--->j的最短路径
- }</span>
2.Dijkstra算法
求单源、无负权的最短路。时效性较好,时间复杂度为O(V*V+E);
以贪心法选取未被处理的具有最小权值的节点,然后对其的出边进行松弛操作;
核心代码:
- int dijkstra (int v)
- {
- //初始化
- for (i=1;i<=n;i++)
- {
- vist[i]=0;
- dis[i]=map[v][i];
- }
- //进行n-1次操作 选择最短路
- for (i=1;i<n;i++)
- {
- mn=inf;
- //与当前点直接相连的点的最短路
- for (j=1;j<=n;j++)
- {
- if (!vist[j] && dis[j]<mn)
- {
- mn=dis[j];
- pos=j;
- }
- }
- vist[pos]=1;
- //松弛操作
- for (j=1;j<=n;j++)
- {
- if (!vist[j] && dis[j]>dis[pos]+map[pos][j])
- dis[j]=dis[pos]+map[pos][j];
- }
- }
- return dis[n];
- }
3.Bellman-ford算法
求单源最短路,可以判断有无负权回路(若有,则不存在最短路),时效性较好,时间复杂度O(VE)。和dijkstra一样都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。
它的原理是对图进行n-1次松弛操作,得到所有可能的最短路径。其优于dijkstra的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高。但是可以进行优化,提高效率。
负权环判定
因为负权环可以无限制的降低总花费,所以如果发现第n次操作仍可降低花销,就一定存在负权环。
核心代码:
- <pre name="code" class="html">struct node
- {
- int u;//起点
- int v;//终点
- int w;//权值
- } p[inf];
-
- int dis[inf];//用来保存远点至当前点的最短路径 迭代更新
- // 点数 边数 源点
- int nodenum,edgenum,source,i,j;
-
- //初始化
- void init ()
- {
-
- scanf ("%d%d%d",&nodenum,&edgenum,&source);
- for (i=0; i<=nodenum; i++)
- {
- dis[i]=inf;
- }
- dis[source]=0;
- for (i=0; i<edgenum; i++)
- {
- scanf ("%d%d%d",&p[i].u,&p[i].v,&p[i].w);
- if (dis[p[i].u]==source)
- dis[p[i].v]=p[i].w;
- }
- }
-
- int Bellman_ford ()
- {
- int flag=0;
- for (i=0; i<nodenum-1; i++) //n-1次循环找最短路
- {
- flag=0;
- for (j=0; j<edgenum; j++)
- {
- if (dis[p[j].v] > dis[p[j].u] + p[j].w)//松弛操作 更新dis数组
- {
- dis[p[j].v] = dis[p[j].u] + p[j].w;
- flag=1;
- }
- }
- if (flag==0)
- break;
- }
- //判断负环
- //上面两层for循环结束之后 dis数组里保存的就是远点到当前点的最短路
- //如果还能进行松弛操作的话就说明有负环存在
- for (i=0; i<edgenum; i++)
- {
- if (dis[p[i].v] > dis[p[i].u] + p[i].w)
- {
-
- return ;//存在负环 返回0 还是 1 根据题意而定
- }
- }
-
- }
4.SPFA算法是Bellman-Ford的队列优化,时效性相对好,时间复杂度O(kE)。(k<<V)。SPFA算法采用一系列的松弛操作以得到从某一个节点出发到达图中其它所有节点的最短路径。所不同的是,SPFA算法通过维护一个队列,使得一个节点的当前最短路径被更新之后没有必要立刻去更新其他的节点,从而大大减少了重复的操作次数。
核心代码:
- void SPFA(int s,int e) // s点 到 e点
- {
- int l,r,i;
- l=r=0;
- memset(vis,0,sizeof(vis));
- memset (dis,inf,sizeof (dis));
- dis[s]=0;
- q[r++]=s;//进队列
- vis[s]=1;//标记 进队列1
- //不在队列为0
- while(l<r)
- {
- int p=q[l++];//出队列
- for(i=0; i<n; i++)
- {
- if(dis[i] > dis[p] + map[p][i])
- {
- dis[i] = dis[p] + map[p][i];
- if(vis[i]==0)
- {
- q[r++] = i;
- vis[i] = 1;
- }
- }
- }
- vis[p] = 0;
- }
- if(dis[e]!= inf)
- printf("%d\n",dis[e]);
- else
- printf("-1\n");
- }