最短路徑的三種算法


ps:給17級講最短路徑時候自己寫的課件

目錄

最短路徑... 1

概述: 1

Floyd算法(弗洛伊德算法)復雜度O(n^3) 3

Dijkstra算法(迪傑斯特拉算法)復雜度O(nlog2n) 5

SPFA算法(Shortest Path Fast Algorithm的縮寫) 12

附錄:... 12

Floyd代碼... 12

Dijkstra O(n^2),鏈式前向星... 13

Dijkstra + priority_queue + 鏈式前向星... 15

 

 

 

最短路徑

概述:

 

 

 

假設有一個圖,點表示城市,邊表示路徑長度。

 

         如圖,從點(2)到點(4)有若干總走法,比如

 

 

(2)->(3)->(4), 這么走路徑長度是4。

 

 

也可以(2)->(3)->(1)->(4) 這樣路徑長度是15。當圖有上萬個點的時候就無法用人工直接求任意兩點的路徑,哪一條是最短的。最短路徑就是解決這類問題。

例題:http://www.fjutacm.com/Problem.jsp?pid=1499

Floyd算法(弗洛伊德算法)復雜度O(n^3)

const int inf = 0x3f3f3f3f;

由於某些情況下避免正無窮加正無窮加成負無窮,所以正無窮有時不用0x7FFFFFFF, 用0x3f3f3f3f,或者1<<29, 1e9.

 

這個算法只能用鄰接矩陣存圖, 我們先初始化一下矩陣

void init() {   ///初始化矩陣

    for(int i = 1; i <= n; i ++ ) {

        for(int j = 1; j <= n; j ++ ) {

            if(i == j) {

                mp[i][j] = 0; ///自己到自己的距離0

            } else {

                mp[i][j] = inf;///否則都是inf

            }

        }

    }

}

 

然后讀入一條u->v權值為w的邊。對於矩陣mp[u][v]記錄的是u->v的邊的長度。對於無向圖,mp[u][v]和mp[v][u]是相等的。而因為我們求的是最短路, u->v的路有多條我們只要存最短的。

 

scanf("%d %d %d", &u, &v, &w);

mp[u][v] = mp[v][u] = min(mp[u][v], w);

 

存好矩陣后我們跑floyd算法,核心代碼就這幾行

for(int k = 0; k < n; k ++ ) {

    for(int i = 0; i < n; i ++ ) {

        for(int j = 0; j < n; j ++ ) {

            mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]);

        }

    }

}

 

然后矩陣mp[s][t]的值就是s->t的最短路徑。對於這題來說就是

printf("%d\n", mp[s][t] == inf ? -1 : mp[s][t]);

 

下面我們來解釋一下floyd的三層循環。

 

 

我們發現(i)->(j)的最短路徑可能是之間i->j,也可能是經過中間點i->k->j。

如果我們用dp[i][j]表示u->v的最短路徑。那么

dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);

然后看循環。

當k = 0時

枚舉I,j.我們嘗試讓編號0的中點。優化我們的最短距離。

矩陣成為了經過0點優化過的最短距離。

K = 1時,我們又枚舉I,j。讓矩陣成為經過中間點0,1的最短路矩陣。

以此類推

。。。。

來自知乎的解釋

 

 

 

優點:好寫,核心代碼總共就4行。經常擴展出各種圖上動態規划

缺點:復雜度高。點的數量1000的圖就容易TLE

 

Dijkstra算法(迪傑斯特拉算法)復雜度O(nlog2n)

 

 

 

我們先有張圖,我們要求1->5的最短路徑是什么。

換個問題,我們要求一個dist數組,dist[i]表示起點到i的最短路徑。

如果起點是1,初始化dist[1] = 0,其余都是正無窮

 

 

我們的dist[]數組已經完成了dist[1](藍色),和1相連的有兩條邊。

 

 

 

我們更新dist數組后結果如上。

Dist[2] = min(dist[2], dist[1]+(1->2的權值))

Dist[3] = min(dist[3], dist[1]+(1->3的權值))

我們從數組中找一個最小的數值。上圖中的點2,那么dist[2]已經可以確定了。因為邊最短,走其他的路繞回來肯定路更長。

 

 

通過點2的邊,我們可以更新dist數組,如果邊能使得dist數值變小

(2)能到達3和4.

Dist[4]=min(dist[4], dist[2] + (2->4的權值))

Dist[3]=min(dist[3], dist[2] + (2->3的權值))

 

 

 

