最短路

今天lyq大佬問了菜雞我一道最短路的題,結果把我問懵逼了,WC ,最短路忘乾淨了,咕咕咕,嚇得我趕忙去看了看最短路,順便水一篇博客c++

floyed

這東西是個區間dp,找了中間點來更新區間的最優值算法

沒什麼好說的就是\(3\)層循環跑,也沒什麼用數組

適用範圍:無負權迴路便可,邊權可正可負,運行一次算法便可求得任意兩點間最短路優化

時間複雜度:O(\(n^3\))這複雜度除非CCF用神威太湖之光給你跑,不然就會TLEspa

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<stack>
#include<queue>
using/nnamespace/nstd;
int/na[101][3];
double/nf[101][101];
int/nn,i,j,k,x,y,m,s,e;
int/nmain()
{
    cin>>n;
    for(int/ni=1;i<=n;++i)
        cin>>a[i][1]>>a[i][2];
    cin>>m;
    memset(f,0x7fffffff,sizeof(f));
    for(int/ni=1;i<=m;++i)
    {
        cin>>x>>y;
        f[x][y]=f[y][x]=sqrt((pow(double(a[x][1]-a[y][1]),2))+(pow(double(a[x][2]-a[y][2]),2)));
    }
    cin>>s>>e;
    for(int/nk=1;k<=n;++k)
        for(int/ni=1;i<=n;++i)
              for(int/nj=1;j<=n;++j)
                    if((i!=j)&&(i!=k)&&(k!=j)&&(f[i][k]+f[k][j]<f[i][j]))
    f[i][j]=f[i][k]+f[k][j];
    cout<<f[s][e];
    return/n0;
}

優化:

利用對稱性,只適用於無向圖code

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<iomanip>
#include<cctype>
using namespace std;
const int maxn=2501;
long long int map[maxn][maxn];
int main() {
    int n,m,s,t;
    cin>>n>>m>>s>>t;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            map[i][j]=0x7fffff;
    for(int i=1; i<=m; i++) {
        int x,y,z;
        cin>>x>>y>>z;
        map[x][y]=z;
        map[y][x]=z;
    }
    for(int k=1; k<=n; k++)
        for(int i=1; i<=n; i++)
            for(int j=1; j<=i; j++)//根據對稱性優化
                map[j][i]=map[i][j]=min(map[i][j],map[i][k]+map[k][j]);
    printf("%lld ",map[s][t]);
    return 0;
}

Dijkstra

  • 適用範圍:無負權迴路,邊權必須非負,單源最短路blog

  • 時間複雜度:優化前O(\(n^2\))隊列

數組dis[u]表示u到s點的最短距離。ip

咱們一直找點u = min{ dis[k] , k點未訪問 },這個點就是最短路上的點,而後根據其餘點v跟u點的關係去更新下dis[v],不斷重複找和更新便可。

dis[s]=0將源點加入最短路,而後循環n-1次每次找出一個最短路上的點,找的方法是直接找出剩下的點中dis[ ]最小的那個點u,u點就是最短路上的點,而後看看其餘點v到s點的距離會不會由於

這個u點的加入而改變,即若dis[v] > dis[u] + distance[u][v] 則更新dis[v]爲 dis[u] + distance[u][v]。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<stack>
using namespace std;
const int maxn=1e9;
const int hhh=105;
int inq[hhh],n,m,s,x,y,z,a[105][105];
int dis[hhh];
int main() {
    cin>>n>>m>>s;//別忘了 !!!!!!!!!!!!!!!!!!! 
    for(int i=1; i<=m; ++i) {//別忘了 !!!!!!!!!!!!!!!!!!! 
        for(int j=1; j<=m; ++j) {//別忘了 !!!!!!!!!!!!!!!!!!! 
            if(i==j) {//別忘了 !!!!!!!!!!!!!!!!!!! 
                a[i][j]=0;//別忘了 !!!!!!!!!!!!!!!!!!! 
            } else {//別忘了 !!!!!!!!!!!!!!!!!!! 
                a[i][j]=maxn;//別忘了 !!!!!!!!!!!!!!!!!!! 
            }//別忘了 !!!!!!!!!!!!!!!!!!! 
        }//別忘了 !!!!!!!!!!!!!!!!!!! 
    }//別忘了 !!!!!!!!!!!!!!!!!!! 
    for(int i=1; i<=m; ++i) {
        cin>>x>>y>>z;
        a[x][y]=z;
        a[y][x]=z;
    }
    for(int i=1; i<=n; ++i) {
        dis[i]=a[s][i];
    }
    dis[s]=0;
    inq[s]=1;
    for(int i=1; i<=n-1; ++i) {
        int k=0;
        int minn=1e9+100;
        for(int j=1; j<=n; ++j) {
            if((inq[j]==0)&&(dis[j]<minn)) {
                minn=dis[j];
                k=j;
            }
        }
        if(k==0) break;
        inq[k]=1;
        for(int j=1; j<=n; ++j) {
            if(dis[k]+a[k][j]<dis[j]) {
                dis[j]=dis[k]+a[k][j];
            }
        }
    }
    for(int i=1; i<=n; ++i) {
        cout<<dis[i]<<" ";
    }
    return 0;
}
/*
5 7 1
1 2 10
1 5 7 
1 3 49
2 3 17
2 4 7
2 5 5
3 4 34
*/

