【算法简述】图论专题:最短路

纵然是瞬间 提交于 2019-11-30 10:52:58

图论问题概述总结

对于**图论**,我们尊熟悉的算法是比较多的,这次,我就找了集中常用的算法。
## 几种算法

 1. **最短路**算法(Dijkstra,SPFE,FLOYD)

- Dijkstra单源最短算法

首先,此算法适用于计算一个点到另一个点的最短路径,且算法绝对不能出现负环。这个算法的速度慢,只用于接觉小规模的问题,如图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190609113100303.png)这个图就是求算法的基本思路。算法过程:

 - 从节点上找到最近点那个节点,将他标记,加入集合U。
 - 将定点U连出边的邻点相连接,不在集合U中寻找。
 - 重复前面的操作,用来指导V=U是,查找结束,最后结束流程。

本算法的算法流程图:
https://wenku.baidu.com/view/8a5c11303968011ca300916a.html
参考代码:

```

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;
const int inf = 0x3f3f3f3f;
struct edge {
int v, w, next;
edge() {}
edge(int _v, int _w, int _next) {
    v = _v;
    w = _w;
    next = _next;
}
} e[M << 1];
int head[N], len;
void init() {
    memset(head, -1, sizeof head);
    len = 0;
}
void add(int u, int v, int w) {
    e[len] = edge(v, w, head[u]);
    head[u] = len++;
}
void add2(int u, int v, int w) {
    add(u, v, w);
    add(v, u, w);
}
int n, m;
int dis[N];
bool vis[N];
void dijkstra(int u) {
memset(vis, false, sizeof vis);
memset(dis, inf, sizeof dis);
dis[u] = 0;
for (int i = 0; i < n; ++i) {
    int mi = inf;
    for (int j = 1; j <= n; ++j) {
        if (!vis[j] && dis[j] < mi) {
            mi = dis[u = j];
        }
    }    
if (mi == inf) {
    return;
}
vis[u] = true;
            for (int j = head[u]; ~j; j = e[j].next) {
    int v = e[j].v;
    int w = e[j].w;
                if (!vis[v] && dis[v] > dis[u] + w) {
    dis[v] = dis[u] + w;
                }
            }
        }
    }
int main() {
init();
int u, v, w;
cin >> n >> m;
while (m--) {
    cin >> u >> v >> w;
    add2(u, v, w);
}
dijkstra(1);
cout << dis[n] << endl;
return 0;
}

 


```
这只是一个基本的流程代码,你可在刷掉模板的基础上,在进行修改。
**它的基本思想是以起始点为中心往外层扩展(广度优先搜索+贪心),直到扩展到终点为止。**

这就是我们的算法,因此,可以解决很多问题。

```
3 3
1 2 5
2 3 5
3 1 2
```
输入数据,看看会怎么样! 

-堆优化Dijkstra

正对于稀疏图的算法,我们用与优化。

```

#include <iostream>
#include <cstring>
#include <set>
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;
const int inf = 0x3f3f3f3f;
typedef pair<int, int> pall;
#define X first
#define Y second
struct edge {
int v, w, next;
edge() {}
edge(int _v, int _w, int _next) {
v = _v;
w = _w;
next = _next;
}
} e[M << 1];
int head[N], len;
void init() {
memset(head, -1, sizeof head);
len = 0;
}
void add(int u, int v, int w) {
e[len] = edge(v, w, head[u]);
head[u] = len++;
}
void add2(int u, int v, int w) {
add(u, v, w);
add(v, u, w);
}
int n, m;
int dis[N];
bool vis[N];
void dijkstra(int u) {
memset(vis, false, sizeof vis);
memset(dis, inf, sizeof dis);
dis[u] = 0;
}
int main() {
init();
int u, v, w;
cin >> n >> m;
while (m--) {
cin >> u >> v >> w;
add2(u, v, w);
}
dijkstra(1);
cout << dis[n] << endl;
return 0;
}

 


```
找最小值,这里是和普通 Dijkstra 的核心不同之处,我们只需要获取堆顶元素即可(堆自动实现排序,
堆顶元素就是我们需要的最小值)。然后我们把这个元素加入集合(标记就是加入集合的意思)。
 

- SPFA单源最短路算法

