最短路径专题

最短路问题可以分类成两种:

1、单源最短路:给定源点 ss,试求 ss到图中任一顶点 vv的最短路长度。

2、多源最短路:求出任意一对顶点 (u, v)(u,v)之间的最短路距离。

通常情况下,我们需要根据问题的不同需要选择不同的算法:

1、根据问题模型分类,求解单源最短路需要使用 Dijkstra或 SPFA算法,求解多源最短路需要使用 Floyd算法。

2、根据边权分类,如果图中不存在负边权,则可以使用 Dijkstra算法,否则只能使用 Floyd 或 SPFA算法。

Floyd

最短路求解

Floyd算法实质上属于动态规划的思想。 Floyd算法用于求解多源最短路问题。它的效率较低,但是模型的可拓展性很高。通常情况下,除了需要灵感的题目,其余的难题均需要使用 Floyd算法实现。同时, Floyd算法的代码实现很简单。

Floyd算法的主要思想是:
设 dp[i][j]表示从顶点 i 到顶点 j 的最短路距离。另有一顶点 k, i 到 k、 k到 j均存在一条边。则 dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])

从 i到 j的最短路有两种形式:

1、直接从 i 到 j。

2、从 i 经过某个中转点 k,再从 k到达 j。

我们枚举每一个中转点 k k k,从 i i i j j j 的距离有两种选择:经过中转点 k k k d p [ i ] [ j ] = d p [ i ] [ k ] + d p [ k ] [ j ] dp[i][j] = dp[i][k] + dp[k][j] dp[i][j]=dp[i][k]+dp[k][j];取最小值即为从 i i i j j j的最短路。

Floyd算法的时间复杂度是 O ( n 3 ) O(n^3) O(n3),空间复杂度是 O ( n 2 ) O(n^2) O(n2)

Floyd模板

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
int n,m,x,y,a,b,w,f[maxn][maxn];
int main(){
	cin>>n>>m>>x>>y;
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=m;i++)cin>>a>>b>>w,f[a][b]=w,f[b][a]=w;
	for(int k=1;k<=n;k++)
	    for(int i=1;i<=n;i++)
	        for(int j=1;j<=n;j++)f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
	cout<<f[x][y];
}

SPFA

“关于 S P F A SPFA SPFA ,它死了”
SPFA是一种类似于 bfs的算法。它在大部分情况下速度会优于其他的最短路算法。 故而除非图中可能出现负环或负权边,否则不使用 S P F A SPFA SPFA

SPFA的主要思想是:使用队列保存可以松弛的顶点。每次取出队首的顶点,并松弛与其相邻的顶点。如果某一顶点 v被松弛,且其未被加入松弛队列,则将其加入松弛队列。重复以上操作,直到队列为空。通常情况下,我们使用 bool 数组表示顶点是否在队列中。

SPFA不能处理有负环的图,但是它可以判断图中是否存在负环。判断负环的方法有两种,其中第二种方法通常会更为高效:

  1. 如果图中有 n个顶点,且某个顶点的入队次数达到 n次,说明图中存在负环。
  1. 如果图中有 n个顶点,且某个顶点的最短路经过了超过 n个顶点,说明图中存在负环。

负环传送门

Spfa模板

bfs版spfa

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int n,m,x,y,a,b,w,tot,head[maxn],dis[maxn],vis[maxn];
queue<int>q;
struct edge{
	int to,next,w;
}e[maxn];
void add(int a,int b,int w){
	e[++tot].next=head[a];
	e[tot].w=w;
	e[tot].to=b;
	head[a]=tot;
}
void Spfa(){
	memset(dis,0x3f,sizeof(dis));
	dis[x]=0;
	q.push(x);
	vis[x]=1;
	while(!q.empty()){
		int tmp=q.front();
		q.pop();
		vis[tmp]=0;
		for(int i=head[tmp];i;i=e[i].next){
			if(dis[e[i].to]>dis[tmp]+e[i].w){
				dis[e[i].to]=dis[tmp]+e[i].w;
				if(!vis[e[i].to]){
					q.push(e[i].to);
					vis[e[i].to]=1;
					//cou[e[i].to]++;  //判负回,入队次数大于节点数
					//if(cou[e[i].to]>n){cout<<"-1";exit(0);};
				}
			}
		}
	}
}
int main(){
	cin>>n>>m>>x>>y;
	for(int i=1;i<=m;i++)cin>>a>>b>>w,add(a,b,w);//add(b,a,w);
	Spfa();
	//for(int i=1;i<=n;i++)cout<<dis[i]<<" ";
	cout<<dis[y];
	return 0;
}

d f s dfs dfs版本的 s p f a spfa spfa

bool spfa(int x) //bool类型为判负环
{
    vis[x]=1;
    for (int i=head[x];i;i=e[i].next)
    {
        int y=e[i].to;
        if (dis[y]<dis[x]+e[i].dis)
        {
            if (vis[y]) return 0;
            dis[y]=dis[x]+e[i].dis;
            vis[y]=1;
            if (!spfa(y)) return 0;
        }
    }
    vis[x]=0;
    return 1;
}

Dijkstra

Dijkstra算法是一种贪心算法。它用于求解单源最短路问题。它可以用于处理没有负边权的图,但是不能处理负边权,也不能用于求解最长路问题。通常情况下,脑洞题思维题都会用到 Dijkstra算法。

Dijkstra算法的主要思想是:我们每次取出一个最短路已经被更新的、最短路最短的顶点 u,此时从1(x)点到u点的距离已经确定,再也不能更改(这就是贪心的真理! ! !),然后用 u更新与其相邻的顶点 v,这步操作称为松弛。如果 v的最短路长度大于从源点 s到达 u,再从 u到达 v的路径长度,那么就将 v的最短路长度更新为最小值。

