最短路(Floyd+Dijkstra+(Bellman-Ford)SPFA)

       最短路一共有4种,也不难,这里讲一下当做复习好了。我在这里讲的都是我自己的理解,如果有误大家可以提出,而这些算法的推导、实现过程建议还是去百科里看。

       有关于算法的实现过程还是非常重要的,当你忘记怎么写的时候,记住简单的实现过程好歹还可以手推出来。这些我在代码里也会有讲解。

Floyd(百科):

       最简单,最暴力的最短路,主要是一个dp的思想,也很好理解,优点是可以处理负边以及将任意两个点之间的最短路都求出来了,一般没什么用,时间复杂度Θ(n³)。

       推一道例题HDU2544

       其实这道题完全可以用Dijsktra来做,但是n非常小,所以就直接上Floyd代码更短。

Code:

#include<bits/stdc++.h>
#define N 105
using namespace std;
int dist[N][N];
int inline read()
{
    int f=1,x=0;char s=getchar();
    while(s<'0'||s>'9')
    {
        if(s=='-')f=-1;
        s=getchar();
    }
    while(s<='9'&&s>='0')
    {
        x=x*10+s-'0';
        s=getchar();
    }
    x*=f;
    return x;
}
int main()
{
    int n=read(),m=read();
    while(n!=0||m!=0)
    {
        memset(dist,0x3f,sizeof(dist));
        for(int i=1;i<=m;i++)
        {
            int x=read(),y=read(),z=read();
            dist[x][y]=min(dist[x][y],z);
            dist[y][x]=min(dist[y][x],z);
        }
        //Floyd核心代码 
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                for(int k=1;k<=n;k++)
                    if(k!=j)
			dist[j][k]=min(dist[j][k],dist[j][i]+dist[i][k]);
    	//i表示断点,j,k是两个端点。 
        printf("%d\n",dist[1][n]);
        n=read(),m=read();
    }
    return 0;
}

Dijkstra(百科

       这是一个稳定的算法,能计算一个起点到其他所有点的最短路,用了一个贪心的思想,但是它无法处理边权为负的情况,是一个比较优秀,也很常见的算法。时间复杂度Θ(n²)。

       例题:POJ2387

       可以发现n已经有1000了,用Floyd肯定会超时,所以用Dijkstra来处理。

Code:

#include<cstdio>
#include<algorithm>
#include<cstring>
#define inf 0x7ffffff
#define N 1005
using namespace std;
int dist[N][N],ans[N];
bool b[N];
int inline read()
{
    int f=1,x=0;char s=getchar();
    while(s<'0'||s>'9')
    {
        if(s=='-')f=-1;
        s=getchar();
    }
    while(s<='9'&&s>='0')
    {
        x=x*10+s-'0';
        s=getchar();
    }
    x*=f;
    return x;
}
int main()
{
    int t=read(),n=read();
    memset(dist,0x3f,sizeof(dist));//n只有1000,可以开邻接矩阵方便读取。
    for(int i=1;i<=t;i++)
    {
    	int x=read(),y=read(),z=read();
    	dist[x][y]=min(dist[x][y],z);
    	dist[y][x]=min(dist[y][x],z);
	}
	for(int i=1;i<=n;i++)ans[i]=dist[1][i];//ans[i]表示1到i的最短距离。当求以s为起点的最短路时将1改为s即可
	memset(b,false,sizeof(b));//b[i]表示i这个点有没有被访问过
	b[1]=true;
	for(int i=1;i<=n;i++) 
	{
		int Min=inf,k=0;
		for(int j=1;j<=n;j++)//找目前没有访问过的离起点最近的点 
			if(!b[j]&&ans[j]<Min)
			{
				Min=ans[j];
				k=j;
			}
		if(k==0)break;//没找到直接跳出循环 
		b[k]=true;
		for(int j=1;j<=n;j++)//以k为中转点松弛与它相连的每一条边 
			if(!b[j])
				ans[j]=min(ans[j],ans[k]+dist[k][j]);
	}
	printf("%d",ans[n]);
    return 0;
}

 

       如果还要追求更短的时间,我们还可以进行堆优化,时间复杂度Θ(E㏒V)(因为我非常菜,只会STL优先队列优化,不会非常牛逼的Fibonacci堆和二叉堆)。

       依然是POJ这道题下面用STL优先队列写一遍。

Code:

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=2005,inf=1e9+7;
struct node
{
	int vet,next,len;
}edge[N*2];
int tot,head[N*2],dist[N],vis[N];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >Q;
inline int read()
{
	int x=0,f=1;
	char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
	return x*f;
}
void add(int u,int v,int w)
{
	edge[++tot].vet=v;
	edge[tot].next=head[u];
	head[u]=tot;
	edge[tot].len=w;
}
int main()
{
	int T=read(),n=read();
	memset(head,-1,sizeof(head));
	while(T--)
	{
		int u=read(),v=read(),w=read();
		add(u,v,w);add(v,u,w);
	}
	for(int i=1;i<=n;i++)dist[i]=inf,vis[i]=0;
	dist[1]=0;
	Q.push(make_pair(dist[1],1));
	while(!Q.empty())
	{
		int u=Q.top().second;
		Q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=head[u];i!=-1;i=edge[i].next)
		{
			int v=edge[i].vet;
			if(dist[u]+edge[i].len<dist[v]&&!vis[v])
			{
				dist[v]=dist[u]+edge[i].len;
				Q.push(make_pair(dist[v],v));
			}
		}
	}
	printf("%d\n",dist[n]);
	return 0;
}

