当前位置:首页 » 《资源分享》 » 正文

最短路_u011612364的博客

29 人参与  2021年03月15日 17:03  分类 : 《资源分享》  评论

点击全文阅读


https://ac.nowcoder.com/acm/contest/12606/H
大一就学了的东西但是一直没有系统总结过,正好碰到一个最短路题目直接开干。

Dijkstra:适用于权值为非负的图的单源最短路径,用斐波那契堆的复杂度O(E+VlgV)
BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE)
SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).
Floyd:每对节点之间的最短路径。

先给出结论:
(1)当权值为非负时,用Dijkstra。
(2)当权值有负值,且没有负圈,则用SPFA,SPFA能检测负圈,但是不能输出负圈。
(3)当权值有负值,而且可能存在负圈,则用BellmanFord,能够检测并输出负圈。
(4)SPFA检测负环:当存在一个点入队大于等于V次,则有负环

spfa

时间复杂度 O ( k E ) O(kE) O(kE), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

优点:
1.适用范围广。
2.能够计算负权图问题。
3.能够判断是否有负环 (即:每跑一圈,路径会减小,所以会一直循环跑下去)。

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

判断负环:如果有一个点进入队列次数超过n次则有负环。

运用:实际上spfa相当于一个用队列优化的Bellman-Ford,时间复杂度很玄学,最坏情况下时间复杂度就退化为的 O ( V E ) O(VE) O(VE)复杂度。
在正边的时候有可能会被卡,可以用于负边权存在时的最短路,正边时还是建议用堆优化后的迪杰斯特拉。(但是它好写,而且适用范围广,蓝桥杯这种可以骗分。)

代码(使用vector存储邻接表)

#include<bits/stdc++.h>
#include<iostream>
#include <stdio.h>
using namespace std;
const int maxn=100005;
const int base=131;
typedef long long ll;
#define pi acos(-1)
#define INF 0x3f3f3f3f
#define mod 998244353
const int inf=1<<30;
int n,m;
struct edge{
    int u,v,w;//出发点,目标点,距离
}ed[maxn];
vector<edge> g[maxn];//邻接表
int vis[maxn],dis[maxn];//标记i是否在队列中,起始点到i点的距离
void sqfa(int s)
{
    queue<int> q;
    memset(vis,0,sizeof(vis));
    memset(dis,INF,sizeof(dis));//初始化
    q.push(s);//起始点
    dis[s] = 0;
    vis[s] = 1;
    while(!q.empty()){
        int x = q.front();
        q.pop();
        vis[x] = 0;
        for(int i = 0;i < g[x].size() ;i++){
            edge nx = g[x][i];
            //如果更新的结果更好,进行松弛操作,将下一个点距离更新,并加入队列
            if(dis[nx.v] > dis[x] + nx.w){
                dis[nx.v] = dis[x] + nx.w;
                if(!vis[nx.v]){
                    q.push(nx.v);
                    vis[nx.v]  = 1;
                }
            }
        }
    }
}
int main()
{
    
    cin>>n>>m;
    int u,v;
    for(int i = 1 ;i <= m ;i++){
        cin>>u>>v;
        edge e1,e2;
        e1.u = e2.v = u;
        e1.v = e2.u = v;
        e1.w = e2.w = 1;
        g[u].push_back(e1);
        g[v].push_back(e2);
    }
    sqfa(1);
    cout<<dis[n]-1<<endl;
    return 0;
}

Dijkstra

普通的迪杰斯塔拉就懒得在写了,主要是它的堆优化。
不优化的时候时间复杂度是 O ( n 2 ) O(n^2) O(n2),优化后可以到 O ( m l o g n ) O(mlog_n) O(mlogn)
用堆维护最小的dis节点,就不用每次都 O ( n ) O(n) O(n)的查询了。c++的优先队列适用队实现的,直接优先队列就ok。

#include<bits/stdc++.h>
#include<iostream>
#include <stdio.h>
using namespace std;
const int maxn=100005;
const int base=131;
typedef long long ll;
#define pi acos(-1)
#define INF 0x3f3f3f3f
#define mod 998244353
const int inf=1<<30;
int n,m;
struct edge{
    int u,v,w;//出发点,目标点,距离
}ed[maxn];
vector<edge> g[maxn];//邻接表
int vis[maxn],dis[maxn];//标记i是否在队列中,起始点到i点的距离
priority_queue< pair<int,int> > q;//大顶队,first为排序依据,存距离,second存对应的节点
void dil(int s)
{
    memset(vis,0,sizeof(vis));
    memset(dis,INF,sizeof(dis));//初始化
    q.push(make_pair(0,s));//起始点
    dis[s] = 0;
    while(!q.empty()){
        int x = q.top().second;
        q.pop();
        vis[x] = 1;
        for(int i = 0;i < g[x].size() ;i++){
            edge nx = g[x][i];
            //如果更新的结果更好,进行松弛操作,将下一个点距离更新,并加入队列
            if(dis[nx.v] > dis[x] + nx.w){
                dis[nx.v] = dis[x] + nx.w;
                q.push(make_pair(-dis[nx.v],nx.v));//因为是大顶堆,存相反数
            }
        }
    }
}
int main()
{
    
    cin>>n>>m;
    int u,v;
    for(int i = 1 ;i <= m ;i++){
        cin>>u>>v;
        edge e1,e2;
        e1.u = e2.v = u;
        e1.v = e2.u = v;
        e1.w = e2.w = 1;
        g[u].push_back(e1);
        g[v].push_back(e2);
    }
    dil(1);
    cout<<dis[n]-1<<endl;
    return 0;
}

点击全文阅读


本文链接:http://m.zhangshiyu.com/post/16301.html

队列  复杂度  最短  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1