您的位置:首页 > 其它

最短路问题(4种方法)(邻接矩阵,邻接表,bellman-ford,spfa)

2017-01-17 11:48 603 查看
以最简单的   Til the Cows Come Home    为例 点击打开链接

几种算法的核心思想就是,先找到距离起点最近的点,以它为松弛点,对所有的点进行松弛操作;

1.dijkstra-邻接矩阵(无法处理负权值)

       以二维数组为存储方式,以节点的序号为数组的横纵坐标来存储权值;

 以图为例:

       


           存储方式:

              


             到自身的位置为0,有权值的赋为权值,没有的赋为无穷大(0x3f3f3f3f)

            设一数组dis[],存储起点到各点的最小值;

            先初始化:           

         


           然后以距离起点最近的点为松弛点,对每个点进行松弛;

          详细解释请看代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define MAX 5000
#define inf 0x3f3f3f3f//较大的数,可以看为无穷大

int n,t,i,j;
int map[MAX][MAX],dis[MAX],xia[MAX];//map记录题目给出的各条边的权值,dis记录起点到到每个点的最短路,xia记录那些点进行过松弛操作

int main()
{
while(~scanf("%d%d",&t,&n))
{
for(i=1; i<=n; i++)//对map进行初始化操作
for(j=1; j<=n; j++)
if(i==j) map[i][j]=0;//到自己的距离为零
else map[i][j]=inf;
for(i=1; i<=t; i++)//记录题目给的每一条边
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);//可能有重复的边所以要记录最短的距离
map[a][b]=map[b][a]=min(map[a][b],c);//因为是无向图,所以可以双向走:如果是有向图就改为:map[a][b]=min(map[a][b],c);
}
memset(xia,0,sizeof(xia));//记录那些点进行过松弛操作
xia[1]=0;
for(i=1; i<=n; i++)//对dis初始化
dis[i]=map[1][i];//如果不是求1到n的最短距离,就改成:dis[i]=map[起点的标号][i];
int h,x;
for(i=1; i<=n; i++)//重点。。松弛操作
{
h=inf;
for(j=1;j<=n;j++)//求出距离起点最近的点
{
if(xia[j]==0&&h>dis[j])
h=dis[x=j];//x记录距离起点最近的点
}
xia[x]=1;//并且记录对x进行过松弛操作
for(j=1;j<=n;j++)//对每个点进行松弛操作,求出最短距离
dis[j]=min(dis[j],dis[x]+map[x][j]);
}
printf("%d\n",dis
);//输出起点到n的最短距离;
}
return 0;
}


  dijkstra-邻接表(不能处理负权值)

  邻接表比邻接矩阵快的多;(难理解,不好解释)这是啊哈算法上的解释









#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define MAX 5000
#define inf 0x3f3f3f3f

int dis[MAX],u[MAX],v[MAX],w[MAX],first[MAX],next[MAX];
bool xia[MAX];
int i,j,n,t;

int main()
{
while(~scanf("%d%d",&t,&n))
{
memset(xia,false,sizeof(xia));//初始化操作
xia[1]=true;
memset(first,-1,sizeof(first));//存储给定边的序号
memset(next,-1,sizeof(next));//存储给定边的序号
memset(dis,inf,sizeof(dis));//记录1到各个点的最短路
dis[1]=0;
for(i=1;i<=2*t;i+=2)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);//下面是记录无向图的,代码比有向图多一倍,核心操作
u[i]=v[i+1]=a,u[i+1]=v[i]=b,w[i]=w[i+1]=c;
next[i]=first[u[i]],next[i+1]=first[u[i+1]];
first[u[i]]=i,first[u[i+1]]=i+1;
}
int f=1,hmax;
for(i=2;i<=n;i++)//核心操作
{
for(j=first[f];j!=-1;j=next[j])//找出从f点出发的边的终点的最短距离
{
if(xia[v[j]]==false)//找最短距离
dis[v[j]]=min(dis[v[j]],dis[f]+w[j]);
}
hmax=inf;
for(j=1;j<=n;j++)
{
if(xia[j]==false&&hmax>dis[j])
hmax=dis[f=j];//f记录到1最近的点
}
xia[f]=true;//对松驰过的点进行标记
}
printf("%d\n",dis
);
}
return 0;
}


