最短路径
来源:互联网 发布:c语言是c 的基础吗 编辑:程序博客网 时间:2024/06/10 18:15
最短路径
有四种方法:Floyd、dijkstra、bellman_ford、SPFA算法;(最后两个可以判断是否具有负权回路)
方1:Floyd(或弗洛伊德)
目的:
寻找给定的加权图中多源点之间最短路径。
原理:
利用动态规划的思想。即,通过一个图的权值矩阵求出它的每两点间的最短路径矩阵。
时间复杂度:O(n^3);
核心代码:
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(mp[i][j]>mp[i][k]+mp[k][p])
mp[i][j]=mp[i][k]+mp[k][p];
k:中间转点;
思路:利用三重循环;一次打开中间转点;for(k=1;k<=n;k++);
那我每打开一个中间转点,我就把我的地图遍历一遍,把可以连通的点的值加以更新,最终我们把所有点都打开了,就可以获得任意两个点之间的最短距离;
注:
这里值得我们注意的是在初始化时,mp地图应该被初始为INF,(INF指无限大);#define INF 100000000
记住不要用memset(mp,INF,sizeof(mp));值不稳定!
应该是:for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j)mp[i][j]=0;
else
mp[i][j]=INF;
方2:dijkstra迪杰斯特拉算法(或狄克斯特拉算法)
目的:是从一个顶点到其余各顶点的最短路径.
原理:用一维数组建立1~n之间的最短路径.
时间复杂度:o(n^2);
思路:首先我要清楚我需要用到哪些成员:
vis[]:判断是否访问过顶点;
mp[][]:记录地图的,即建立各点之间的关系;
p:记录我找符合条件的所在点;目的:再继续比较目标层与dis[]的关系;
一开始,我就把1号与各点的长度依次赋值给dis[],然后我通过找到与1号点最近的点,然后对最近的点继续找下去看看需不需要更新1-n点长度,如果当前点j比(前驱点+已确定的值)要大,则我们更新dis[]所在1~j之间的长度;
当然我们每确定1~k那个k的顶点时,我们就标记一下,以后不再访问;
核心代码:
n:有n个顶点
for(i=1;i<=n;i++)
dis[i]=map[1][i];//dis[]含义是指1号点到各点的路径长;
for(i=1;i<=n;i++)
{
min=INF;
for(j=1;j<=n;j++)
{
if(!vis[j]&&min>dis[j])//查找1号点到各点的最小值;
{
min=dis[j];
p=j;
}
}
vis[p]=1;
for(k=1;k<=n;k++)
if(dis[k]>mp[p][k]+dis[p]&&mp[p][k]<INF)
dis[k]=mp[p][k]+dis[p];//继续找1号对应的最小权的点,所对应的层的值,一次对比一下,判断是否更新dis的值,到达松弛的效果;
}
}
一共我需要循环n次,dis被更新的次数最多是n-1次;
方3:Bellman– ford(或贝尔曼-福特算法)
目的:求含负权图的单源最短路径。
原理:
连续进行松弛,在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。
思路:找与它有联系的点且联系点已确定,比较与源点的距离大小,比本来的小就更新;
核心代码:
for(i=1;i<=n;i++)
scanf(“%d %d %d”,&u[i],&v[i],&w[i]);
for(k=1;k<n;k++)//n:点数
{
check=0;//检查是否更新;
for(j=1;j<=m;j++)//m边数
if(dis[v[j]]>dis[u[j]]+w[j])
{
dis[v[j]]=dis[u][j]]+w[j];
check=1;
}
if(check==0)break;//没有更新,就直接跳出;
}
下面我们检查负回权路
int flag=0;
for(i=1;i<=m;i++)
{
if(dis[v[i]]>dis[u[i]]+w[i])//表示更新n-1次之后我还能继续更新,还有最小值,就不符合有最短路的情况;so,是负权回路!
{
flag=1;//标记有负权回路;
}
}
方四:SPFA算法:
目的:求距离源点的最短路径。同时SPFA可以处理负权边。
原理:运用队列,首先找到源点,将它入列,同时我还需要标记vis[u]中u这个点是否在列中,我先找到离源点最近的点,比较我已经确定的点(亦可称为前驱点)+前驱点与该点边权值与该点本来具有的值相比较,然后更新dis[],使dis[]松弛到最小;
基本步骤:
初始化+松弛
初始化: d数组全部赋值为INF(无穷大);p数组全部赋值为s(即源点),或者赋值为-1,表示还没有知道前驱
然后d[s]=0; 表示源点不用求最短路径,或者说最短路就是0。将源点入队;
顶点入队标记vis数组,有顶点出队消除标记;
思路:
用dis[]数组记录每个结点的最短路径估计值,用邻接链表储存图,主要采用的思想是动态逼近法,运用队列保存待优化的结点,优化时每次取队首结点u,并且u点当前的最短路径估计值对离开u点所指向的结点的v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾,就是继续不断进行取队首,然后找到与其相匹配的最短路径点,判断是否need更新,同时还要标记是否在队列中;
主要元素:
队列+松弛
读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队
以此循环,直到队空为止就完成了单源最短路的求解
网上证明:
每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕)
期望的时间复杂度O(ke),其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。
判断有无负环:
如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
代码实现:
bfs_SPFA:
int spfa_bfs(int s)
{
queue <int> q;
memset(d,INF,sizeof(d));
d[s]=0;
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
q.push(s); vis[s]=1; cnt[s]=1;
//顶点入队vis要做标记,另外要统计顶点的入队次数
int OK=1;
while(!q.empty())
{
int x;
x=q.front(); q.pop(); vis[x]=0;
//队头元素出队,并且消除标记
for(int k=f[x]; k!=0; k=nnext[k]) //遍历顶点x的邻接表
{
int y=v[k];
if( d[x]+w[k] < d[y])
{
d[y]=d[x]+w[k]; //松弛
if(!vis[y]) //顶点y不在队内
{
vis[y]=1; //标记
c[y]++; //统计次数
q.push(y); //入队
if(c[y]>NN) //超过入队次数上限,说明有负环
return OK=0;
}
}
}
}
return OK;
}
dfs_SPFA:
int spfa_dfs(int u)
{
vis[u]=1;
for(int k=f[u]; k!=0; k=e[k].next)
{
int v=e[k].v,w=e[k].w;
if( d[u]+w < d[v] )
{
d[v]=d[u]+w;
if(!vis[v])
{
if(spfa_dfs(v))
return 1;
}
else
return 1;
}
}
vis[u]=0;
return 0;
}
最小生成树:
构成最小生成树条件:
1).尽可能去权值小的边,但不可构成回路;
2).选取N-1条边连通N个顶点;
prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。
最小生成树之prim算法
边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权。
最小生成树(MST):权值最小的生成树。
生成树和最小生成树的应用:要连通n个城市需要n-1条边线路。可以把边上的权值解释为线路的造价。则最小生成树表示使其造价最小的生成树。
构造网的最小生成树必须解决下面两个问题:
1、尽可能选取权值小的边,但不能构成回路;
2、选取n-1条恰当的边以连通n个顶点;
MST性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
1.prim算法
基本思想:假设G=(V,E)是连通的,TE是G上最小生成树中边的集合。算法从U={u0}(u0∈V)、TE={}开始。重复执行下列操作:
在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE中,同时v0并入U,直到V=U为止。
此时,TE中必有n-1条边,T=(V,TE)为G的最小生成树。
Prim算法的核心:始终保持TE中的边集构成一棵生成树。
注意:prim算法适合稠密图,其时间复杂度为O(n^2),其时间复杂度与边得数目无关,而kruskal算法的时间复杂度为O(eloge)跟边的数目有关,适合稀疏图。
看了上面一大段文字是不是感觉有点晕啊,为了更好理解我在这里举一个例子,示例如下:
(1)图中有6个顶点v1-v6,每条边的边权值都在图上;在进行prim算法时,我先随意选择一个顶点作为起始点,当然我们一般选择v1作为起始点,好,现在我们设U集合为当前所找到最小生成树里面的顶点,TE集合为所找到的边,现在状态如下:
U={v1}; TE={};
(2)现在查找一个顶点在U集合中,另一个顶点在V-U集合中的最小权值,如下图,在红线相交的线上找最小值。
通过图中我们可以看到边v1-v3的权值最小为1,那么将v3加入到U集合,(v1,v3)加入到TE,状态如下:
U={v1,v3}; TE={(v1,v3)};
(3)继续寻找,现在状态为U={v1,v3}; TE={(v1,v3)};在与红线相交的边上查找最小值。
我们可以找到最小的权值为(v3,v6)=4,那么我们将v6加入到U集合,并将最小边加入到TE集合,那么加入后状态如下:
U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}; 如此循环一下直到找到所有顶点为止。
(4)下图像我们展示了全部的查找过程:
2.prim算法程序设计
(1)由于最小生成树包含每个顶点,那么顶点的选中与否就可以直接用一个数组来标记used[max_vertexes];(我们这里直接使用程序代码中的变量定义,这样也易于理解);当选中一个数组的时候那么就标记,现在就有一个问题,怎么来选择最小权值边,注意这里最小权值边是有限制的,边的一个顶点一定在已选顶点中,另一个顶点当然就是在未选顶点集合中了。我最初的一个想法就是穷搜了,就是在一个集合中选择一个顶点,来查找到另一个集合中的最小值,这样虽然很易于理解,但是很明显效率不是很高,在严蔚敏的《数据结构》上提供了一种比较好的方法来解决:设置两个辅助数组lowcost[max_vertexes]和closeset[max_vertexes],lowcost[max_vertexes]数组记录从U到V-U具有最小代价的边。对于每个顶点v∈V-U,closedge[v], closeset[max_vertexes]记录了该边依附的在U中的顶点。
注意:我们在考虑两个顶点无关联的时候设为一个infinity 1000000最大值。
说了这么多,感觉有点罗嗦,还是发扬原来的风格举一个例子来说明,示例如下:
过程如下表:顶点标号都比图中的小1,比如v1为0,v2为1,这里首先选择v1点。
Lowcost[0]
Lowcost[1]
Lowcost[2]
Lowcost[3]
Lowcost[4]
Lowcost[5]
U
V-U
closeset
v1,infinity
v1,6
v1,1
v1,5
v1,infinity
v1,infinity
v1
v1,v2,v3,v4,v5,v6
从这个表格可以看到依附到v1顶点的v3的Lowcost最小为1,那么选择v3,选择了之后我们必须要更新Lowcost数组的值,因为记录从U到V-U具有最小代价的边,加入之后就会改变。这里更新Lowcost和更新closeset数组可能有点难理解,
for (k=1;k<vcount;k++)
if(!used[k]&&(G[j][k]<lowcost[k]))
{ lowcost[k]=G[j][k];
closeset[k]=j; }
}
j为我们已经选出来的顶点,如果G[j][k]<lowcost[k],则意味着最小权值边发生变化,更新该顶点的最小lowcost权值,依附的顶点肯定就是刚刚选出的顶点j,closeset[k]=j。
Lowcost[0]
Lowcost[1]
Lowcost[2]
Lowcost[3]
Lowcost[4]
Lowcost[5]
U
V-U
closeset
v1,infinity
v1,6
v1,1
v1,5
v3,6
v3,4
v1,v3
v1,v2,v4,v5,v6
这样一直选择下去直到选出所有的顶点。
(2)上面把查找最小权值的边结束了,但是这里有一个问题,就是我们没有存储找到的边,如果要求你输出找到的边那么这个程序就需要改进了,我们刚开始的时候选取的是v1作为第一个选择的顶点,那我们设置一个father[]数组来记录每个节点的父节点,当然v1的父节点肯定没有,那么我们设置一个结束标志为-1,每次找到一个新的节点就将它的父节点设置为他依附的节点,这样就可以准确的记录边得存储了。
语法:prim(Graph G,int vcount,int father[]);
参数:
G:图,用邻接矩阵表示
vcount:表示图的顶点个数
father[]:用来记录每个节点的父节点
返回值:
null
注意:
常数max_vertexes为图最大节点数
常数infinity为无穷大
数组存储从0开始
如果下面的源程序有错请参照测试程序。
源程序:
#define infinity1000000
#definemax_vertexes 5
typedef intGraph[max_vertexes][max_vertexes];
void prim(GraphG,int vcount,int father[])
{
int i,j,k;
int lowcost[max_vertexes];
intcloseset[max_vertexes],used[max_vertexes];
int min;
for (i=0;i<vcount;i++)
{
/* 最短距离初始化为其他节点到1号节点的距离 */
lowcost[i]=G[0][i];
/* 标记所有节点的依附点皆为默认的1号节点 */
closeset[i]=0;
used[i]=0;
father[i]=-1;
}
used[0]=1; /*第一个节点是在U集合里的*/
/* vcount个节点至少需要vcount-1条边构成最小生成树 */
for (i=1;i<=vcount-1;i++)
{
j=0;
min= infinity;
/* 找满足条件的最小权值边的节点k */
for (k=1;k<vcount;k++)
/* 边权值较小且不在生成树中 */
if((!used[k])&&(lowcost[k]<min))
{
min = lowcost[k];
j=k;
}
father[j]=closeset[j];
used[j]=1;;//把第j个顶点并入了U中
for (k=1;k<vcount;k++)
/* 发现更小的权值 */
if(!used[k]&&(G[j][k]<lowcost[k]))
{
lowcost[k]=G[j][k];/*更新最小权值*/
closeset[k]=j;/*记录新的依附点*/
}
}
}
测试程序:
测试用例:
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
5 6 6
4 6 2
#include<stdio.h>
#include<string.h>
#include <stdlib.h>
#define infinity1000000
#definemax_vertexes 6
typedef intGraph[max_vertexes][max_vertexes];
void prim(GraphG,int vcount,int father[])
{
int i,j,k;
int lowcost[max_vertexes];
intcloseset[max_vertexes],used[max_vertexes];
int min;
for(i=0;i<vcount;i++)
{
/* 最短距离初始化为其他节点到1号节点的距离 */
lowcost[i]=G[0][i];
/* 标记所有节点的依附点皆为默认的1号节点 */
closeset[i]=0;
used[i]=0;
father[i]=-1;
}
used[0]=1; /*第一个节点是在s集合里的*/
/* vcount个节点至少需要vcount-1条边构成最小生成树 */
for (i=1;i<=vcount-1;i++)
{
j=0;
min = infinity;
/* 找满足条件的最小权值边的节点k */
for (k=1;k<vcount;k++)
/* 边权值较小且不在生成树中 */
if ((!used[k])&&(lowcost[k]<min))
{
min = lowcost[k];
j=k;
}
father[j]=closeset[j];
printf("%d%d\n",j+1,closeset[j]+1);//打印边
used[j]=1;;//把第j个顶点并入了U中
for(k=1;k<vcount;k++)
/* 发现更小的权值 */
if(!used[k]&&(G[j][k]<lowcost[k]))
{
lowcost[k]=G[j][k];/*更新最小权值*/
closeset[k]=j;;/*记录新的依附点*/
}
}
}
int main()
{
FILE *fr;
int i,j,weight;
Graph G;
intfatheer[max_vertexes];
for(i=0;i<max_vertexes; i++)
for(j=0;j<max_vertexes; j++)
G[i][j] = infinity;
fr =fopen("prim.txt","r");
if(!fr)
{
printf("fopenfailed\n");
exit(1);
}
while(fscanf(fr,"%d%d%d",&i, &j, &weight) != EOF)
{
G[i-1][j-1] =weight;
G[j-1][i-1] =weight;
}
prim(G,max_vertexes,fatheer);
return 0;
}
程序结果:
3 1
6 3
4 6
2 3
5 2
线段树:lazy_tag
- 最短路径算法
- 最短路径算法
- 最短路径理解
- 最短路径算法
- 最短路径算法
- 最短路径问题
- 最短路径
- 最短路径
- 最短路径算法
- 图@ 最短路径
- 最短路径
- 最短路径算法
- 最短路径
- hdu2544(最短路径)
- 最短路径问题
- 最短路径问题
- 最短路径算法
- 最短路径算法
- linux常用命令
- jsonp(跨域)-------可以运行的小实例!
- linux系统编程收集
- 实现背景透明,宽度可控的Dialog
- Linux中find常见用法示例
- 最短路径
- Picasso加载圆角图片
- 上传文件到luix系统
- pom文件详解
- java很好的一些面试问题
- js基础和简单应用
- Map中封装Map并且在前端取出
- [Java]复习笔记(三)--基本语法
- hdu 6181 Two Paths -最短路条数+次短路