次短路和第 k 短路

次短路和第 k 短路

次短路

在最短路的基础上,次短路可以由次短路   +   ~ +~  + 边更新,也可以由最短路   +   ~+~  + 边更新,这里注意一点,因为次短路更新时也会对其它次短路产生影响,所以更新次短路时也需要入队,我们先尝试更新最短路,成功的话就把原来的最短路给次短路,不成功的话就单独尝试更新次短路。

也就是说,我们可以在算法   d i j k s t r a   ~dijkstra~  dijkstra 的基础上,不断维护最短路和次短路。证明则十分简单,在下面的   k   ~k~  k 短路算法证明时,可以带入次短路,具体实现见代码。

priority_queue<pair<int, int>, vector<vector<int, int> >, greater<pair<int ,int> > > q;
memset(dis, 0x3f, sizeof dis); 
dis[1][1] = 0;//dis[i][1]表示 1 到 i 的最短路,dis[i][2]表示 1 到 i 的次短路
q.push({0, 1});
while(!q.empty())
{
    int x = q.top().second;
    int dist = q.top().first;
    q.pop();
    if(dist > dis[x][2]) continue;
    for(auto it : v[x])
    {
        int cnt = it.first;
        if(dis[cnt][1] > dist + it.second)//如果最短路需要松弛,那么当前的最短路将会被松弛,次短路就会松弛为原先的最短路
        {
            dis[cnt][2] = dis[cnt][1];
            dis[cnt][1] = dist + it.second;
            q.push({dis[cnt][1], cnt});
        }
        if(dis[cnt][2] > dist + it.second && dist + it.second > dis[cnt][1])//如果当前的路径比次短路优,而比最短路劣,那么只需要更新次短路
        {
            dis[cnt][2] = dist + it.second;
            q.push({dis[cnt][2], cnt});
        }
    }
}
return dis[n][2];//返回

那么,关于次短路,应该就结束了。

k 短路

此时,如果按照次短路的做法显然是不可行的,这时候我们需要引入一个叫做启发式搜索的东西。

A* 算法

定义

A* 搜索算法(英文:A* search algorithm,A* 读作 A-star),简称 A* 算法,是一种在图形平面上,对于有多个节点的路径求出最低通过成本的算法。它属于图遍历(英文:Graph traversal)和最佳优先搜索算法(英文:Best-first search),亦是 BFS 的改进。

过程

定义起点 s s s,终点 t t t,从起点(初始状态)开始的距离函数 $g(x) $,到终点(最终状态)的距离函数 h ( x h(x h(x)$, h ∗ ( x ) 1 h^{\ast}(x)1 h(x)1,以及每个点的估价函数 f ( x ) = g ( x ) + h ( x ) f(x)=g(x)+h(x) f(x)=g(x)+h(x)

A* 算法每次从优先队列中取出一个 f f f 最小的元素,然后更新相邻的状态。

如果 h ≤ h ∗ h\leq h* hh,则 A* 算法能找到最优解。

上述条件下,如果 h h h 满足三角形不等式,则 A* 算法不会将重复结点加入队列。

h = 0 h=0 h=0 时,A* 算法变为 D i j k s t r a Dijkstra Dijkstra;当 h = 0 h=0 h=0 并且边权为 1 1 1 时变为 BFS。

如何用 A* 解决 k 短路问题

A* 算法定义了一个对当前状态 x x x 的估价函数 f ( x ) = g ( x ) + h ( x ) f(x)=g(x)+h(x) f(x)=g(x)+h(x),其中 g ( x ) g(x) g(x) 为从初始状态到达当前状态的实际代价, h ( x ) h(x) h(x) 为从当前状态到达目标状态的最佳路径的估计代价。每次取出 f ( x ) f(x) f(x) 最优的状态 x x x,扩展其所有子状态,可以用 优先队列 来维护这个值。

在求解 k k k 短路问题时,令 h ( x ) h(x) h(x) 为从当前结点到达终点 t t t 的最短路径长度。可以通过在反向图上对结点 t t t 跑单源最短路预处理出对每个结点的这个值。

由于设计的距离函数和估价函数,对于每个状态需要记录两个值,为当前到达的结点 x x x 和已经走过的距离 g ( x ) g(x) g(x),将这种状态记为 ( x , g ( x ) ) (x,g(x)) (x,g(x))

开始我们将初始状态 ( s , 0 ) (s,0) (s,0) 加入优先队列。每次我们取出估价函数 f ( x ) = g ( x ) + h ( x ) f(x)=g(x)+h(x) f(x)=g(x)+h(x) 最小的一个状态,枚举该状态到达的结点 x x x 的所有出边,将对应的子状态加入优先队列。当我们访问到一个结点第 k k k 次时,对应的状态的 g ( x ) g(x) g(x) 就是从 x x x 到该结点的第 k k k 短路。

优化:由于只需要求出从初始结点到目标结点的第 k k k 短路,所以已经取出的状态到达一个结点的次数大于 k k k 次时,可以不扩展其子状态。因为之前 k k k 次已经形成了 k k k 条合法路径,当前状态不会影响到最后的答案。

当图的形态是一个 n n n 元环的时候,该算法最坏是 O ( n k log ⁡ n ) O(nk\log n) O(nklogn) 的。但是这种算法可以在相同的复杂度内求出从起始点 s s s 到每个结点的前 k k k 短路。

具体实现

因为步骤较为繁琐,所以直接给出完整代码

#include <bits/stdc++.h>
#define IOS ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, pair<int, int> > PIII;
typedef pair<int, int> PII;
int s, t, k, n, m, u, v, z;
struct node
{
	int v;
	int len;
};
vector<node> mp[1010], mp_dij[1010];
int dist[1010];
bool vis[1010];
int cnt[1010];
void dij()
{
	priority_queue<PII, vector<PII>, greater<PII> > q;
	q.push({0, t});
	memset(dist, 0x3f, sizeof dist);
	dist[t] = 0;
	while(q.size())
	{
		int u = q.top().y;
		q.pop();
		if(vis[u]) continue;
		vis[u] = true;
		for(auto it : mp_dij[u])
		{
			int v = it.v;
			if(dist[v] > dist[u] + it.len) dist[v] = dist[u] + it.len, q.push({dist[v], v});
		}
	}
	return;
}
int A_star()
{
	priority_queue<PIII, vector<PIII>, greater<PIII> > q;
	q.push({dist[s], {0, s}});
	while(q.size())
	{
		PIII cur = q.top();
		q.pop();
		int u = cur.y.y;
		int dis = cur.y.x;
		cnt[u] ++;
		if(cnt[t] == k) return dis;
		if(cnt[u] > k) continue;
		for(auto it : mp[u])
		{
			int v = it.v;
			q.push({dis + it.len + dist[v], {dis + it.len, v}});
		}
	}
	return -1;
}
int main()
{
	IOS;
	cin >> n >> m;
	for(int i = 0; i < m; i ++) cin >> u >> v >> z, mp_dij[v].push_back({u, z}), mp[u].push_back({v, z});
	cin >> s >> t >> k;
	if(s == t) k ++;
	dij();
	cout << A_star();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值