最短路问题
最短路问题中的非常著名的Dijkstra算法、Floyd-Warshall算法以及经典的练习题,大家可以去下面的链接看哈。
Dijkstra算法Floyd-Warshall算法
一、K 短路问题
A*算法
给定一个图,定义起点 𝑠 和终点 𝑡,以及数字 𝑘;求 𝑠 到 𝑡 的第 𝑘 短的路。允许环路。相同长度的不同路径,也被认为是完全不同的。
思路:
用A*算法求解。把从 𝑠 到 𝑡 的路径分为两部分:从 𝑠 到中间某个 𝑖 的路径、从 𝑖 到 𝑡 的路径。估价函数 f(i) = g(i) + h(i)
,𝑔(𝑖) 是从 𝑠 到 𝑖 的路径长度,ℎ(𝑖)是从 𝑖 到 𝑡 的路径长度。𝑔(𝑖) 用 BFS 搜索;ℎ(𝑖) 是从 𝑖 到 𝑡 的最短路长度,用 dijkstra 计算得到。
代码:
- 用邻接表存图;
dijkstra()
函数是标准的模板;astar()
函数实际上就是一个简单的“BFS+优先队列”,当终点 𝑡 第 𝑘 次从优先队列中弹出时,就是从 𝑠 到 𝑡 的第 𝑘 短路。
当 𝑘=1 时,第 1 短路就是最短路。
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;const int INF = 0x3f3f3f3f;
const int maxn = 1005, maxm = 100005;/// 记录边
struct edge { int to, w;// vector edge[i]:起点是i; 它有很多边,其中一个边的to是边的终点,w是边长edge(int a, int b) { to = a, w = b; } //赋值
};/// G:原图 G2:反图
vector <edge>G[maxm], G2[maxm]; /// 用于dijkstra。记录点,以及点到起点的路径
struct node { int id, dis; //id:点;dis:点id到起点的路径长度node(int a, int b) { id = a, dis = b; } //赋值bool operator < (const node& u) const { return dis > u.dis; }
};int dist[maxn]; //dist[i]: 从s到点i的最短路长度
bool done[maxn]; //done[i]=ture: 表示到i的最短路已经找到
/// 标准的dijkstra: 求s到其他所有点的最短路
void dijkstra(int s) { for (int i = 0;i < maxn;i++) { dist[i] = INF; done[i] = false; } //初始化dist[s] = 0; //起点s到自己的距离是0priority_queue<node> q;q.push(node(s, dist[s])); //从起点开始处理队列while (!q.empty()) {node u = q.top(); //pop出距起点s最近的点uq.pop();if (done[u.id]) continue; //丢弃已经找到最短路的点 done[u.id] = true; //标记:点u到s的最短路已经找到for (int i = 0; i < G2[u.id].size(); i++) { //检查点u的所有邻居edge y = G2[u.id][i];if (done[y.to]) continue; //丢弃已经找到最短路的邻居 if (dist[y.to] > u.dis + y.w) {dist[y.to] = u.dis + y.w;q.push(node(y.to, dist[y.to])); //扩展新的邻居,放进优先队列}}}
}/// 用于 astar
struct point { int v, g, h; //评估函数 f = g + h, g是从s到i的长度,h是从i到t的长度point(int a, int b, int c) { v = a, g = b, h = c; }bool operator < (const point& b) const { return g + h > b.g + b.h; }
};int times[maxn]; //times[i]: 点i被访问的次数
int astar(int s, int t, int k) {memset(times, 0, sizeof(times));priority_queue<point> q;q.push(point(s, 0, 0));while (!q.empty()) {point p = q.top(); //从优先队列中弹出f = g + h最小的q.pop();times[p.v]++;if (times[p.v] == k && p.v == t) //从队列中第k次弹出t,就是答案return p.g + p.h;for (int i = 0; i < G[p.v].size(); i++) {edge y = G[p.v][i];q.push(point(y.to, p.g + y.w, dist[y.to]));}}return -1;
}int main() {int n, m;scanf("%d%d", &n, &m);while (m--) {int a, b, w; //读边:起点、终点、边长scanf("%d%d%d", &a, &b, &w); //本题是有向图G[a].push_back(edge(b, w)); //原图G2[b].push_back(edge(a, w)); //反图}int s, t, k;scanf("%d%d%d", &s, &t, &k);if (s == t) k++; //一个小陷阱dijkstra(t); //在反图G2上,求终点t到其他点的最短路printf("%d\n", astar(s, t, k)); //在原图G上,求第k短路return 0;
}
二、次短路问题
给出一个图,包括 𝑛 个点,1≤𝑛≤5000,𝑚 条边,1≤𝑚≤100000。问从起点 11 到终点 𝑛 的第二短路的长度。
第二短的路径中,可以包含任何一条在最短路中出现的道路,并且,一条路可以重复走多次。当然,第二短路的长度必须严格大于最短路(可能有多条)的长度,但它的长度必须不大于所有除最短路外的路径的长度。
思路:
次短路问题即当 𝑘=2 时的第 𝑘 短路问题,用前面求第 𝑘 短路的 A* 算法就能处理了。
只要把前面的代码简单修改三处:
- 修改第 7 行的 maxn 和 maxm 为本题的规模
- 第 81∼82 行,按双向边读图
- 第 88 行,改为
astar(1, n, 2)
当然这个问题还个有更简单的解法:从起点 𝑠 到图上某个点 𝑣 的最短路长度 𝑃𝑣 容易计算,而 𝑠−𝑣 的次短路,肯定是从 𝑣 的某个邻居 𝑢 过来的,它是两种情况之一:
- 𝑠−𝑢 的最短路加上边 𝑢−𝑣,总长度为 𝑃1
- 𝑠−𝑢 的次短路加上边 𝑢−𝑣,总长度为 𝑃2
比最短路 𝑃𝑣 大一点的 𝑃1 或 𝑃2 就是 𝑠−𝑣 的次短路长度,做一次 Dijkstra 计算每个点的最短路和次短路即可,复杂度和只求最短路一样也是 𝑂(𝑚𝑙𝑜𝑔𝑛) 的,比前面用A*算法求次短路稍好一点。