最短路算法集合

8 篇文章 0 订阅
5 篇文章 0 订阅

目录

一、Dijkstra算法:

二、Spfa算法:

三、Floyd算法:

四、DP记忆化搜索

五、线段树求最短路

终、对比与分析


一、Dijkstra算法:

仅限用于无边权为负数的图,更不能正确处理负环的情况。

把所有点分成两个集合,S集合是存已经确定下来、不会再更改(准确一点的话是“不能再更改”)的点,T集合是待确定的点集。

从T集合中的点中,选择一个到st最近的点x。让这个点为其它点更新:如果y点到st的距离大于 x点到st的距离 + x->y这条边的距离,则改进y到st的值。然后x加入S集合。

更透彻地理解,这种算法每次循环都会确定下一个点,这个点到st的距离一定是最优解中它到st的距离。理由是它是目前距离最小的点之一,边权都是正的,对于后面的点到st的距离相对于当前的最小点来说只会加而不会减,所以它无法再有更小的值了。借着这一点贪心,我们就可以通过n(点的个数)次上面这样的操作,十分稳定地求出全图各点到st的最短距离。

Dijkstra的算法可以用堆优化,其实就是开一个priority_queue(优先队列),来存储和快速提取dis里的最小值。可以由O(n^2)优化到O(nlogn)。

堆优化的dijkstra代码见下:

#include<queue>
#include<cstdio>
#include<cstring>
#include<utility>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;//pii(边权,点编号)
const int inf=1061109567;
const int maxn=10010,maxm=500010;

int n,m,s;

struct edge
{
    int x,y,c,next;
}e[maxm];int len=0,last[maxn];
void ins(int x,int y,int c)
{
    e[++len]=(edge){x,y,c,last[x]};last[x]=len;
}

int dis[maxn];
bool v[maxn];
priority_queue<pii,vector<pii>,greater<pii> > q;
void dijkstra()
{
    memset(dis,63,sizeof(dis));dis[s]=0;
    
    int cnt=0;
    q.push(pii(0,s));
    while(!q.empty())
    {
        int x=q.top().second,d=q.top().first;q.pop();
        if(v[x]) continue;
        dis[x]=d;v[x]=true;
        cnt++;
        for(int k=last[x];k;k=e[k].next)
        {
            int y=e[k].y;
            if(dis[y]>dis[x]+e[k].c)
            {
                dis[y]=dis[x]+e[k].c;
                q.push(pii(dis[y],y));
            }
        }
    }
}

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++)
    {
        int x,y,c;
        scanf("%d%d%d",&x,&y,&c);
        ins(x,y,c);
    }
    dijkstra();
    for(int i=1;i<=n;i++)
    {
        if(dis[i]==inf) printf("2147483647 ");
        else printf("%d ",dis[i]);
    }
    return 0;
}

二、Spfa算法:

可以用于带负边权的图,也可以用来判断图中有无负环。

用于判断负环的方法是:再标准spfa模版下,如果有一个点进队列的次数达到n次,则存在负环。

spfa可以用SLF和LLL来优化,用得好可以减少一半左右时间。
由于spfa较于简单,此处不再详讲。

三、Floyd算法:

此算法可以求any point(任意一点)到any other point(其它任意一点)的最短距离。时间复杂度O(n^3)


floyd其实是一个DP算法,它先枚举一个交换点,然后看看哪对经过这个点的距离会比原来要短。一开始的时候只有1做交换点,此时的f[x][y]仅表示可以经过1号店后的最短距离。接下来又允许经过2号点,求出了允许经过1、2号点的最短距离。……到n号点时,就求出了可以经过1~n号点的最短距离,此时的f[x][y]就是这张图中两两间的最短距离了。它的DP方程完整为f[k][i][j]=f[k-1][i][k]+f[k-1][k][j](其中k为中转点,f[k][i][j]表示可以把1~k作为中转点时i~j的最短距离)。其中第一维的k没有意义,可以删掉。

floyd保存路径的方法要只需要一句话,但背后的原理还是不浅的。想要达到目的,可以用前缀、后缀、保存中转点等多种方法,愚认为用前缀最好了。

设path[i][j]表示从i->j这条路径上离开i后的下一个点的坐标,这样我们就可以通过下面这段代码输出路径。它的循环就是为了不断找寻当前路径上的次靠前的那个点,再借助那个点来求下一步路径。例如有这么一条路径:4->5->3->1,则path[4][1]=5,path[5][1]=3,path[3][1]=1,这样一开头再加上4就成了一条完整的路径了。

