【圖論算法】 最短路,次短路,k短路總結

在圖論裏,最短路,次短路,k短路的問題很常見。
這裏總結一下。

存圖技巧

數據小,稠密圖的一般用鄰接矩陣
稀疏圖,數據大一般用鄰接表(vector,鏈式前向星都可)

鄰接矩陣

const int maxn = 1e5+5;
int Graph[maxn][maxn];	// 正權圖可以初始化成-1來判斷是否連通,負權圖可以再考慮開個數組或者用一個很大的值。

鏈式前向星

const int maxn = 1e5+5;
struct Edge{
		int u,v,nxt;
}edge[maxn];
int head[maxn],tot;
inline void addedge(int u,int v,int w){	// u->v 權值是w
		edge[++tot] = {u,v,w};
		head[u] = tot;
}

最短路

一般最短路算法有 BFS(暴力,一般只適用於點數小的情況),dijiastra(解決正權圖),spfa(解決負權圖,但容易死,被卡),floyed(解決點與點之間的最短距離問題,dp)

這裏直接給出單源最短路的算法(因爲都挺好理解的) (重點在於鬆弛操作!)

dijiastra

// 堆優化版本
const int maxn = 1e5+5;
struct Edge{
    int u,v,nxt,w;
}edge[maxn];
int head[maxn],tot;
inline void init(){
    memset(head,-1,sizeof(head));
}
inline void addedge(int u,int v,int w){
    edge[++tot] = {u,v,head[u],w};
    head[u] = tot;
}
int dis[maxn];  bool vis[maxn];
inline void dijiastra(int s){
    struct Node{
        int u,dis;
        bool operator <(const Node &h)const{
            return dis > h.dis;
        }
    };
    memset(dis,0x3f,sizeof(dis));   //  視具體情況而定初始化的值
    dis[s] = 0;
    priority_queue<Node> pq;    pq.push(Node{s,0});
    while(!pq.empty()){
        Node u = pq.top();  pq.pop();
        if(vis[u.u])    continue;
        vis[u.u] = true;
        for(int i = head[u.u]; ~i; i = edge[i].nxt){
            Edge &e = edge[i];
            if(dis[e.v] > u.dis + e.w){
                dis[e.v] = u.dis + e.w;
                pq.push(Node{e.v,dis[e.v]});
            }
        }
    }
}

spfa

struct Edge{
    int v,d;
    Edge(int vv,int dd):v(vv),d(dd){}
};
struct Node{
    int u,cost;
    Node(int uu,int cc):u(uu),cost(cc){}
    bool operator < (const Node & h)const{
        return cost > h.cost;
    }
};
const int MAX = 1e5+5;
vector < Edge > edges;
vector < int > Graph[MAX];
bool vis[MAX];
int dp[MAX];
void AddEdge(int u,int v,int dis){
    edges.push_back(Edge{v,dis});
    Graph[u].push_back(edges.size()-1);
}
void SPFA(int s,int n){
    memset(dp,0x3f,sizeof(dp));
    dp[s-1] = 0;
    priority_queue<Node>q ;
    q.push(Node{s-1,0});  vis[s-1] = 1;
    while(!q.empty()){
        Node x = q.top(); q.pop();
        int u = x.u; vis[u] = 0; // cancel the tag;
        for(int i = 0; i < Graph[u].size(); ++i ){
            Edge & e = edges[Graph[u][i]];
            if( dp[e.v] > dp[u]+ e.d ){
                dp[e.v] = dp[u] + e.d;
                if(!vis[e.v]) q.push(Node{e.v,dp[e.v]});
                  vis[e.v] = 1;
            }
        }
    }
}

次短路(利用最短路來求)

目前見到的方法一般有兩種,遍歷or刪邊

1.刪邊。
首先使用最短路算法求出sts - t的最短路,並且記錄下最短路的路徑(在鬆弛的時候記錄前驅),然後對於這條路徑,我們依次去刪去一條邊,然後跑最短路,去更新ans

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
const double inf = 1e9+5;
struct Edge{
    int u,v,nxt;    double w;
}edge[maxn];
int head[maxn],tot;
pair<int,int> path[maxn];
inline void addedge(int u,int v,double w){
    edge[++tot] = {u,v,head[u],w};
    head[u] = tot;
}
inline void init(){
    memset(head,-1,sizeof(head));
    tot = 0;
}

double dis[maxn];   bool vis[maxn];
bool isok[maxn];
inline void dijiastra(int s){
    struct Node{
        int u;  double dis;
        bool operator <(const Node &h)const{
            return dis > h.dis;
        }
    };
    for(int i = 0; i < maxn; ++i)  dis[i] = inf,vis[i] = false;
    priority_queue<Node> pq;    pq.push(Node{s,0});
    while(!pq.empty()){
        Node u = pq.top();  pq.pop();
        if(vis[u.u])
            continue;
        vis[u.u] = true;
        for(int i = head[u.u]; ~i; i = edge[i].nxt){
            if(isok[i]) continue;
            Edge &e = edge[i];
            if(dis[e.v] > u.dis + e.w){ //  鬆弛過程中記錄
                dis[e.v] = u.dis + e.w;
                path[e.v] = make_pair(u.u,i);
                pq.push(Node{e.v,dis[e.v]});
            }
        }
    }
}
struct Point{
    int x,y;
}p[maxn];
inline double get_dis(const Point &p,const Point&q){
    return sqrt( (p.x-q.x)*(p.x-q.x) + (p.y-q.y)*(p.y-q.y));
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    init();
    int n,m;    cin >> n >> m;
    for(int i = 1; i <= n; ++i) cin >> p[i].x >> p[i].y;
    for(int i = 0,u,v; i < m; ++i){
        cin >> u >> v;  double d = get_dis(p[u],p[v]);
        addedge(u,v,d); addedge(v,u,d);
    }
    dijiastra(1);
    pair<int,int> tt = path[n];
    vector<int> ee; ee.push_back(tt.second);
    while(tt.first!=1){
        tt = path[tt.first];
        ee.push_back(tt.second);
    }

    double ans = inf;
    for(int i = 0; i < ee.size(); ++i){
        isok[ee[i]] = true;
        dijiastra(1);
        isok[ee[i]] = false;
        ans = min(ans,dis[n]);
    }
    if(ans==inf){
        cout << -1 << endl;
    }
    else cout <<fixed<<setprecision(2)<< ans << endl;
    return 0;
}

2.遍歷。
跑兩次最短路,分別是以起點,以終點。 然後遍歷所有邊,去更新長度
e(u,v)G(v,e)min(dis[1][u]+w(u,v)+dis[v][n])\sum_{e(u,v)\in G(v,e)}min(dis[1][u]+w(u,v)+dis[v][n])

k短路 ( A*或者左偏樹)(當然也可以用於解決次短路問題)

左偏樹,A*待更新

左偏樹學習:大佬博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章