我們發現原來的dist[3]=4被dist[2]+(2->3的邊)=3覆蓋

Dist[4]=inf被dist[2]+(2->4的邊)=7覆蓋。

然后我們在選一條最短邊。

 

 

把點3加入集合。擴展的兩條邊更新dist數組。

 

 

繼續找最小

 

 

Dist數組計算完成。

在數據集合中選最小的數我們用優先隊列logn,每次都能確定一個點,總過n個點。總復雜度O(nlogn)

 

寫法和大部分和周三的prim算法幾乎一模一樣的,也分O(n^2)的版本和O(nlogn)優化的版本。一般肯定寫快的O(nlogn)的;

 

 

//比較挫的O(N^2)版本,可能比較好理解

int dist[maxn], vis[maxn];

int dijkstra(int s, int t) { //從s->t的最短路徑,無法到達反回-1

    for(int i = 0; i < n; i ++ ) {

        dist[i] = inf, vis[i] = 0;

    }

    dist[s] = 0;

    for(int k = 1; k <= n; k ++ ) { ///dist數組n個,每次確定一個值

        vis[s] = 1;

        for(int i = first[s]; ~i; i = edge[i].next) { ///更新dist數組

            int to = edge[i].to, w = edge[i].w;

            if(!vis[to] && dist[to] > dist[s] + w) {

                dist[to] = dist[s] + w;

            }

        }

        int mincost = inf;

        for(int i = 0; i < n; i ++ ) { ///找最小

            if(!vis[i] && dist[i] < mincost) {

                mincost = dist[i];

                s = i;

            }

        }

    }

    if(dist[t] == inf) {

        return -1;

    } else {

        return dist[t];

    }

}

 

 

 

//優先隊列實現

struct Node {

    int to, cost;

    Node() {}

    Node(int tt, int cc):to(tt), cost(cc) {}

    friend bool operator < (const Node &a, const Node &b) {

        return a.cost > b.cost;

    }

};

 

int dist[maxn], vis[maxn];

 

int dijkstra(int s, int t) {

    for(int i = 0; i < n; i ++ ) {

        dist[i] = inf, vis[i] = 0;

    }

    priority_queue<Node>que;

    que.push(Node(s, 0));

    /**

    丟進去一個to=s,cost=0的結構體

    等價於下面三行,看不懂往下翻附錄構造函數

    struct Node tmp;

    tmp.to = s, tmp.cost = 0;

    que.push(tmp);

    */

    while(!que.empty()) {

        Node now = que.top();

        que.pop();

        if(!vis[now.to]) {

            vis[now.to] = 1;

            dist[now.to] = now.cost;

            for(int i = first[now.to]; ~i; i = edge[i].next) {

                int to = edge[i].to, w = edge[i].w;

                if(!vis[to]) {

                    que.push(Node(to, now.cost + w));

                }

            }

        }

    }

    if(dist[t] == inf) {

        return -1;

    } else {

        return dist[t];

    }

}

 

 

 

SPFA算法(Shortest Path Fast Algorithm的縮寫)

Ps: SPFA也叫bellman ford的隊列優化。但是bellman ford的復雜度比較高。SPFA的平均復雜度是O(n*log2n),復雜度不穩定,在稠密圖(邊多的圖)跑的比dijkstra慢,稀疏圖(邊少的圖)跑的比Dijkstra快。在完全圖達到最壞的平方級復雜度。我們學它是因為有些圖的邊的長度是負數。這時候dijkstra就GG了。

 

完全圖: n個點中如果每個點都與其他n-1有邊,無向圖中,就有n*(n-1)/2條邊。

 

我們要求一個dist[]數組表示起點,到其他點的最短路徑。

 

 

 

 

我們有一條u->v的邊,s是我們的起點。那么如果. S->V可以通過s->u,u->v來縮短距離那么就更新。我們稱之為【松弛】

 

我們可以每次對圖中每條邊u->v都拿出起點,松弛。直到每一次操作都不會改變dist數組的時候。Dist數組就是答案。(可以證明這樣最多我們只要進行n-1次操作,每次操作拿出全部的m條邊松弛,復雜度O(n*m),這就是bellman ford)。

 

但是,起點附近的松弛必定會影響其他點,起點沒松弛完,終點附近的變的松弛可以說是無效的。簡單來說,近的點的dist還沒確定,就用這個未確定的dist取更新遠的點,浪費時間。

所以我們需要一個隊列優化。

 

在隊列中的點,需要松弛。我們先把起點丟進隊列。具體看代碼。