Bellman-Ford(百科

       这可以说是最没有用的一个算法,因为人们通常会记住它的改进版SPFA。所以这里也不再复述,直接讲SPFA。

SPFA(百科

       SPFA是一个不稳定的算法,是Bellman-Ford的队列优化,唯一比Dijkstra好的地方就是它可以处理负权边,时间复杂度Θ(kE),在稀疏图中k小于2,但稠密图中SPFA复杂度会退化,最大甚至会达到Θ(VE)。但一般来讲还是非常高效的。

       随便弄一道题:HDU2066。用SPFA写一遍。

Code:

#include<bits/stdc++.h>
#define INF 0x7fffffff
#define N 1005
using namespace std;
int dist[N][N],b[N],ans[N],Q[N];
inline int read()
{
    int f=1,x=0;char s=getchar();
    while(s>'9'||s<'0'){if(s=='-')s=-1;s=getchar();}
    while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
int main()
{
    while(EOF)
    {
        int T=read(),S=read(),D=read(),n=0;
        memset(dist,0x3f,sizeof(dist));
        for(int i=1;i<=T;i++)
        {
            int a=read(),b=read(),time=read();
            dist[a][b]=min(dist[a][b],time);
            dist[b][a]=min(dist[b][a],time);
            n=max(n,a);n=max(n,b);
        }
        for(int i=1;i<=S;i++)
        {
            int city=read();
            dist[0][city]=0;dist[city][0]=0;
            n=max(n,city);
        }
        memset(ans,0x3f,sizeof(ans));
        memset(b,true,sizeof(b));
        int head=0;Q[++head]=0;ans[0]=0,b[0]=false;
        while(head>0)
        {
            int k=Q[head--];
            b[k]=true;
            for(int i=0;i<=n;i++)
                if(ans[i]>ans[k]+dist[k][i])
                {
                    ans[i]=ans[k]+dist[k][i];
                    if(b[i])
                    {
                        b[i]=false;
                        Q[++head]=i;
                    }
                }
        }
        int lastans=INF;
        for(int i=1;i<=D;i++)
        {
            int want=read();
            lastans=min(lastans,ans[want]);
        }
        printf("%d\n",lastans);
    }
    return 0;
}

       这个代码应该说是最浅显易懂的了,下面再用STL写一遍。

 

Code:

#include<bits/stdc++.h>
#define INF 0x7fffffff
#define N 1005
using namespace std;
int b[N],ans[N],head[2*N],tot;
queue<int>Q;
struct edge
{
	int vet,len,next;
}edge[2*N];
inline int read()
{
	int f=1,x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')s=-1;s=getchar();}
	while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
	return x*f;
}
void add(int u,int v,int len)
{
	edge[++tot].vet=v;
	edge[tot].next=head[u];
	head[u]=tot;
	edge[tot].len=len;
}
int main()
{
	int T,S,D;
	while(~scanf("%d%d%d",&T,&S,&D))
	{
		tot=0;
		memset(head,-1,sizeof(head));
		for(int i=1;i<=T;i++)
		{
			int a=read(),b=read(),time=read();
			add(a,b,time);add(b,a,time);
		}
		for(int i=1;i<=S;i++)
		{
			int city=read();
			add(0,city,0);add(city,0,0);
		}
		memset(b,true,sizeof(b));
		memset(ans,0x3f,sizeof(ans));
		ans[0]=0,b[0]=false;Q.push(0);
		while(!Q.empty())
		{
			int k=Q.front();
			Q.pop();
			b[k]=true;
			for(int i=head[k];i;i=edge[i].next)
			{
				int v=edge[i].vet;
				if(ans[v]>ans[k]+edge[i].len)
				{
					ans[v]=ans[k]+edge[i].len;
					if(b[v])
					{
						b[v]=false;
						Q.push(v);
					}
				}
			}
		}
		int lastans=INF;
		for(int i=1;i<=D;i++)
		{
			int want=read();
			lastans=min(lastans,ans[want]);
		}
		printf("%d\n",lastans);
	}
	return 0;
}

       据说SPFA算法有两个优化算法 SLF 和 LLL:
       SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j) < dist(i),则将j插入队首,否则插入队尾。
       LLL:Large Label Last 策略,设队首元素为i,每次弹出时进行判断,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。

 

       然后我一脸懵逼。

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/qq_34531807/article/details/79305049
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