關於最短路


[前言&胡扯]

\(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

相關文章
相關標籤/搜索