Dist數組記錄距離,inq數組標記是否在隊列

 

 

 

int SPFA(int s, int t) {

    int dist[maxn], inq[maxn];

    for(int i = 0; i < n; i ++ ) {

        dist[i] = inf, inq[i] = 0;

    }

    queue<int>que;

    que.push(s), inq[s] = 1, dist[s] = 0;

    while(!que.empty()) {

        int now = que.front();

        que.pop();

        inq[now] = 0;

        for(int i = first[now]; ~i; i = edge[i].next) { //每次拿出一個點開始松弛。

            int to = edge[i].to, w = edge[i].w;

            if(dist[to] > dist[now] + w) { //這個if看下面的圖

                dist[to] = dist[now] + w;

                if(!inq[to]) { //松弛過的點dist變換了,可能影響其他的點。需要繼續松弛

                    inq[to] = 1;

                    que.push(to);

                }

            }

        }

    }

    return dist[t] == inf ? -1 : dist[t];

}

 

 

 

 

 

附錄:

構造函數

平常我們寫結構體這么寫

struct Point {

    int x, y;

};

Point a; //c++才能這么寫,c語言中要struct Point a;或者typedef后才能直接point a;

這是結構體a的值不確定。

 

 

struct Point {

int x, y;

Point() {}

};

其實它默認有一個沒有返回值,和結構體同名的函數,什么都不干。如果我們給他加點東西

struct Point {

int x, y;

Point() { x = 0; y = 0};

};

這樣Point a;那么a.x和a.y都是0.

如果我們這么寫

struct Point {

int x, y;

Point() { x = 0; y = 0};

Point(int xx, int yy) {

         X = xx;

         Y = yy;

}

};

那么Point a(2, 3), 點a的x就是2,y就是3。

所以 queue<Point >que; que.push(Point(2, 3));能直接往隊列丟一個(2,3)的結構體

Floyd代碼

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <iostream>

 

using namespace std;

const int maxn = 1005;

const int inf = 0x3f3f3f3f;

 

int n, m, mp[maxn][maxn];

 

void init() {

    for(int i = 0; i < n; i ++ ) {

        for(int j = 0; j < n; j ++ ) {

            if(i == j) {

                mp[i][j] = 0;

            } else {

                mp[i][j] = inf;

            }

        }

    }

}

 

int main() {

    int u, v, w;

    while(~scanf("%d %d", &n, &m)) {

        init();

        for(int i = 1; i <= m; i ++ ) {

            scanf("%d %d %d", &u, &v, &w);

            mp[u][v] = mp[v][u] = min(mp[u][v], w);

        }

 

        for(int k = 0; k < n; k ++ ) {

            for(int i = 0; i < n; i ++ ) {

                for(int j = 0; j < n; j ++ ) {

                    mp[i][j] = min(mp[i][j], mp[i][k] + mp[k][j]);

                }

            }

        }

 

        int s, t;

        scanf("%d %d", &s, &t);

        printf("%d\n", mp[s][t] == inf ? -1 : mp[s][t]);

 

    }

 

    return 0;

}

 

Dijkstra O(n^2),鏈式前向星

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <iostream>

#include <queue>

 

using namespace std;

const int maxn = 1005;

const int inf = 0x3f3f3f3f;

 

int n, m, first[maxn], sign;

 

struct Edge {

    int to, w, next;

} edge[maxn * 2];

 

void init() {

    for(int i = 0; i < n; i ++ ) {

        first[i] = -1;

    }

    sign = 0;

}

 

void add_edge(int u, int v, int w) {

    edge[sign].to = v;

    edge[sign].w = w;

    edge[sign].next = first[u];

    first[u] = sign ++;

}

 

int dist[maxn], vis[maxn];

 

int dijkstra(int s, int t) {

    for(int i = 0; i < n; i ++ ) {

        dist[i] = inf, vis[i] = 0;

    }

    dist[s] = 0;

    for(int k = 1; k <= n; k ++ ) { ///dist數組n個,每次確定一個值

        vis[s] = 1;

        for(int i = first[s]; ~i; i = edge[i].next) { ///更新dist數組

            int to = edge[i].to, w = edge[i].w;

            if(!vis[to] && dist[to] > dist[s] + w) {

                dist[to] = dist[s] + w;

            }

        }

        int mincost = inf;

        for(int i = 0; i < n; i ++ ) { ///找最小

            if(!vis[i] && dist[i] < mincost) {

                mincost = dist[i];

                s = i;

            }

        }

    }

    if(dist[t] == inf) {

        return -1;

    } else {

        return dist[t];

    }

}

 