優化

複雜度\(O(n*log(n))\)

利用堆(優先隊列)找最近的點,避免了循環

/*
        dijjstra+堆優化
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
const int maxn=520;
const int INF=1e9;
vector<pair<int,int> >/*edge*/e[maxn];//定義一個二維的動態數組以便之後創建鄰接表
int dis[maxn],inq[maxn]/*in_queue*/;
int n,m,s,t;
void start() {
    for(int i=0; i<maxn; ++i) {
        e[i].clear() ;
    }//若是不是多組數據能夠沒有
    memset(inq,0,sizeof(inq));
    for(int i=0; i<maxn; ++i) {
        dis[i]=INF;
    } //初始化dis
}
int main() {
    std::ios::sync_with_stdio(false);//cin優化一下下(主要是我懶不想打scnaf)
    while(cin>>n>>m) {
        start();//初始化
        for(int i=0; i<m; ++i) {
            int x,y,z;//雙向道路的起點、終點 、權值
            cin>>x>>y>>z;
            e[x].push_back(make_pair(y,z));//把x連到y上權值是z
            e[y].push_back(make_pair(x,z));//雙向的緣由
        }
        int s,t;
        cin>>s>>t;
        //queue<int>q;
        priority_queue< pair<int,int> >q;
        //q.push(s);//將lyq家(起點入隊相似於bfs)
        q.push(make_pair(-dis[s],s));//pushu(-d[s])變成小根堆 ,其實也能夠直接定義(不過我懶)
        dis[s]=0;//從起點到起點必定距離爲0
        inq[s]=1;//標記一下s點入隊
        /*bfs階段*/
        while(!q.empty() ) {
            //int now=q.front() ;//now 是如今lyq到的點
            int now=q.top().second;//只須要第二個就ok
            q.pop();//吃隊
            inq[now]=0;//更新inq
            for(int i=0; i<e[now].size(); ++i) {//遍歷
                int v=e[now][i].first;

                /*鬆弛操做*/
                if(dis[v]>dis[now]+e[now][i].second) {
                    dis[v]=dis[now]+e[now][i].second;
                    if(inq[v]==1) continue;//若是在隊列裏就無論了
                    else {
                        inq[v]=1;//標記進隊
                        //q.push(v);
                        q.push(make_pair(-dis[v],v));
                    }
                }

            }
        }
        if(dis[t]==1e9) {
            cout<<"-1\n";//若是最短路仍是1e9那lyq就找不到了....
        } else {
            cout<<dis[t];
        }
    }
    return 0;
}

spfa

就和bfs差很少,本身看代碼吧

#include<bits/stdc++.h>
using namespace std;
const int maxn=99999999;
const int h=1005;
int dis[h],a[h][h],inq[h],pre[h];
int n,m,x,y,z,s;
queue<int>q;
int main() {
    cin>>n>>m>>s;//別忘了 !!!!!!!!!!!!!!!!!!! 
    for(int i=1; i<=m; ++i) {//別忘了 !!!!!!!!!!!!!!!!!!! 
        for(int j=1; j<=m; ++j) {//別忘了 !!!!!!!!!!!!!!!!!!! 
            if(i==j) {//別忘了 !!!!!!!!!!!!!!!!!!! 
                a[i][j]=0;//別忘了 !!!!!!!!!!!!!!!!!!! 
            } else {//別忘了 !!!!!!!!!!!!!!!!!!! 
                a[i][j]=maxn;//別忘了 !!!!!!!!!!!!!!!!!!! 
            }//別忘了 !!!!!!!!!!!!!!!!!!! 
        }//別忘了 !!!!!!!!!!!!!!!!!!! 
    }//別忘了 !!!!!!!!!!!!!!!!!!! 
    for(int i=1; i<=m; ++i) {
        dis[i]=maxn;
    }
    for(int i=1; i<=m; ++i) {
        cin>>x>>y>>z;
        a[x][y]=z;
        a[y][x]=z;
    }
    inq[s]=1;
    dis[s]=0;
    q.push(s);
    while(!q.empty()) {
        int k=q.front() ;
        q.pop();
        inq[k]=0;
        for(int i=1; i<=n; ++i) {
            if(a[k][i]!=maxn) {
                if(dis[k]+a[k][i]<dis[i]) {
                    dis[i]=dis[k]+a[k][i];
                    if(!inq[i]) {
                        q.push(i);
                        inq[i]=1;
                    }
                }
            }
        }
    }
    for(int i=1; i<=n; ++i) {
        cout<<dis[i]<<" ";
    }
    return 0;
}

優化

雙端隊列:

