首页 > 其他分享 >最短路

最短路

时间:2022-12-05 22:36:41浏览次数:30  
标签:right int 短路 left 节点 dis

最短路

记号约定

\(n\)是点数,\(m\)是边数。

\(s\)是源点。

\(D\left(u\right)\)是从\(s\)点到\(u\)点的实际最短路,\(D\left(u,v\right)\)是从\(u\)点到\(v\)点的实际最短路。

\(dis\left(u\right)\)是从\(s\)点到\(u\)点的即时最短路,\(dis\left(u,v\right)\)是从\(u\)点到\(v\)点的即时最短路。

\(w\left(u,v\right)\)是从\(u\)点到\(v\)点的边权值。

说明

由于全源最短路中的\(Johnson\)算法需要\(SPFA\)与\(Dijkstra\)的基础,所以先介绍单源最短路。

若一个图上存在负环,即负权边连成的环,就是负环。在有负环的图中,我们可以不断的绕着负环,所以最短路为\(-\infty\)。

单源最短路

单源最短路,是从一个源点到其余点的最短路。

\(SPFA\)

即\(Shortest\ Path\ Faster\ Algorithm\) (更快的最短路)

松弛操作

对于节点\(u\),对于所有与\(u\)点相连的\(v\)节点,使\(dis\left(v\right)=\min\left(dis\left(u\right),dis\left(u\right)+w\left(u,v\right)\right)\)

这么做的目的是显然的,用\(s\to u\)的最短路去更新\(s\to v\)的最短路。

过程

我们发现只有被进行过松弛操作的节点才有用来松弛的价值,这是显然的,如果没有经过松弛,就没有可能更新最短路。

所以我们可以使用一个队列,将被松弛过的节点放入队列中,每次从队头取出节点进行松弛,最后队列为空,就表示最短路算法的结束。

在实际实现中,要判断是否存在负环。实际上,我们可以证明一个节点最多被松弛\(n\)次,所以在实现中,我们记录每个节点松弛的次数,就能判断出负权图。

性质

时间复杂度\(\Omega\left(km\right)\)或\(O\left(nm\right)\),\(k\)为常数,一般为二。

由于代码中没有对最短路的不降要求,所以可以用于有负权边的图。

\(Code:\)
void SPFA(int s){
    memset(dis,0x7f,sizeof(dis));
    memset(vis,false,sizeof(vis));
    dis[s]=0;
    vis[s]=true;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i;i=nxt[i]){
            int v=to[i];
            if(dis[v]>dis[u]+val[i]){
                dis[v]=dis[u]+val[i];
                if(!vis[x]){
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
}

\(Dijkstra\)

\(Dijkstra\)只能用在非负权边的图上。

过程

将原图中的点分为两个集合,\(T\)所有未确定最短路的节点集合,\(S\)为已确定的集合,显然\(D\left(s\right)=dis\left(s\right)=0\)。

从源点开始,反复重复下列操作:

\(1.\)从\(T\)集合中取出最短路最小的节点\(u\),放入集合\(S\)。

\(2.\)用\(u\)去松弛所有出边\(\left(u,v\right)\)。

直到\(T=\emptyset\),算法结束。

正确性证明

\(T\)集合为未确定最短路的集合,移出\(T\)集合就意味着\(u\)点的最短路确定,即\(dis\left(u\right)=D\left(u\right)\),证明的关键,就是证明只用一次松弛就能将最短路确定下来。

我们设\(k\)为当前操作次数。

命题:\(\forall k\in N_{+},D\left(u\right)=dis\left(u\right)\)

证明:

当\(k=1\)时,\(S=\left\{s\right\}\),\(D\left(u\right)=dis\left(u\right)=0\)

设当\(k=l\)时成立,当\(k=l+1\)时:

记\(v\)点是\(l+1\)步时选择的节点,我们设\(v\)点是由\(u\)点松弛的,则\(u\in S\),即\(D\left(u\right)=dis\left(u\right)\)。

若\(v\)点的最短路径不是\(s\to u\to v\),则在\(v\)点的最短路径上有一个或多个节点\(\in T\),即存在一条路径\(s\to x\to y\to v\),其中\(y\)是路径上第一个不属于\(T\)集合的节点,\(x\)是\(y\)的前驱节点。

因为边权非负,有\(D\left(y\right)\le dis\left(y\right)\le D\left(v\right)\le dis\left(v\right)\)

由于当\(v\)节点被取出时,\(y\)节点没有被取出,所以\(dis\left(v\right)\le dis\left(y\right)\)

\(\therefore D\left(v\right)=dis\left(v\right)\)

\(\therefore s\to u\to v\)是\(v\)点的最短路

由数学归纳法得,命题成立。

性质

根据实现方式的不同,有不同的时间复杂度。

优先队列:\(O\left(m\log m\right)\)

\(Code:\)
struct node{
    int num,dist;
    bool operator <(const node &a)const{return dist>a.dist;}
    node(int a,int b){
        num=a,dist=b;
    }
};
priority_queue<node>q;
void Dijkstra(int s){
    memset(vis,false,sizeof(vis));
    memset(dis,0x7f,sizeof(dis));
   	dis[s]=0;
    q.push(node(s,0));
    while(!q.empty()){
        int point=q.top().num;
        q.pop();
        if(vis[point])continue;
        vis[point]=true;
        for(int i=head[point];i;i=nxt[i]){
            int son=to[i];
            if(dis[son]>dis[point]+1){
                dis[son]=dis[point]+1;
                q.push(node(son,dis[son]));
            }
       	}
    }
}

全源最短路

即任意两点之间的最短路。

\(Floyed\)

原理

略。

性质

无负环图。

时间复杂度\(O\left(n^{3}\right)\),空间复杂度\(O\left(n^{2}\right)\)

\(Code:\)
for (k = 1; k <= n; k++) {
  	for (x = 1; x <= n; x++) {
    	for (y = 1; y <= n; y++) {
      		f[x][y] = min(f[x][y], f[x][k] + f[k][y]);
    	}
  	}
}

\(Johnson\)

(挖坑)

标签:right,int,短路,left,节点,dis
From: https://www.cnblogs.com/jd122/p/16953735.html

相关文章