int main() {

    int u, v, w;

    while(~scanf("%d %d", &n, &m)) {

        init();

        for(int i = 1; i <= m; i ++ ) {

            scanf("%d %d %d", &u, &v, &w);

            add_edge(u, v, w);

            add_edge(v, u, w);

        }

        int s, t;

        scanf("%d %d", &s, &t);

        printf("%d\n", dijkstra(s, t));

    }

 

    return 0;

}

 

Dijkstra + priority_queue + 鏈式前向星

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <iostream>

#include <queue>

 

using namespace std;

const int maxn = 1005;

const int inf = 0x3f3f3f3f;

 

int n, m, first[maxn], sign;

 

struct Edge {

    int to, w, next;

} edge[maxn * 2];

 

void init() {

    for(int i = 0; i < n; i ++ ) {

        first[i] = -1;

    }

    sign = 0;

}

 

void add_edge(int u, int v, int w) {

    edge[sign].to = v;

    edge[sign].w = w;

    edge[sign].next = first[u];

    first[u] = sign ++;

}

 

struct Node {

    int to, cost;

    Node() {}

    Node(int tt, int cc):to(tt), cost(cc) {}

    friend bool operator < (const Node &a, const Node &b) {

        return a.cost > b.cost;

    }

};

 

int dist[maxn], vis[maxn];

 

int dijkstra(int s, int t) {

    for(int i = 0; i < n; i ++ ) {

        dist[i] = inf, vis[i] = 0;

    }

    priority_queue<Node>que;

    que.push(Node(s, 0));

    while(!que.empty()) {

        Node now = que.top();

        que.pop();

        if(!vis[now.to]) {

            vis[now.to] = 1;

            dist[now.to] = now.cost;

            for(int i = first[now.to]; ~i; i = edge[i].next) {

                int to = edge[i].to, w = edge[i].w;

                if(!vis[to]) {

                    que.push(Node(to, now.cost + w));

                }

            }

        }

    }

    if(dist[t] == inf) {

        return -1;

    } else {

        return dist[t];

    }

}

 

int main() {

    int u, v, w;

    while(~scanf("%d %d", &n, &m)) {

        init();

        for(int i = 1; i <= m; i ++ ) {

            scanf("%d %d %d", &u, &v, &w);

            add_edge(u, v, w);

            add_edge(v, u, w);

        }

        int s, t;

        scanf("%d %d", &s, &t);

        printf("%d\n", dijkstra(s, t));

    }

 

    return 0;

}

 

SPFA鏈式前向星

#include <cstdio>

#include <cstring>

#include <iostream>

#include <algorithm>

#include <queue>

 

using namespace std;

const int maxn = 205;

const int maxm = 1005;

const int inf = 0x3f3f3f3f;

 

int n, m, first[maxn], sign;

 

struct Edge {

    int to, w, next;

} edge[maxn * maxn];

 

void init() {

    for(int i = 0; i < n; i ++ ) {

        first[i] = -1;

    }

    sign = 0;

}

 

void add_edge(int u, int v, int w) {

    edge[sign].to = v;

    edge[sign].w = w;

    edge[sign].next = first[u];

    first[u] = sign ++;

}

 

int SPFA(int s, int t) {

    int dist[maxn], inq[maxn];

    for(int i = 0; i < n; i ++ ) {

        dist[i] = inf, inq[i] = 0;

    }

    queue<int>que;

    que.push(s), inq[s] = 1, dist[s] = 0;

    while(!que.empty()) {

        int now = que.front();

        que.pop();

        inq[now] = 0;

        for(int i = first[now]; ~i; i = edge[i].next) {

            int to = edge[i].to, w = edge[i].w;

            if(dist[to] > dist[now] + w) {

                dist[to] = dist[now] + w;

                if(!inq[to]) {

                    inq[to] = 1;

                    que.push(to);

                }

            }

        }

    }

    return dist[t] == inf ? -1 : dist[t];

}

 

 

int main()

{

    while(~scanf("%d %d", &n, &m)) {

        init();

        for(int i = 1; i <= m; i ++ ) {

            int u, v, w;

            scanf("%d %d %d", &u, &v, &w);

            add_edge(u, v, w);

            add_edge(v, u, w);

        }

        int s, t;

        scanf("%d %d", &s, &t);

        printf("%d\n", SPFA(s, t));

    }

 

    return 0;

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM