最短路问题——K短路问题 / 次短路问题

最短路问题


        最短路问题中的非常著名的Dijkstra算法、Floyd-Warshall算法以及经典的练习题,大家可以去下面的链接看哈。
Dijkstra算法

Floyd-Warshall算法

一、K 短路问题

A*算法

        给定一个图,定义起点 𝑠 和终点 𝑡,以及数字 𝑘;求 𝑠 到 𝑡 的第 𝑘 短的路。允许环路。相同长度的不同路径,也被认为是完全不同的。

思路:

        用A*算法求解。把从 𝑠 到 𝑡 的路径分为两部分:从 𝑠 到中间某个 𝑖 的路径、从 𝑖 到 𝑡 的路径。估价函数 f(i) = g(i) + h(i),𝑔(𝑖) 是从 𝑠 到 𝑖 的路径长度,ℎ(𝑖)是从 𝑖 到 𝑡 的路径长度。𝑔(𝑖) 用 BFS 搜索;ℎ(𝑖) 是从 𝑖 到 𝑡 的最短路长度,用 dijkstra 计算得到。

代码:

  1. 用邻接表存图;
  2. dijkstra() 函数是标准的模板;
  3. 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到自己的距离是0
	priority_queue<node> q;
	q.push(node(s, dist[s]));		//从起点开始处理队列
	while (!q.empty()) {
		node u = q.top();			//pop出距起点s最近的点u
		q.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* 算法就能处理了。

        只要把前面的代码简单修改三处:

  1. 修改第 7 行的 maxn 和 maxm 为本题的规模
  2. 第 81∼82 行,按双向边读图
  3. 第 88 行,改为 astar(1, n, 2)

        当然这个问题还个有更简单的解法:从起点 𝑠 到图上某个点 𝑣 的最短路长度 𝑃𝑣​ 容易计算,而 𝑠−𝑣 的次短路,肯定是从 𝑣 的某个邻居 𝑢 过来的,它是两种情况之一:

  1. 𝑠−𝑢 的最短路加上边 𝑢−𝑣,总长度为 𝑃1​
  2. 𝑠−𝑢 的次短路加上边 𝑢−𝑣,总长度为 𝑃2​

        比最短路 𝑃𝑣 大一点的 𝑃1 或 𝑃2​ 就是 𝑠−𝑣 的次短路长度,做一次 Dijkstra 计算每个点的最短路和次短路即可,复杂度和只求最短路一样也是 𝑂(𝑚𝑙𝑜𝑔𝑛) 的,比前面用A*算法求次短路稍好一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值