最短路的常用算法模板

最短路分为单源最短路和多源最短路。

在这里先写出两种能够解决单源最短路的算法。

值得一提的是 二叉堆优化基于贪心的Dijkstra算法 和 优先队列优化基于BFS的SPFA算法 两种算法的思想殊途同归,两者的代码实现有大多相同之处,都能解决非负权单源最短路的问题,Dijkstra算法较稳定可作为首选,但如果图中存在长度为负数的边,便只有SPFA算法能够正常工作。

1.Dijkstra 算法

算法思想:
1.初始化距离d[s]=0,其余节点的d值为正无穷大;
2.找出一个未被标记且d[u]最小的节点,然后标记u;
3.扫描跟u相连的所有边v , 若d[v]>d[u]+w,则松弛更新距离 d[v]=d[u]+w;
4.重复1-3的步骤,直到所有点被标记。

朴素版本 时间复杂度O(n^2):

#include<iostream>
#include<cstdio>
#include<cstring>
const int manx=1e5+5;
const int inf=2147483647;
int a[manx][manx],d[manx];
bool vis[manx];
int n,m,s,e;
void dijkstra()
{
	for(int i=1;i<=n;i++) d[i]=inf,vis[i]=0,a[i][i]=0; //初始化各数组
	d[s]=0; //s作为起点
	for(int i=1;i<n;i++) //重复n-1次
	{
		int x=0;
		for(int j=1;j<=n;j++)
			if( !vis[j] && (x==0||d[j]<d[x]) ) //寻找未访问过且离起点最近的点
				x=j;
		vis[x]=1;
		for(int y=1;y<=n;y++)  //第一轮d[y]中由起点可到达的点得到更新
			d[y]=min( d[y ], d[x]+a[x][y])	
		}
}
int main()
{
	cin>>n>>m;
	memset(a,0x3f, sizeof(a));
	for(int i=1 ; i<=m;i++)
	{
		int u, v, w;
		scanf("%d%d%d",&u,&v,&w);
		a[u][v]=min(a[u][v],w);
	}
	s=1;
	dijkstra();
	for(int i=1;i<=n;i++)
		printf("%d ",d[i]);
	return 0;
}

朴素算法的瓶颈在于第一步的寻找全局的最小值,可以用二叉堆对d 数组进行维护, 用O(log n)的时间分别进行获得最小值和对一条边的扩展和更新操作,最终得到优化。

优先队列(堆)优化 时间复杂度 (mlogn) :

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int inf=2147483647;
const int manx=1e4+5; //与n相对,对应顶点的个数
const int mamx=5e5+5; //与m相对,对应边的个数
priority_queue< pair<int,int> >q; 
struct node{ 
    int next,v,w;
}edge[mamx];  //边去mamx,其余取manx
bool vis[manx];  //这里的标记数组与spfa的vis数组含义不同,这里标记是否入过队列
int head[manx],d[manx];
int k=0;
int n,m,s,e; //s作为起点,e作为终点
void add(int u,int v, int w) //链式前向星存图
{
    edge[++k].next=head[u];
    edge[k].v=v;
    edge[k].w=w;
    head[u]=k;
}
void dijkstra()
{
    for(int i=1;i<=n;i++) //初始化vis d 数组
        d[i]=inf,vis[i]=0;
    d[s]=0; //s作为起点
    q.push(make_pair(0,s));
    while(q.size()){
        int x=q.top().second; //取出队头
        q.pop();
        if(vis[x]) continue; //如果点x访问过,跳过,访问下一个队头
        vis[x]=1; //访问x做标记
        for(int i=head[x];i;i=edge[i].next){
            int v=edge[i].v,w=edge[i].w;
            if(d[v]>d[x]+w){ //松弛操作,更新距离
                d[v]=d[x]+w;
                q.push(make_pair(-d[v],v)); //把更新的距离和点入队,这里距离取负变成小根堆
            }
        }
    }
}
int main()
{
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        //无向图  add(v,u,w);
    }
    s=1;
    dijkstra();
    for(int i=1;i<=n;i++)
        printf("%d ",d[i]);
    return 0;
}

2.spfa算法

算法思想:
1.建立一个队列,队列初始只有起点;
2.取出队头点,扫描与它相连的所有边,如果出现dis[v] > dis[u]+w, 则进行更新操作,dis[v] = dis[u] +w;
3.重复1-2步骤,直到队列为空。

在某些特殊构造的图上,该算法会对不需要扩展的节点进行扫描,将原本运行效率O(Km) 中的K极大化,验证了其不稳定性,因此本文开头讲了一句非负权图的单源最短路最好用Dijkstra算法。
PFA的不稳定性也让我知道了他的可优化性,SLF优化 / LLL优化 / DFS优化 留给以后补上。

优先队列版本:

#include<iostream>
#include<cstdio>
#include<queue>
const long long int inf=214748647;
const int manx=1e5+5;  //与n相对,对应顶点的个数
const int mamx=5e5+5; //与m相对,对应边的个数
using namespace std;
int n,m,s,e,k=0;
int dis[manx],head[manx];
bool vis[manx]; //这里的标记是判断点是否在队列中
struct Edge{
    int next,to,dis;
}edge[mamx];
void add(int from,int to, int dis) //链式前向星
{
    edge[++k].next=head[from];
    edge[k].to=to;
    edge[k].dis=dis;
    head[from]=k;
}
void spfa()
{
    queue<int >q; //建立队列
    for(int i=1;i<=n;i++)  //初始化dis vis数组
        dis[i]=inf,vis[i]=0;
    q.push(s)//s作为起点
    dis[s]=0//此处开始与dij不同
    vis[s]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0; //出队列的时候改标记
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to,w=edge[i].dis;
            if(dis[v]>dis[u]+w)   //如果存在边使得两顶点距离更小进行更新
            {
                dis[v]=dis[u]+w;
                if(vis[v]==0)   //如果不在队列中就入队
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
}
int main()
{
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    //无向图  add(v,u,w);
    }
    spfa();
    for(int i=1;i<=n;i++)
        cout<<dis[i]<<" ";
    return 0;
}

多源最短路问题便可以用Floyd算法来完成求解。

Folyd算法 时间复杂度O(n^3) :

算法思想 (动态规划) :
d[i][j] = min ( d[i][j] , d[i][k] + d[k][j] ) // 从i顶点到j顶点只经过前k个点的最短路程
在由i 到 j 的最短路径可由 两个状态转移:
1.由i 到 j 的路径(只经过k-1个点)
2.由i 到k , 再由k 到j 的路径(经过k个点)
这里的k指的是能够作为中转点(即由i到j的途中经过的点) 的 顶点数。

	memset( d , 0x3f , sizeof(d) );  //初始化距离
	for(int i=1 ;i<=n ;i++) d[i][i]= 0; // 同一点距离为0
	for(int i=1 ;i<=m ;i++){
		int u, v, w;
		cin>>u>> v>> w;
		a[u][v]=min( a[u][v] , w);
	}
	for(int k=1 ;k<=n ;k++) //注意k为阶段必须放在最外层
		for(int i=1 ;i<=n ;i++)
			for(int j=1 ;j<=n ;j++)
				d[i][j] = min( d[i][j] , d[i][k] +d[k][j]);

还有,Floyd也不能解决带负权图的问题。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值