次短路和第 k 短路

次短路和第 k 短路

次短路

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

也就是说,我们可以在算法\(~dijkstra~\)的基础上,不断维护最短路和次短路。证明则十分简单,在下面的\(~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\),终点 \(t\),从起点(初始状态)开始的距离函数 $g(x) $,到终点(最终状态)的距离函数 \(h(x\))$, \(h^{\ast}(x)1\),以及每个点的估价函数 \(f(x)=g(x)+h(x)\)

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

如果 \(h\leq h*\),则 A* 算法能找到最优解。

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

\(h=0\) 时,A* 算法变为 \(Dijkstra\);当 \(h=0\) 并且边权为 \(1\) 时变为 BFS。

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

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

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

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

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

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

当图的形态是一个 \(n\) 元环的时候,该算法最坏是 \(O(nk\log n)\) 的。但是这种算法可以在相同的复杂度内求出从起始点 \(s\) 到每个结点的前 \(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;
}
posted @ 2024-08-09 15:41  LTC_Augenstern  阅读(15)  评论(0编辑  收藏  举报