在 SPFA 算法中,使用 di表示从源点到顶点 i的最短路,额外用一个队列来保存即将进行拓展的顶点
列表,并用ingi 来标识顶点 i是不是在队列中。
如图:
![](https://img-blog.csdnimg.cn/20190609150752915.png)
SPFA 的空间复杂度为O(V),有点像这个稀疏图的步揍。他用队列来运行,因此,SPEA只是是在对列上升级以下,并不是那种十分多的。**在一定程度上,可以认为 SPFA 是由 BFS 的思想转化而来。从不含边权或者说边权为 个单位长度的图上的 BFS,推广到带权图上,就得到了 SPFA。**

    算法步揍:
 - 用一个队列来保存多个扩展的队列。
 - 用一个队列中取的一个元素,并且对其他点进行松弛。
 - 当所有不在队列的点都入了队列,则程序结束,算法进行完毕。

```

bool inq[MAX_N];
int d[MAX_N]; // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
void spfa(int s) {
    memset(inq, 0, sizeof(inq));
    memset(d, 0x3f, sizeof(d));
d[s] = 0;
inq[s] = true;
queue<int> q;
q.push(s);
while (!q.empty()) {
    int u = q.front();
    q.pop();
    inq[u] = false;
    for (int i = p[u]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if (d[u] + e[i].w < d[v]) {
            d[v] = d[u] + e[i].w;
            if (!inq[v]) {
                q.push(v);
                inq[v] = true;
                }
            }
        }
    }
}
```
算法的图:https://i.loli.net/2019/06/09/5cfcb3db1267062487.jpg
上面的代码就是SPEA的全部结构,初始化与第一个算法一样的结构一样的。
**判断负环**的SPEA算法:

```
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;
const int inf = 0x3f3f3f3f;
struct edge {
    int v, w, next;
    edge() {}
    edge(int _v, int _w, int _next) {
        v = _v;
        w = _w;
        next = _next;
}
} e[M << 1];
int head[N], len;
void init() {
    memset(head, -1, sizeof head);
    len = 0;
}
void add(int u, int v, int w) {
    e[len] = edge(v, w, head[u]);
    head[u] = len++;
}
void add2(int u, int v, int w) {
    add(u, v, w);
    add(v, u, w);
}
int n, m;
int main() {
init();
int u, v, w;
cin >> n >> m;
while (m--) {
    cin >> u >> v >> w;
    add2(u, v, w);
}
return 0;
}

 


```
接下来就可以运行程序了:
```
3 3
1 2 5
2 3 5
3 1 2
``` 

 

- Floyd 多源最短路算法


Floyd 算法是一种计算给定的带权图中任意两个顶点之间最短路径的算法。相比于重复执行多次单源最
短路算法,Floyd 具有高效、代码简短的优势,在解决图论最短路题目时比较常用。
**floyd 算法是解决负环(可以计算出任意两点之间的最短路)有向图或无向图的多源最短路问题。常
用邻接矩阵存储,算法的时间复杂度 O(N2),空间复杂度O(N2) 。**

算法方式:

 - 如果不经过第 个点,那么就是 dp[k-1][i][j]。
 - 如果经过第 个点,那么就是 dp[k-1][i][j]+dp[k-1][i][j]。

所以就变成了:

```

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

 


```
所以就变成了如下的代码:

```

int g[N][N]; // 邻接矩阵存图
int dp[N][N][N];
void floyd(int n) {
    for (int k = 0; k <= n; ++k) {
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (k == 0) {
                    dp[k][i][j] = g[i][j];
                } else {
                    dp[k][i][j] = min(dp[k - 1][i][j], dp[k - 1][i][k] + dp[k - 1][k][j]);
                }
            }
        }
    }
}
```
我们写出最终的 Floyd 的形式,这也是常用的写法,优化了一维的空间。并且写法更加简单。这里要注
意,枚举的中间点 一定要写在最外面。没有注意这一点,很容易把 3 个循环的顺序弄错了,那么结
果也就是错的。
刚才的分析得出:

```
int g[N][N];
void floyd(int n) {
    for (int k = 1; k <= n; ++k) {
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
            }
        }
    }
}

 


```
算法到这里就完成了,接下来给大家介绍**差分约束系统**。

 -  差分约束系统

我们在求解差分约束系统时,可以将其转化为图论中单源最短路(或最长路)问题。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190609154201381.png)
途中有负环是,就可以这样。

```

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e3 + 9;
const int M = 1e4 + 9;
const int inf = 0x3f3f3f3f;
struct edge {
    int v, w, next;
    edge() {}
    edge(int _v, int _w, int _next) {
        v = _v;
        w = _w;
        next = _next;
    }
} e[M << 1];
int head[N], len;
void init() {
    memset(head, -1, sizeof head);
    len = 0;
}
void add(int u, int v, int w) {
    e[len] = edge(v, w, head[u]);
    head[u] = len++;
}
void add2(int u, int v, int w) {
    add(u, v, w);
    add(v, u, w);
}
int n, m;
int dis[N], in[N];
bool vis[N];
bool spfa(int u) {
    memset(vis, false, sizeof vis);
    vis[u] = true;
    memset(dis, -1, sizeof dis);
    dis[u] = 0;
    memset(in, 0, sizeof in);
    in[u] = 1;
    queue<int> q;
    q.push(u);
    while (!q.empty()) {
        u = q.front();
        q.pop();
        vis[u] = false;
        for (int j = head[u]; ~j; j = e[j].next) {
            int v = e[j].v;
            int w = e[j].w;
            if (dis[v] < dis[u] + w) { // 求最长路,和求最短路相反
                dis[v] = dis[u] + w;
                if (!vis[v]) {
                    q.push(v);
                    vis[v] = true;
                    ++in[v];
                    if (in[v] > n + 1) {
                        return true;
                }
            }
        }
    }
}
return false;
}
int main() {
init();
int u, v, w, op;
cin >> n >> m;
while (m--) {
    cin >> op;
    cin >> u >> v >> w;
}
if (op == 1) {
    add(u, v, -w);
}else if (op == 2) {
    add(v, u, w);
}else {
    add(u, v, -w);
    add(v, u, w);
}
for (int i = 1; i <= n; ++i) {
    add(0, i, 0);
}
if (spfa(0)) {
    cout << "no" << endl;
}else {
    for (int i = 1; i <= n; ++i) {
        cout << "x" << i << " = " << dis[i] << endl;
    }
}
return 0;
}

 


```
这就是差分约束,现在运行你的程序:

```
4 3
1 1 2 3
2 3 2 2
3 3 4 1
```
~~点个赞呗~~

**本期图论结束,下期再见!**

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!