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;

}