while(1)
{
	int x,y;
	scanf("%d%d",&x,&y);
	if(path[x][y]==-1)
	{
		printf("No Way\n");
	}
	else
	{
		printf("dis: %d\nway:  ",ma[x][y]);
		while(x!=y)
		{
			printf("%d -> ",x);
			x=path[x][y];
		}
		printf("%d\n\n",y);
	}
}


在更改时,也要啃住前缀来更新。如果从i出发去j,途经k后最短,其路径为:i->…->k->…->j。如果想要得到完整路径,可以分别让path[i][k]和path[k][j]递归得到。若要记录下来,我们也只是需要记录下当前路径下从i出发之后的点的编号即可,即path[i][k]。接下来从(设q=path[i][k])q的下一步的点的编号,会在path[q][y]中记录,这是在求q到y的最短路时的事情,它的记录必然会是路径i->q->…->k->…->j的一部分。

以下是floyd+路径储存的代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100;

int ma[maxn][maxn],path[maxn][maxn];

int main()
{
	memset(ma,63,sizeof(ma));
	memset(path,-1,sizeof(path));
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		ma[i][i]=0;
		path[i][i]=i;
	}
	for(int i=1;i<=m;i++)
	{
		int x,y,c;
		scanf("%d%d%d",&x,&y,&c);
		ma[x][y]=c;
		path[x][y]=y;
	}
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(ma[i][j]>ma[i][k]+ma[k][j])
				{
					ma[i][j]=ma[i][k]+ma[k][j];
					path[i][j]=path[i][k];
				}
			}
		}
	}
	while(1)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		if(path[x][y]==-1)
		{
			printf("No Way\n");
		}
		else
		{
			printf("dis: %d\nway:  ",ma[x][y]);
			while(x!=y)
			{
				printf("%d -> ",x);
				x=path[x][y];
			}
			printf("%d\n\n",y);
		}
	}
	return 0;
}

四、DP记忆化搜索

这种方法的限制很多,它必须是无环图,包括不能有双向边。它在应用于无单一源点或汇点,不要求全图连通,但在每个连通块里面边的方向总体一致的图中,或者是求每个点到最近汇点的距离,能达到O(n)的时间复杂度。
每次操作时,随机选取一个没有遍历过的点,让它去搜寻最短路,搜完后记录下来,为后人提供方便。

代码很好理解,就不多写了:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=1061109567;
const int maxn=100;
const int maxm=100;

int d[maxn];
struct node
{
	int x,y,c,next;
}a[maxm];int len=0,last[maxn];
void ins(int x,int y,int c)
{
	len++;
	a[len].x=x;a[len].y=y;a[len].c=c;
	a[len].next=last[x];last[x]=len;
}

void dfs(int x)
{
	if(d[x]!=inf) return ;
	if(last[x]==0) d[x]=0;
	for(int k=last[x];k;k=a[k].next)
	{
		int y=a[k].y;
		dfs(y);
		if(d[x]>d[y]+a[k].c) d[x]=d[y]+a[k].c;
	}
}

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y,c;
		scanf("%d%d%d",&x,&y,&c);
		ins(x,y,c);
	}
	
	memset(d,63,sizeof(d));
	for(int i=1;i<=n;i++)
	{
		if(d[i]==inf) dfs(i);
	}
	for(int i=1;i<=n;i++) printf("%d: %d\n",i,d[i]);
	return 0;
}

五、线段树求最短路

对于一棵每个节点都有直接回到根节点的树,先求出各个点到根节点的最短距离,用线段树来维护。要求各个点的距离时,利用和差可以求到各个点间的最短路径。

具体的内容请往这看

终、对比与分析

最后再来比较一下以上几种最短路算法:

在功能方面:
能判断负环:spfa
多个点到某个点的最短距离(单源最短路):dijkstra,spfa,DP记忆化搜索
任意两点间最短距离(多源最短路)(任意图):floyd
树上任意两点间最短距离:floyd,(dijkstra,spfa,DP记忆化搜索 要多一个是否在一棵子树上的判断)
带正环的图:floyd也可以,但DP记忆化搜索不行

在时间方面:
稠密图dijkstra,稀疏图spfa

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值