关于最短路


[前言&胡扯]

\(By:Soroak\)
说到最短路问题,咱们无非就是用三种算法进行处理:
①: \(Floyd\) 。②: \(Dijkstra\) 。③: \(Spfa\)
对于最短路算法,学长以前给咱们讲过,但是当时我由于不想听课,上课走神等缘由,成功的没有学扎实,如今来从新 复习 从新学习一下c++


[Floyd]

  • 定义: \(Floyd\) 算法,是解决任意两点间的最短路径的一种算法,能够正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。
  • 时间&空间复杂度:
    \(Floyd\) 算法的时间复杂度为 \(O(N^3)\) ,空间复杂度为 \(O(N^2)\)
  • \(Floyd\) 能够说是最暴力的一个算法了,用 \(Floyd\) 的时候,每每用邻接矩阵来存储。
    Q:为何用邻接矩阵来存而不是邻接表来存呢??
    A:没有那个必要啊,Floyd的时间复杂度是 \(O(N^3)\)\(O(N^3)\) 能跑的范围,邻接矩阵彻底能够存过来,不过你要是实在想写邻接表来存,我也不拦你用邻接表来存 $ Floyd$ 用 \(1s\) 就 能跑过来的图。。。

如下是 \(Floyd\) 的模板:git

#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long int 

using namespace std;

int map[5010][5010];
int n,m;

const int INF=0x7fffffff; 

signed main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            map[i][j]=INF;
        }
    }
    for(int i=1;i<=n;i++)
    {
        map[i][i]=0;
    }
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        map[u][v]=w;
        map[v][u]=w;
    }
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(map[i][j]>map[i][k]+map[k][j]&&k!=i&&k!=j&&i!=j)
                {
                    map[i][j]=map[i][k]+map[k][j];
                }               
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cout<<map[i][j]<<" ";
        }
        cout<<endl;
    }
    return 0;
} 

