图论--最短路算法

图论

图论是OI比赛中最重要的部分 。OI中除了送分题和数论题,基本都是图论的题目。

最短路是图论中一个比较核心的问题。在一个图中(有向,无向//有环,无环//有负边权,无负边权)两个点,如何找到一个花费最小的路径呢?

在高中我接触到了4个最短路算法:Floyd,Dijkstra,Bellman-Ford,SPFA
今天只打算讲除了Bellman-Ford之外的三个算法。
你都有SPFA了干嘛还要写BF

Floyd:

本算法的复杂度是算法界中首屈一指的高:O(n³)
但是,它被沿用的原因有两个:

 1.对Floyd单次的调用可以获得所有的点对之间的最短路径。
 2.可以处理正负边权的情况。

本算法的三个循环:
第一层循环是对中间点的穷举;
第二层循环是对起点的穷举;
第三层循环是对终点的穷举;

而确立最短路径的地过程,更像是一个动态规划:

  • 状态转移方程:map[i,j]:=min{map[i,k]+map[k,j],map[i,j]};
    其中map记录了最短路的情况

经过这个动态转移方程的描述,可以更加清楚地看到Floyd的本质。

代码:

for(k=1;k<=n;k++)
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++){
            if(d[i][k]+d[k][j]<d[i][j]){
                d[i][j]=d[i][k]+d[k][j];
                path[i][j]=path[i][k];
            }
        }

Dijkstra:

这个算法用于处理一对结点之间的问题,算法复杂度在n²。
不能处理含有负边权的情况。

dijkstra的思路其实有一些像prim算法的思路(笑
所以其实我懒得写了?

过程

我们将整个图中的点分为两个集合;
一个集合是已扩展的点集,S,一个集合是未扩展的,V。
显而易见,一开始S是空集,而V包含所有的点。

第一步,我们将起点加入S。
然后,选择S连接的点中,花费(边权)最 小的点。加入S。
然后更新这个集合所连接的所有的点的花费。
(一步步…一步步…..直到所有的点都扩展了,我们就得到了终点的最终花费)

代码;

#include<cstdio>
#include<algorithm>
#include<iostream>
#define maxn 100005
using namespace std;
struct node{
    int next;
    int to;
    int w;
}edge[maxn];
struct Node{
    int w;
    bool ty;
}dis[maxn];
int n,m,q,e=0,head[maxn];
void add(int u,int v,int d){
    edge[++e].next=head[u];
    edge[e].to=v;
    edge[e].w=d;
    head[u]=e;
    return ;
}
void dij(int x){
    int c=n-1;
    dis[x].ty=1;
    for(int i=head[x];i!=0;i=edge[i].next)
      dis[edge[i].to].w=edge[i].w;
    while(c>0){
        int min=900000000,p;
        for(int i=1;i<=n;i++){
            if(dis[i].ty==0&&dis[i].w!=0&&dis[i].w<min){
                p=i;
                min=dis[i].w;
            }
        }
        for(int i=head[p];i!=0;i=edge[i].next){
            if(dis[edge[i].to].ty!=1){
                if(dis[edge[i].to].w==0||dis[edge[i].to].w>dis[i].w+edge[i].w)
                  dis[edge[i].to].w=dis[p].w+edge[i].w;
            }
        }
        dis[p].ty=1;
        c--;
    }
    return ;
}
int main(){
    int u,v,d,x,y;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&d);
        add(u,v,d);
        add(v,u,d);
    }
    scanf("%d",&q);
    for(int i=1;i<=q;i++){
        scanf("%d%d",&x,&y);
        dij(x);
        printf("%d\n",dis[y].w);
        for(int j=1;j<=n;j++){
            dis[j].w=0;
            dis[j].ty=0;
        }
    }
    return 0;
}

SPFA(Shortest Path Faster Algorithm):

SPFA的另一个作用:判定图中是否含有负数环23333

用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。
我们采取的方法是动态逼近法

设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

如果一个点进入队列达到n次,则表明图中存在负环,没有最短路径。

复杂度:
期望时间复杂度:O(me), 其中m为所有顶点进队的平均次数,可以证明m一般小于等于2n:

 “算法编程后实际运算情况表明m一般没有超过2n.事实上顶点入队次数m是一个不容易事先分析出来的数,但它确是一个随图的不同而略有不同的常数.所谓常数,就是与e无关,与n也无关,仅与边的权值分布有关.一旦图确定,权值确定,原点确定,m就是一个确定的常数.所以SPFA算法复杂度为O(e).证毕."(SPFA的论文)

伪代码;

ProcedureSPFA;
Begin
    initialize-single-source(G,s);
    initialize-queue(Q);
    enqueue(Q,s);
    while not empty(Q) do begin
        u:=dequeue(Q);
        for each v∈adj[u] do begin
            tmp:=d[v];
            relax(u,v);
            if(tmp<>d[v])and(not v in Q)then enqueue(Q,v);
        end;
    end;
End; 

求最短路不要拘束于这四种算法,很多算法会有出其不意的效果。
比如我们可以把一个图拽起来,变成一棵树。
然后最短路就是求LCA了233333

 求出LCA时,记录逆dfs序列,直接写出路径。

SPFA给我们做了一个好榜样,我们要自己去琢磨新的算法,新的思路。

最后还是祝各位OIer武运昌隆!

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值