2.bellman-ford(可以处理负权值的问题)(目前唯一一个)

核心代码只有四行

n代表点的数量,要进行n-1次松弛操作,t代表边的数量

for(i=1; i<=n-1; i++)
for(j=1; j<=t; j++)
if(dis[v[j]]>w[j]+dis[u[j]])
dis[v[j]]=w[j]+dis[u[j]]


想法与上面相同;

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define MAX 5000
#define inf 0x3f3f3f3f

int u[MAX],v[MAX],w[MAX],dis[MAX];//u记录起始点,v记录终止点,w记录这条边的权值,dis记录1到各个点的最短距离
int i,j,n,t;

int main()
{
    while(~scanf("%d%d",&t,&n))
    {
        memset(u,0,sizeof(u));//初始化
        memset(v,0,sizeof(v));
        memset(w,0,sizeof(w));
        memset(dis,inf,sizeof(dis));
        dis[1]=0;
        for(i=1; i<=t; i++)//输入数据
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
        for(i=1; i<=n-1; i++)//n为点的数量
            for(j=1; j<=t; j++)//t为边的数量
            {
                dis[v[j]]=min(dis[v[j]],w[j]+dis[u[j]]);//求最小值,如果是有向图,就没有下面那一行代码
                dis[u[j]]=min(dis[u[j]],w[j]+dis[v[j]]);
            }
        printf("%d\n",dis
);
    }
    return 0;
}


3.floyd(不太常用)

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define MAX 5000
#define inf 0x3f3f3f3f

int map[MAX][MAX];
int i,j,k,n,t;

int main()
{
while(~scanf("%d%d",&t,&n))
{
for(i=1; i<=n; i++)
for(j=1; j<=n; j++)
if(i==j) map[i][j]=0;
else map[i][j]=inf;
for(i=1; i<=t; i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
map[a][b]=min(map[a][b],c);
}
for(k=1; k<=n; k++) //
for(i=1; i<=n; i++)
for(j=1; j<=n; j++)
if(map[i][j]>map[i][k]+map[k][j])
map[i][j]=map[i][k]+map[k][j];
printf("%d\n",map[1]
);
}
return 0;
}

4.spfa

spfa 是bellman-ford的优化,用队列来优化。

将能通过这个点优化其他点的点放入队列中,再用队列中的点来松弛与这个点直接相连的点,直到队列中没有点为止。

来个例题好理解。 题目链接:传送门

有n个点,m条边,u,v边的两个点,w边的长度,求1到n的最短路;

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
#define inf 0x3f3f3f3f
#define maxx 5009
using namespace std;
struct ndoe
{
int u,v,w;
int next;
}dis[maxx];
int first[maxx];
int dp[maxx];//记录最短路
int xia[maxx];//该点是否在队列中
int M;
void set_dis(int u,int v,int w)//建立邻接表
{
dis[M].u=u;
dis[M].v=v;
dis[M].w=w;
dis[M].next=first[u];
first[u]=M++;
}
int main()
{
int m,n;
while(~scanf("%d%d",&m,&n))
{
M=0;
memset(first,-1,sizeof(first));
memset(xia,0,sizeof(xia));
memset(dp,inf,sizeof(dp));
for(int i=1; i<=m; i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
set_dis(a,b,c);
set_dis(b,a,c);
}
dp[1]=0;//初始化
xia[1]=1;
queue<int>q;
q.push(1);//先将起始点放入队列中
int x;
while(!q.empty())
{
x=q.front();
q.pop();
xia[x]=0;
for(int j=first[x]; j!=-1; j=dis[j].next)
{
if(dp[dis[j].v]>dp[x]+dis[j].w)
{
dp[dis[j].v]=dp[x]+dis[j].w;
if(xia[dis[j].v]==0)//这个点被松弛过,所以也可以松弛别的点,放入队列中
{
q.push(dis[j].v);
xia[dis[j].v]=1;
}
}
}
}
printf("%d\n",dp
);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