/*样例输入
4 6 
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
输出:
0 2 4 3
2 0 2 1
4 2 0 3
3 1 3 0
*、

[Dijkstra及其优化]

  • 定义: \(Dijkstra\) 算法是典型的单源最短路径算法,用于计算一个节点到其余全部节点的最短路径。主要特色是以起始点为中心向外层层扩展,直到扩展到终点为止。算法

  • Q&A:
    Q: \(Dijkstra\)\(Floyd\) 好在哪里呢??
    A:上面说过 \(Dijkstra\) 是典型的单源最短路径算法,何谓单源最短路??就是给定一个起始点,求这个点到全部点的最短路,求一遍 \(Dijkstra\) (非优化)的时间复杂度是 \(O(N^2)\) ,你要是想求出每一个点到每一个点的最短路,我仍是建议你用 \(Floyd\) ,由于 \(Dijkstra\)\(N\) 遍单源最短路的复杂度和 \(Floyd\) 同样都是 \(O(N^3)\) ,固然,这里说的都是未优化的状况,优化的 \(Dijkstra\) 下面会讲到。闭包

  • \(Dijkstra\) 是一种相似于贪心的算法,具体步骤:
    • 当到一个时间点时,图上已经有一部分的点,最短路径已经肯定,而部分点还没有肯定。
    • 在全部未肯定的点中选择离原点最近的点,把它认为是这两个点之间的最短距离。
    • 而后把这个点全部的出边都遍历一边,并更新全部点。

\(Dijkstra\) 从随意一个点出发,到达各点的最短路径长度的代码以下:学习

//#include<iostream>
//#include<cstdio>
//#include<cstring>
//#define int long long int 
//
//using namespace std;
//
//int map[5010][5010];
//int dis[5010];
//int fl[5010];
//int n,m,s;
//const int inf=2147483647;
//
//inline void dijkstra(int u)
//{
//  memset(dis,63,sizeof(dis));
//  int start=u;
//  fl[start]=1;
//  for(int i=1;i<=n;i++)
//  {
//      dis[i]=min(dis[i],map[start][i]);
//  }
//  for(int i=1;i<=n-1;i++)
//  {
//      int minn=inf;
//      for(int j=1;j<=n;j++)
//      {
//          if(fl[j]==0&&minn>dis[j])
//          {
//              minn=dis[j];
//              start=j;
//          }
//      }
//      fl[start]=1;
//      for(int j=1;j<=n;j++)
//      {
//          dis[j]=min(dis[j],dis[start]+map[start][j]);
//      }
//  }
//}
//
//signed main()
//{
//  cin>>n>>m>>s;
//  memset(map,63,sizeof(map));
//  for(int i=1;i<=m;i++)
//  {
//      int a,b,c;
//      cin>>a>>b>>c;
//      map[a][b]=c;
//      map[b][a]=c;
//  }
//  for(int i=1;i<=n;i++)
//  {
//      map[i][i]=0;
//  }
//  dijkstra(s);
//  for(int i=1;i<=n;i++)
//  {
//      cout<<dis[i]<<" ";
//  }
//  return 0;
//}

#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long int 

using namespace std;

int value[10010];
int to[10010];
int nxt[10010];
int head[10010];
int total;
int fl[10010];
int dis[10010];
int n,m,s;

inline void add(int a,int b,int c)
{
    total++;
    to[total]=b;
    value[total]=c;
    nxt[total]=head[a];
    head[a]=total;
}

void dijkstra(int u)
{
    memset(dis,63,sizeof(dis));
    memset(fl,0,sizeof(fl));
    dis[u]=0;
    for(int i=1;i<n;i++)
    {
        int start=-1;
        for(int j=1;j<=n;j++)
        {
            if(fl[j]==0&&(dis[start]>dis[j]||start==-1))
            {
                start=j;
            }
        }
        fl[start]=1;
        for(int e=head[start];e;e=nxt[e])
        {
            dis[to[e]]=min(dis[to[e]],dis[start]+value[e]);
        }
    }
}

signed main()
{
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dijkstra(s);
    for(int i=1;i<=n;i++)
    {
        cout<<dis[i]<<" ";
    }
    cout<<endl;
    return 0;
}
  • Q&A
    Q:那 \(Dijkstra\) 的时间复杂度是 \(O(N^2)\) ,那跑 \(N\) 遍单元最短路的复杂度不就是 \(O(N^3)\) 了吗,那和 \(Floyd\) 没有什么区别啊
    A:别着急,上面的是未优化的,咱们还有堆优化过的 \(Dijkstra\),跑一遍的时间复杂度就成了 \(O(NlogN)\)

下面上代码:
(感谢gyh大佬的大力支持)优化

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>

const int INF=2147483647;
const int MARX=1e5+10;
using namespace std;

struct edge
{
    int u,v,w,ne;
}e[MARX<<1];

struct p
{
    int num,diss;
    bool operator<(const p &a)const
    {
        return diss>a.diss;
    }
}tmp;

int head[MARX],dis[MARX];
bool f[MARX]; 
int num,n,m,s,x;

void add(int u,int v,int w)
{
    e[++num].ne=head[u];
    head[u]=num;
    e[num].u=u;
    e[num].v=v;
    e[num].w=w;
}

void dj(int s)
{
    priority_queue <p> q;
    tmp.num=s;
    tmp.diss=0;
    q.push(tmp);
    for(int i=1;i<=n;i++) 
    {
        dis[i]=INF; 
    }
    dis[s]=0;
    while(!q.empty())
    {
        int top=q.top().num; 
        q.pop();
        if(f[top]) 
        {
            continue;
        }
        f[top]=1;
        for(int j=head[top];j;j=e[j].ne)//找k点的临点,并进行比较 
        {
            if(dis[e[j].v] > dis[top]+e[j].w && (!f[e[j].v]))
            {
                dis[e[j].v] = dis[top]+e[j].w;
                tmp.num=e[j].v;
                tmp.diss=dis[e[j].v];
                q.push(tmp);
            }
        }
    }
}

signed main()
{
    scanf("%d%d%d",&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);
    }
    dj(s);
    for(int i=1;i<=n;i++) 
    {
        printf("%d ",dis[i]);
    }  
    return 0; 
}

/*
//
By:Luckyblock
使用STL中的 pair类实现 
简单好写 
#include<cstdio>
#include<cstring>
#include<ctype.h>
#include<queue>
#define int long long
const int MARX = 2e6+10;
//=============================================================
struct edge
{
    int u,v,w,ne;
}e[MARX<<1];
int n,m,num, head[MARX];
int dis[MARX];
bool vis[MARX];
//=============================================================
inline int read()
{
    int s=1, w=0; char ch=getchar();
    for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
    for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
    return s*w;
}
void add(int u,int v,int w)
{

    e[++num].u = u,e[num].v = v, e[num].w = w;
    e[num].ne = head[u], head[u] = num;
}
void dijkstra(int start)
{
    std::priority_queue <std::pair<int,int> > q;
    memset(dis,63,sizeof(dis));
    dis[start] = 0 ;
    q.push(std::make_pair(0,start));
    
    for(; !q.empty(); )
    {
      std::pair <int,int> top = q.top();  q.pop();
      if(vis[top.second]) continue;
      vis[top.second] = 1;
      
      for(int i=head[top.second]; i; i = e[i].ne)
        if(dis[e[i].v] > dis[e[i].u] + e[i].w)
        {
          dis[e[i].v] = dis[e[i].u] + e[i].w;
          q.push(std::make_pair(-dis[e[i].v], e[i].v));
        }
    }
}
//=============================================================
signed main()
{
    n = read(), m = read();
    int s = read();
    for(int i=1; i<=m; i++) 
    {
      int u = read(), v = read(), w = read();
      add(u,v,w);
    }
    dijkstra(s);
    for(int i=1; i<=n; i++) printf("%lld ",dis[i]);
}
*/

