标签:
一、目的;
求源点到其他点之间的最短距离;
二、floyd算法;
(1) 假设起点为A,终点为B,则A到B的距离要么是A直接到B,要么A经过其他节点到B,假设我们经过的节点为K,则最短路为min(dist[A][B],dist[A][K]+dist[K][B])每次更新即可;
For (i=1;i<=n;i++)
For (j=1;j<=n;j++)
For(k=1;k<=n;k++)
Dist[i][j]=min(dist[i][k]+dist[k][j],dist[i][j]);
(2) 以上代码看起来似乎没错,但是假设图中有环的话,就会出现错误,比如A到B为9,A 到C为2 ,C到D为2,D到B为2,当i等于A,j等于B时,AB之间因为存在两个节点,所以并没有更新,即会出现错误。
我们可以这样来更新,根据中心点,来更新这个点两端的距离,比如C为中心点,则更新AD,然后D为中心点,更新AB,CB,B为中心点,更新AD,AC,以此类推。
For(k=1;k<=n;k++)
For(i=1;i<=n;i++)
For(j=1;j<=n;j++)
Dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
(3)路径保存问题;
我们可以设p[i][j]为路径ij中,j前面的那个节点;
Void savePath(){
For (i=1;i<=n;i++)
For(j=1;j<=n;j++)
If(map[i][j]==MAX){
P[i][j]=-1;
}else p[i][j]=i;
For(k=1;k<=n;k++)
For (i=1;i<=n;i++)
For (j=1;j<=n;j++)
If(dist[i][j]>dist[i][k]+dist[k][j]){
Dist[i][j]=dist[i][k]+dist[k][j];
P[i][j]=p[k][j];
}
I=from;j=to;
Printf(“%d”,j);
While(p[i][j]!=from){
Printf(“%d”,p[i][j]);
J=p[i][j];
}
}
三、dijkstra算法
(1) 本算法是计算单源最短路的算法,即一个原点到其他节点最短路的算法,原理为,
假设已经计算出最短路的节点集合为S,没计算出来的为V,最开始S只有一个源点,然后d[i]代表s到i的最短距离,首先我们找出d中的最小值的节点t,然后将他加入S。
为什么这个时候d[t]一定是最短路径呢?
因为假设t不是最短路,那么一定存在另外的一条路更小,假设中间节点为k则最短路一定为d[k]+map[k][t];而d[t]为最小值与题意不符,故d[t]一定为最短路径。
(2)
void init(){
int i,j;
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
map[i][j]=MAX;
for (i=1;i<=m;i++)
{
int k,l,g;
scanf("%d%d%d",&k,&l,&g);
map[k][l]=g;
map[l][k]=g;
}
}
void dijkstra()
{
int s,i,j;
int v[10]={0};
scanf("%d",&s);
for (i=1;i<=n;i++)
d[i]=map[s][i];
v[s]=1;
for (i=1;i<=n-1;i++)
{
int min=MAX,minj;
for(j=1;j<=n;j++)
if(!v[j]&&min>d[j])
{
min=d[j];
minj=j;
}
v[minj]=1;
for (j=1;j<=n;j++)
if(!v[j]&&d[j]>d[minj]+map[minj][j])
{
d[j]=d[minj]+map[minj][j];
}
}
for (i=1;i<=n;i++)
printf("%d %d\n",i,d[i]);
}
(2)这个算法不能用于存在负权值的情况。
如1->2 6
1->3 5
2 ->3 -2
此时算法会出错。
四、Bellman-ford算法
(1)这个算法可以用于处理负权值边,原理:是利用每条边进行松弛操作,个人认为就是用每一条边刷新一次最短路。
(2)我们可以用d数组来记录最短路径长度,初始化为max,然后原点到原点的最短路为零,然后遍历n-1次,每次遍历利用所有的边来刷新最短路,要遍历n-1次的原因是因为最短路一定不包含环,假如包含正值环的话,去掉这个环,结果显然更优,因此最短路径中一定包含n-1条边,而我们每次刷新至少要刷新一条边,假设没有刷新了说明我们最短路已经找到了。
(3)void bellman_ford()
{
int s,i,j;
scanf("%d",&s);
for (i=1;i<=n;i++)
d[i]=MAX;
d[s]=0;
for (i=1;i<=n-1;i++)
for (j=1;j<=m;j++)
if(d[edge[j].v]>d[edge[j].u]+edge[j].value)
{
d[edge[j].v]=d[edge[j].u]+edge[j].value;
}
int flag=0;
//判断是否存在权值为负的环
for (j=1;j<=m*2;j++)
if(d[edge[j].v]+d[edge[j].u]+edge[j].value<0)
{
flag=1;
}
if(flag)printf("存在负环\n");
else printf("不存在负环\n");
for (i=1;i<=n;i++)
printf("%d %d\n",i,d[i]);
}
四、spfa算法
(1) 算法是对bellman-ford算法的优化
(2) 将我们每次优化后的节点放入队列,根据队列里面的节点进行优化,减少了比较次数,同时减小了时间复杂度。
(3) 初始化我们可以定义一个数组d表示最短距离,然后c数组代表每个点进入队列的次数,当c超过n次的时候一定存在负环,要大于n而不是n-1次的原因,我认为应该防止只有一个点的情况。
(4) Spfa-bfs算法;
采用邻接表储存数据,
#include<stdio.h>
#define MAX 9999
int b[10][10]={0},w[10][10]={0};
int n,m;
int d[10];
void init()
{
int i,j;
scanf("%d%d",&n,&m);
for (i=1;i<=m;i++)
{
int k,l,g;
scanf("%d%d%d",&k,&l,&g);
b[k][0]++;
b[k][b[k][0]]=l;
w[k][b[k][0]]=g;
}
}
void spfa_bfs()
{
int q[100];
int h,t,i,j;
h=1;t=1;
int s;
int flag=0;
int v[10]={0};
int c[10]={0};
scanf("%d",&s);
for (i=1;i<=n;i++) d[i]=MAX;
d[s]=0;c[s]=1;q[1]=s;v[s]=1;
while(h<=t)
{
int u=q[h];v[u]=0;
for (j=1;j<=b[u][0];j++)
{
int y=b[u][j];
if(d[y]>d[u]+w[u][j])
{
d[y]=d[u]+w[u][j];
if(!v[y]){
t++;
q[t]=y;
c[y]++;
v[y]=1;
if(c[y]>n){
flag=1;
break;
}
}
}
}
if(flag) break;
h++;
}
for(i=1;i<=n;i++)
printf("%d %d\n",i,d[i]);
if(flag) printf("There exist fuhuan\n");
else printf("There not exist fuhuan\n");
}
int main()
{
init();
spfa_bfs();
return 0;
}
(5) 当我们用队列宽搜来判断是否有负环时,会让一个负环重复很多次才会判断出来,浪费了时间,而我们用深搜可以直接判断假设有负环我们会回到访问过的节点,并且会执行松弛操作,而正环则不会进行松弛操作。
int spfa_dfs(int u)
{
vi[u]=1;
int j;
for (j=1;j<=b[u][0];j++)
{
int y=b[u][j];
if(d[y]>d[u]+w[u][j]){
d[y]=d[u]+w[u][j];
if(!vi[y]){
if(spfa_dfs(y)){
return 1;
}
}else
return 1;
//当访问过这个节点并且沿着这条路又可以对当前节点进行松弛操作时,说明存在负环
}
}
vi[u]=0;
return 0;
}
(6)关于最短路径的记录问题,直接定义一个数组pre记录前驱,在进行松弛操作时更改即可。
标签:
原文地址:http://www.cnblogs.com/dlut-li/p/5572803.html