/*
原隊列改成雙端隊列,對一個要加入隊列的點u,若是dis[u] 小
於隊首元素v的dis[v],那麼就就加入到隊首元素,不然加入到隊尾。
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=99999999;
const int h=2505;
long long int dis[h],a[h][h],inq[h],pre[h];
int n,m,x,y,z,s;
deque<int>q;
int main() {
    cin>>n>>m;//別忘了 !!!!!!!!!!!!!!!!!!!
    for(int i=1; i<=m; ++i) {//別忘了 !!!!!!!!!!!!!!!!!!!
        for(int j=1; j<=m; ++j) {//別忘了 !!!!!!!!!!!!!!!!!!!
            if(i==j) {//別忘了 !!!!!!!!!!!!!!!!!!!
                a[i][j]=0;//別忘了 !!!!!!!!!!!!!!!!!!!
            } else {//別忘了 !!!!!!!!!!!!!!!!!!!
                a[i][j]=maxn;//別忘了 !!!!!!!!!!!!!!!!!!!
            }//別忘了 !!!!!!!!!!!!!!!!!!!
        }//別忘了 !!!!!!!!!!!!!!!!!!!
    }//別忘了 !!!!!!!!!!!!!!!!!!!
    for(int i=1; i<=m; ++i) {
        dis[i]=maxn;
    }
    for(int i=1; i<=m; ++i) {
        cin>>x>>y>>z;
        a[x][y]=z;
        a[y][x]=z;
    }
    s=1;
    inq[s]=1;
    dis[s]=0;
    q.push_front(s);
    while(!q.empty()) {
        int k=q.front() ;
        q.pop_front() ;
        inq[k]=0;
        for(int i=1; i<=n; ++i) {
            if(a[k][i]!=maxn) {
                if(dis[k]+a[k][i]<dis[i]) {
                    dis[i]=dis[k]+a[k][i];
                    if(!inq[i]) {
                        if(dis[i]>dis[q.front()])
                            q.push_front(i);
                        else
                            q.push_back(i);
                        inq[i]=1;
                    }
                }
            }
        }
    }
    cout<<dis[n];
    return 0;
}

dfs版本

int flag=0;
int dis[N]= {};
int vis[N]= {};
void Clr() {
    memset(dis,0,sizeof(dis));
    memset(vis,0,sizeof(vis));
    memset(first,0,sizeof(first));
    cnt=1;
    flag=0;
}
void SPFA(int u) {
    vis[u]=1;
    for(int i=first[u]; i; i=e[i].nxt) {
        int v=e[i].v;
        if(dis[u]+e[i].w<dis[v]) {
            if(vis[v]||flag) {
                flag=1;
                break;
            }
            dis[v]x=dis[u]+e[i].w;
            SPFA(v);
        }
    }
    vis[u]=0;
}
//
for(int i=1; i<=n; i++) {
    SPFA(i);
    if(flag)break;
}
/*第二種*/
/*
bool spfa(int u){
  vis[u] = true;
  int i;
  for(i = head[u]; i; i = e[i].next){
    int v = e[i].to, w = e[i].w;
    if(dis[v] > dis[u] + w){
      dis[v] = dis[u] + w;
      if(vis[v]) return false;
      if(!spfa(v)) return false;
    }
  }
  vis[u] = false;
  return true;
}
*/

LLL版本:

/*
對每一個要出對的元素u,比較dis[u]和隊列中dis的平均值,若是dis[u]更大,
那麼將它彈出放到隊尾,取隊首元素在進行重複判斷,直至存在dis[x]小於平均值
*/
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int maxn = 205;
int n, dis[maxn], sum = 0, cnt = 0;
int G[maxn][maxn] = {};
bool inq[maxn] = {};

int main() {
    memset(dis, 0x3f, sizeof(dis));
    cin >> n;
    for (int i = 1; i < n; ++i) {
        for (int j = i + 1; j <= n; ++j) {
            cin >> G[i][j];
        }
    }
    queue<int> Q;
    Q.push(1);
    inq[1] = true;
    dis[1] = 0;
    cnt = 1;
    while (!Q.empty()) {
        int u = Q.front();
        while (dis[u]*cnt > sum) {
            Q.pop();
            Q.push(u);
            u = Q.front();
        }
        Q.pop();
        cnt--;
        sum -= dis[u];
        inq[u] = false;
        for (int i = u + 1; i <= n; ++i) {
            if (dis[i] > dis[u] + G[u][i]) {
                dis[i] = dis[u] + G[u][i];
                if (!inq[i]) {
                    Q.push(i);
                    sum += dis[i];
                    cnt++;
                }
            }
        }
    }
    cout << dis[n];
}

幾道例題

例題1

將全部的邊方向取反,求從1號點到全部點的單源最短路便可。

例題2

新建一個超級源點,向全部起點連一條邊權爲0的邊,從它開始跑單源最短路。

最短路計數

在進行dijkstra算法時,額外維護一個sum數組表示到達某個點的最短路條數。在進行鬆弛操做時維護sum數組。

最短路輸出方案

在dijkstra算法中額外記錄一個數組from,表示從1號點到達它的最短路是從哪一個點走過來的。
最後從n號點不斷沿着from走回去便可。

相關文章
相關標籤/搜索