【POJ 2449】第K短路【A*算法】

题意:

给定一张N个点,M条边的有向图,求从起点到终点T的第K短路的长度。

思路:

求第K短路,我们先回忆一下Dijkstra求最短路的方法。

每次松弛了一个节点,则将该节点放入优先队列,然后在取优先队列的第一个点,即源点到该点距离最短的点,再用该点去松弛其他的点。最终求出的距离就是单源最短距离。

换句话说,就是用优先队列bfs对整张图进行遍历,当一个点被第一次取出时,此时的距离便是该点的最短距离。

由此,我们便得知了求第K短路的方法。即对于任意顶点 i ,当顶点 i 被第K次取出时,此时的距离便是该点的第K短路。

但是,如果直接用优先队列的bfs去求第K短路,无疑会造成大量的冗余搜索。因此我们需要用A*算法,即启发式算法进行优化。

启发式算法的核心:

1.设立一个估价函数,对于每一个状态,估价函数的值就是该状态到目标状态所需的花费。注意这个花费必须小于真实花费,否则程序会出错。

扫描二维码关注公众号,回复: 2928700 查看本文章

2.对每一次搜索到的状态,我们定义该状态的值为“该状态的花费”+“该状态到目标状态所需的值”,即估价函数在该状态下的值。

3.然后我们每次取出状态值最小的点进行bfs,即可优化搜索。

由此,A*,即是改变bfs的搜索方向,使得我们更快地搜到目标值。

我们再回到本题,在该题中,估价函数应该是该点到目标点的距离,此时估价花费 <= 真实花费,因此可行。

我们定义每个点的值为当前到该点的距离 + 该点到目标点的距离,然后再进行bfs优先队列搜索,即为A*算法在本题的应用。

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
const int M = 1e5+100;
const int N = 2000;
const int inf = 0x3f3f3f3f;

int n,m,s,t,k;
struct Edge{
	int to,next,w;
}e[M],ef[M];
int head[N],headf[N],tot,totf;
int dis[N],book[N],dist[N];
struct Po{
	int id;	//到达哪一个点
	int fw; //到该点已经走过的距离
	int w; //到该点已经走过的距离+该点距离终点的距离
}tmp;

bool operator < (Po x,Po y)	//优先队列只能重载 < 
{
	return x.w>y.w;	//如果放在结构体内重载,需要加const
}
void init()
{
	tot = 1; totf = 1;
	memset(head,0,sizeof head);
	memset(headf,0,sizeof headf);
}

void add(int x,int y,int w)
{
	e[++tot].to = y; e[tot].next = head[x]; head[x] = tot; e[tot].w = w;
}

void addf(int x,int y,int w)
{
	ef[++totf].to = y; ef[totf].next = headf[x]; headf[x] = totf; ef[totf].w = w;
}

void spfa(int x)
{
	queue<int> q;
	memset(book,0,sizeof book);
	rep(i,1,n) dis[i] = inf;
	while(!q.empty()) q.pop();
	q.push(x);
	book[x] = 1;
	dis[x] = 0;
	while(!q.empty())
	{
		int p = q.front();
		for(int i = headf[p]; i ; i = ef[i].next)
		{
			if(dis[ef[i].to] > dis[p]+ef[i].w)
			{
				dis[ef[i].to] = dis[p]+ef[i].w;
				if(book[ef[i].to] == 0)
				{
					q.push(ef[i].to);
					book[ef[i].to] = 1;
				}
			}
		}
		book[p] = 0;
		q.pop();
	}
}

int bfs()
{
	if(dis[s] == inf) return -1;	//如果不加这句话,就是wa,确保连通性
	int val = 0;
	priority_queue<Po> q;
	while(!q.empty()) q.pop();
	tmp.fw = 0;	//走到id这个点目前所需要的距离
	tmp.id = s;
	tmp.w = tmp.fw+dis[tmp.id];
	q.push(tmp);
	if(t==s)k++;	//这里也是一个大坑点
	while(!q.empty())
	{
		Po p = q.top();
		q.pop();	//优先队列,必须在push之前就pop,不然队列顶的那个点会被调到底下去
		int id = p.id;	//p所在的点
		if(id == t) val++;
		if(val == k) return p.fw;	//求第k短路
		for(int i = head[id]; i ; i = e[i].next)	//加入新边
		{
			tmp.fw = p.fw+e[i].w;
			tmp.id = e[i].to;
			tmp.w = tmp.fw+dis[tmp.id];	//此处为估价函数
			q.push(tmp);
		}
	}
	if(val!=k){return -1;}
}

int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		init();
		rep(i,1,m)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			add(x,y,z);
			addf(y,x,z);
		}
		scanf("%d%d%d",&s,&t,&k);
		spfa(t);
		printf("%d\n",bfs());
	}
	return 0;
}

/*
2 10
1 2 42
1 2 1
1 1 79
2 2 65
2 2 82
1 2 92
2 1 28
2 1 5
2 1 93
2 1 17
1 2 1000
*/

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/81707295