因为图中不存在负边权,所以每次我们取出的顶点 u的最短路长度一定不大于从图中另外一个未被松弛的顶点 v到达 u的最短路长度。故而 Dijkstra算法在不存在负边权的情况下正确。

因为我们需要维护最短路最小的顶点(维护最值),所以 Dijkstra算法可以使用堆优化。使用堆优化后, Dijkstra算法的时间复杂度是 O(nlogn + m),其中 n为点数, m为边数。堆优化的实现可以使用 set,也可以使用优先队列,推荐使用后者。

Dijkstra模板

#include<bits/stdc++.h>
using namespace std;
const int maxn=4*1e5+5;
const int maxm=1e5+5;
int n,m,head[maxn],a,b,w,tot,dis[maxn],vis[maxn];
struct edge{
	int to,next,w;
}g[maxn];
struct node{
	int i,dis;
	bool operator < (const node tmp)const{
		return tmp.dis<dis;
	}
};
void add(int a,int b,int w){
	g[++tot].to=b;
	g[tot].next=head[a];
	g[tot].w=w;
	head[a]=tot;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++)cin>>a>>b>>w,add(a,b,w);
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	priority_queue<node>q;
	q.push((node){1,0});
    while(!q.empty()){
        node temp=q.top();
        q.pop();
        //cout<<temp.i<<" "<<temp.dis<<endl;
        int now=temp.i;
        if(vis[now]==1) continue;
        vis[now]=1;
        for(int i=head[now];i;i=g[i].next){
            int next=g[i].to;
            if(dis[next]>dis[now]+g[i].w){
                dis[next]=dis[now]+g[i].w;
                q.push((node){next,dis[next]});
            }
        }
    }
    cout<<dis[n];
	//for(int i=1;i<=n;i++)cout<<dis[i]<<" ";
}

次短路模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define INF 0x3f3f3f3f
using namespace std;
const int M=200000+10;
int head[M],cnt;
struct node
{
    int v,w,nxt;
}edge[M];
struct edg{
    int i,dis;
    bool operator < (const edg tmp)const{
        return tmp.dis<dis;
    }
};
int dis1[5005];
int dis2[5005];
void add(int x,int y,int w)
{
    edge[++cnt].nxt=head[x];
    edge[cnt].v=y;
    edge[cnt].w=w;
    head[x]=cnt;
}
void Dijkstra(int x)
{
    memset(dis1,INF,sizeof(dis1));
    memset(dis2,INF,sizeof(dis2));
    priority_queue<edg>qu;
    dis1[x]=0;//最短路初始值为0,次短路无穷大
    qu.push((edg){x,0});
    while(!qu.empty( ))
    {
        int w=qu.top( ).dis;//弹出最小值,或许是最短路,或许是次短路
        int u=qu.top( ).i;
        qu.pop( );
        if(dis2[u]<w)//弹出来的值比当前的次短路大,就可以跳过这个
        {
            continue;
        }
        for(int i=head[u];i;i=edge[i].nxt)
        {
            int v=edge[i].v;
            int cost=w+edge[i].w;//u到v的花费
            if(dis1[v]>cost)//花费大于原来的最小值,更新最短路
            {
                swap(dis1[v],cost);//交换值
                qu.push((edg){v,dis1[v]});//压入队列
            }
            if(dis2[v]>cost&&cost>dis1[v])//交换次短路
            {
                swap(dis2[v],cost);
                qu.push((edg){v,dis2[v]});//压入队列,之所以次短路要压入队列是因为后面更新需要。
                                             //例子:dis[2] = 10, dis2[2] = 20 有一条边 2 到 6 的边权值为 5                 
                                            //如果不把 dis2 入队,那么之后的算法中 dis[6] = 15, dis2[6] = INF              
                                           //只有当队列里有 20 这个值,才能 20+5 得出 25,然后更新 dis2[6] = 25 
            }
        }
    }
}
int main( ){
    int n,r;
    scanf("%d%d",&n,&r);
    int x,y,w;
    for(int i=1;i<=r;i++){
        scanf("%d%d%d",&x,&y,&w);
        add(x,y,w);
        add(y,x,w);
    }
    Dijkstra(1);
    printf("%d",dis2[n]);
    return 0;
}

记录最短路径

spfa

void spfa(int st)
{
	queue<int> q;
	for(int i = 1; i <= n + 100; ++i) dis[i] = INT_MAX;
	dis[st] = 0;
	q.push(st);
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for(int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].v;
			if(dis[v] > dis[u] + e[i].w)
			{
				dis[v] = dis[u] + e[i].w;
				pre[v] = i;
				fr[v] = u;
				if(!vis[v])
				{
					q.push(v);
					vis[v] = 1;
				}
			}
		}
	}
}

int now = n;
while(now != 1)
{
	t[++cnt] = pre[now];
	now = fr[now];
}

dijkstra

void dij(int st)
{
    priority_queue<edg> q;
    memset(dis, 0x3f3f3f3f, sizeof dis);
    memset(vis, 0, sizeof vis);
    dis[st] = 0;
    q.push({st, 0});
    while (!q.empty())
    {
        edg tmp = q.top();
        int u = tmp.i;
        q.pop();
        if (vis[u])
            continue;
        vis[u] = 1;
        for (int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].v;
            if (dis[v] > dis[u] + e[i].w)
            {
                dis[v] = dis[u] + e[i].w;
                pre[v] = u;
                if (i % 2)
                    pred[v] = i;
                else
                    pred[v] = i - 1;
                if (!vis[v])
                {
                    q.push({v, dis[v]});
                }
            }
        }
    }
}

int now = n;
while (now != 1)
{
	iq[++bb] = pred[now];
	now = pre[now];
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值