[SPFA及其优化]

  • 定义:\(SPFA\) 算法是 \(Bellman-Ford\) 算法 的队列优化算法的别称,一般用于求含负权边的单源最短路径,以及判负权环。
  • \(SPFA\) 通常是用于判断负环的,要是题目中给的图并无负边权,那么仍是建议用 \(Dijkstra\) 。。。
  • Q&A
    Q:那怎么判断是否有负环呢??
    A:其实很简单,就只要判断一下是否有一个点入队次数超过 \(N\) 就行了。
  • 实现方法:创建一个队列,初始时队列里只有起始点,在创建一个表格记录起始点到全部点的最短路径(该表格的初始值要赋为极大值,该点到他自己的路径赋为0)。而后执行松弛操做,用队列里有的点去刷新起始点到全部点的最短路,若是刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

\(SPFA\) 代码以及优化:(在这里再次感谢gyh大佬的支持)ui

#include<cstdio>
#include<queue>
#define INF 2147483647

using namespace std;

queue<int>q;

struct edg
{
    int u,v,next;
    int w;
} edge[5000500];

int head[3000000];
int ans[3000000],vis[3000000];
int num=0;
int s,t;

void build(int u,int v,int w)
{
    edge[++num].next=head[u];
    head[u]=num;
    edge[num].u=u;
    edge[num].v=v;
    edge[num].w=w;
}

void SPFA(int s)
{
    ans[s]=0;
    vis[s]=1;
    q.push(s);
    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].v;
            if(ans[v]>edge[i].w+ans[u])
              {
                ans[v]=edge[i].w+ans[u];
                if(!vis[v])
                  {
                    q.push(v);
                    vis[v]=1;
                  }
              }

          }
      }
}

int main()
{
    int n,m,s;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1; i<=n; i++)
      ans[i]=INF;
    for(int i=1; i<=m; i++)
      {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        build(a,b,c);
      }
    SPFA(s);
    for(int i=1; i<=n; i++)
      printf("%d ",ans[i]);
    return 0;
}

//==========简单好用优先队列优化==========
//优化很成功,时间复杂度较低,吊打DJ BOBO
/*
//将queue改成priority_queue,注意将q.front()改成q.top()
//同时自定义优先级 , 以最短路径长度升序排列 
#include<cstdio>
#include<queue>
#define INF 2147483647
using namespace std;

struct edg
{
    int u,v,next;
    int w;
} edge[5000500];
int head[3000000];
int ans[3000000],vis[3000000];
int num=0;
int s,t;
struct cmp1
{
    bool operator ()(const int a,const int b)
      {
        return ans[a]>ans[b];
      }
};
priority_queue <int,vector<int>,cmp1> q;
void build(int u,int v,int w)
{
    edge[++num].next=head[u];head[u]=num;
    edge[num].u=u;edge[num].v=v;edge[num].w=w;
}
void SPFA(int s)
{
    ans[s]=0;
    vis[s]=1;
    q.push(s);
    while(!q.empty())
    {
      int u=q.top();
      q.pop();
      vis[u]=0;
      for(int i=head[u]; i; i=edge[i].next)
      {
        int v=edge[i].v;
        if(ans[v]>edge[i].w+ans[u])
        {
          ans[v]=edge[i].w+ans[u];
          if(!vis[v])
          {
            q.push(v);
            vis[v]=1;
          }
        }
      }
    }
}
int main()
{
    int n,m,s;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1; i<=n; i++) ans[i]=INF;
    for(int i=1; i<=m; i++)
    {
      int a,b,c;
      scanf("%d%d%d",&a,&b,&c);
      build(a,b,c);
    }
    SPFA(s);
    for(int i=1; i<=n; i++)
      printf("%d ",ans[i]);
}
*/
//==========可能负优化的LLL+SLF优化================
/*
#include<bits/stdc++.h>
#define mokou 2147483647
using namespace std;
struct edg
{
    int u,v,w,next;
}asd[5000550];
int sum,n,m,s,vis[150000],sp[150000],head[150000];
void put(int a,int b,int c)
{
    asd[++ sum].u = a;
    asd[sum].v = b;
    asd[sum].w = c;
    asd[sum].next = head[a];
    head[a] = sum;
}
deque<int>q;
int main(){
    int cnt = 1,tot = 0;
    cin >> n >> m >> s;
    for(int i = 1;i <= m;i ++)
    {
        int u,v,w;
        cin >> u >> v >> w;
        put(u,v,w);
    }
    for(int i = 1;i <= n;i ++)
        sp[i] = mokou;
    sp[s] = 0;
    vis[s] = 1;
    q.push_front(s);
    while(! q.empty())
      {
        int x = q.front();
        while(cnt * sp[x] > tot)                                             
          {
            q.pop_front();
            q.push_back(x);
            x = q.front();
          }
        q.pop_front();
        cnt --, tot -= sp[x], vis[x] = false;                                     
        for(int i = head[x]; i; i = asd[i].next)
            if(sp[asd[i].v] > sp[x] + asd[i].w)
              {
                sp[asd[i].v] = sp[x] + asd[i].w;
                if(! vis[asd[i].v])
                  {
                    vis[asd[i].v] = 1;
                    if(q.empty() || sp[asd[i].v] > sp[q.front()])   
                        q.push_back(asd[i].v);
                    else                                        
                        q.push_front(asd[i].v);
                    cnt++, tot += sp[asd[i].v];                                 
                  }
              }
      }
    for(int i = 1;i <= n;i ++)
      cout << sp[i] << " ";
}
*/

有什么问题能够在下方评论留言或找我私聊哦
\(QQ:2876140034\)
\(Luogu:Soroak\)spa

相关文章
相关标签/搜索