A* && 第k短路详解 (详尽)


首先分享一个我学习的博客文章:

Poj2449-A*初步+k短路

看着他的题解学会了k短路。。%%%

然后我就大致说一说k短路的求法吧。。


首先我们来看看A*。

A*,启发式搜索,是一种较为有效的搜索方法。

我们在搜索的时候,很多时候在当前状态,已经不是最优解了,但是我们却继续求解;

这个就是暴力搜索浪费时间的原因。

我们在有些时候,往往可以根据一些信息推断出继续搜索是一种劣解,

所以如果能够判断出来的话,就可以不继续了,以达到节省运行时间的目的。

来看一题很经典的例题:BZOJ 1085

1085: [SCOI2005]骑士精神

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 2276   Solved: 1312
[
Submit ][ Status ][ Discuss ]

Description

  在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位。在任何时候一个骑士都能按照骑
士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2,纵坐标相差为1的格子)移动到空
位上。 给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘: 为了体现出骑士精神,他们必须以最少的步
数完成任务。

Input

  第一行有一个正整数T(T<=10),表示一共有N组数据。接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑
士,*表示空位。两组数据之间没有空行。

Output

  对于每组数据都输出一行。如果能在15步以内(包括15步)到达目标状态,则输出步数,否则输出-1。

Sample Input

2
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100

Sample Output

7
-1

HINT


题意很简单,按象棋的马字走法移动黑白骑士到一个“自由位”上,
   问令骑士回到各自位置上的最少移动步数。
首先来考虑一下这题的解法:
dfs?没错,不论dfs,bfs,这种暴力的方法是正确的,但是TLE也是必然的。
如果想要考虑dp,那么此题5*5的棋盘的数据范围也是不允许dp的。
是否山穷水尽?
题目中一个条件: 步数超出15了就不需要继续了。
如果换一个角度去思考问题:假如说给你一个棋盘,让你马上说出答案,你会如何?
显然,如果一个个去操作是肯定不行的;我们一般都会估计一下答案。
我们的估算可以看成“至少需要几个步骤让骑士到各自位置上”。
在这里,我们引入A*算法(所谓IDA*只是在实现的时候用了迭代加深的方法;有时候我们会用IDA*代替递归)
定义一个估价函数,这个估价函数就是所谓的估计值。我们令其为f(x),则
f(x)=g(x)+h(x)
其中,f(x)指我们估计答案的价值,而h(x)是实际值,g(x)是我们的预测,也就是估计值。
通过估价函数,我们可以确定解是否可行/解是否更优等等。
就上题而言,我们应该定义一个怎样的g(x)呢?我们的目标状态是:

黑黑黑黑黑
白黑黑黑黑
白白    黑黑
白白白白黑
白白白白白

那么比如说,对于当前某一个任意的状态,就比如:

黑黑黑
白黑黑
白白    黑黑
白白黑
白白白

它需要几个步骤达到目标状态呢?这是我们要求解的问题,但是我们并不知道。
我们无法马上得到解,但是, 我们可以通过另外的方法得到近似解!
比如说,我们直接统计有几个马不在它该在的位置上,就是上面标红色的几个位置的马。
我们看到这题还有十分显然的一点: 这些位置共x个,那么达到目标状态的步数至少是x
比如上面有4个,我们已经移动了s步数,那么如果s+4>15,就不用继续下去了。
看到这里的f(x)=g(x)+h(x)=s+4。
这也是A*算法的核心:我们建立的估价函数g(x),它不能影响最优解。
我们刚才的统计方式,问题也很明显,这样的话显然估价太低了,很多时候,这x个位置需要>x的步数。
但是这也没有问题,因为估价函数差一点是允许的,它影响我们的时间消耗。
那么如果我们的g(x)=x*5呢?可以看到,这可能会(应该是绝对会)大于最优的步数,
这个时候我们称g(x)是错误的。
当然也可以不用这种方法来估价,因为它的估价太小。
这题还可以用另外的估价,不过相对复杂;前面的g(x)已经可以解决这题了。

我们能够总结一下g(x)设计上的问题。
如果g(x)得到的结果优于最优解,那么g(x)是错误的。
如果g(x)得到的结果劣于最优解,那么g(x)的偏差越大程序性能越差。
我们利用得到的f(x)来判断先往哪一个方向搜索,也就是启发式搜索。
一定要注意!设计g(x)的时候,绝对避免第一种情况,而尽量减小与最优解的误差。

A*的大致思想如此,上面这题的题解可以看我的另一篇blog:
BZOJ 1085 骑士精神 题解

当然,A*可以在递归求解问题的时候加上优化,而所谓IDA*其实就是把搜索的方式换成迭代加深 。


这并不是今天的重点,这些还是有些基础。。


接下来的问题:k短路。它是一种A*的很好的应用,虽然我也不知道会不会有其它解法(本蒟蒻一枚)
k短路同样也有入门题目,或者说是模板题目,或者说是好题目:
Poj2449

Remmarguts' Date
Time Limit: 4000MS Memory Limit: 65536K
Total Submissions: 29710 Accepted: 8068

Description

"Good man never makes girls wait or breaks an appointment!" said the mandarin duck father. Softly touching his little ducks' head, he told them a story.

"Prince Remmarguts lives in his kingdom UDF – United Delta of Freedom. One day their neighboring country sent them Princess Uyuw on a diplomatic mission."

"Erenow, the princess sent Remmarguts a letter, informing him that she would come to the hall and hold commercial talks with UDF if and only if the prince go and meet her via the K-th shortest path. (in fact, Uyuw does not want to come at all)"

Being interested in the trade development and such a lovely girl, Prince Remmarguts really became enamored. He needs you - the prime minister's help!

DETAILS: UDF's capital consists of N stations. The hall is numbered S, while the station numbered T denotes prince' current place. M muddy directed sideways connect some of the stations. Remmarguts' path to welcome the princess might include the same station twice or more than twice, even it is the station with number S or T. Different paths with same length will be considered disparate.

Input

The first line contains two integer numbers N and M (1 <= N <= 1000, 0 <= M <= 100000). Stations are numbered from 1 to N. Each of the following M lines contains three integer numbers A, B and T (1 <= A, B <= N, 1 <= T <= 100). It shows that there is a directed sideway from A-th station to B-th station with time T.

The last line consists of three integer numbers S, T and K (1 <= S, T <= N, 1 <= K <= 1000).

Output

A single line consisting of a single integer number: the length (time required) to welcome Princess Uyuw using the K-th shortest path. If K-th shortest path does not exist, you should output "-1" (without quotes) instead.

Sample Input

2 2

1 2 5

2 1 4

1 2 2

Sample Output

14


这题待会儿要写题解,,算了还是写一遍吧(我帅

题意就是:n个点,m条边,每条给出有向边并带有权值,给出start,end和k,求s~t所有路径中的第k短路。

没错这就是一道模板题目。。

我们来审视一下k短路的问题求解:

我们知道,可以暴力搜索,然后统计到第k短即可。

显然暴力是远远不行的。

另外,我们求最短路有许多优秀算法:dijkstra,SPFA……可以把最短路问题和k短路问题连接起来吗?

我们尝试一下A*。另f(x)是某k短路径的近似长度:

f(x)=g(x)+h(x)

f(x)就是路径的长度,g(x)是估价函数,我们选择x~end的最短路径(这个后面会解释);

h(x)是实际长度,start~x的总路径长。

那么我们优先访问f(x)更加小的点:因为它更可能成为最短,再第二短,再……

如果end被访问了k次了,那么目前得到的值f(x)就是k短路的长度。

现在可能还比较朦胧,我们进一步解释:

1.到底如何求k短路的?

      我们考虑,要求k短路,要先求出最短路/次短路/第三短路……/第(k-1)短路,然后访问到第k短路。

      接下来的方法就是如此操作的。

2.f(x)的意义?

      我们得到的f(x)更小,优先访问这个f(x)的点。

      我们可以定义一组数{p,g,h},p是某一个点,g是估价,h是实际,那么g+h更小的点p会优先访问。

      为什么呢?因为假设我们求出了w短路,接下来要求(w+1)短路,就要求最小的另一条路径。

      应该易理解。

3.为什么选择最短路来估价?

      很简单的选择,我们既然要求最短了,当然是找最短路。

      事实上这不是我们的初衷,但是有了“先求(k-1)短路”的概念后,这么理解也可以了。

4.实现

      实现是比较简单的。

      如何预处理出g(x)呢?显然,将所有边反向,然后求end到所有点的单源最短路径就好了。

      接下来的启发式搜索可以简单解决。

事实上,就是在暴力搜索的基础上增加了启发式搜索:往一个最优的点的地方走。

另外还是要专来一篇这题blog的QAQ

关于上题目的程序:

(跑得有点慢。。300+ms。)

(大部分借鉴开头说的blog的程序)

//#include
#include
#include
#include
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0' || ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int
	Point=1005,
	Edges=100005;
int n,m,start,end,kth;
int dist[Point],times[Point];
bool vis[Point];
struct Edge{
	int to,next,val;
}E[Edges],Eopp[Edges];      //Eopp means Eopposite
int head[Point],head_opp[Point];
struct A_Star_node{
	int p,g,h;
	bool operator < (A_Star_node x)const{
		return x.g+x.hQ;
inline void add(int Ecnt,int u,int v,int w){
	E[Ecnt].next=head[u];
	E[Ecnt].to=v;
	E[Ecnt].val=w;
	head[u]=Ecnt;
}
inline void add_opposite(int EoppCnt,int u,int v,int w){
	Eopp[EoppCnt].next=head_opp[u];
	Eopp[EoppCnt].to=v;
	Eopp[EoppCnt].val=w;
	head_opp[u]=EoppCnt;
}
void dijkstra(int s,int e){
	memset(vis,0,sizeof(vis));
	memset(dist,127,sizeof(dist));
	int mini;	dist[e]=0;
	for (int i=1;i<=n;i++){
		mini=0;
		for (int j=1;j<=n;j++)
			if (!vis[j] && dist[mini]>dist[j])	mini=j;
		vis[mini]=1;
		for (int x=head_opp[mini];x;x=Eopp[x].next)
			dist[Eopp[x].to]=min(dist[Eopp[x].to],dist[mini]+Eopp[x].val);
	}
}
int A_Star(int s,int e){
	A_Star_node t1,tmp;
	memset(times,0,sizeof(times));
	t1.g=t1.h=0; t1.p=s;
	Q.push(t1);
	while (!Q.empty()){
		t1=Q.top();	Q.pop();
		times[t1.p]++;
		if (times[t1.p]==kth && t1.p==e) return t1.h+t1.g;
		if (times[t1.p]>kth) continue;
		for (int i=head[t1.p];i;i=E[i].next){
			tmp.p=E[i].to;
			tmp.g=dist[E[i].to];
			tmp.h=E[i].val+t1.h;
			Q.push(tmp);
		}
	}
	return -1;
}
int main(){
	n=read(),m=read(),kth=read(),start=read(),end=read();
	int x,y,z;
	memset(head,0,sizeof(head));
	memset(head_opp,0,sizeof(head_opp));
	for (int i=1;i<=m;i++){
		x=read(),y=read(),z=read();
		add(i,x,y,z);
		add_opposite(i,y,x,z);
	}
	dijkstra(start,end);
	if (start==end) kth++;
	int ans=A_Star(start,end);
	if (ans==-1) puts("No");
		else	printf("%d\n",ans);
	return 0;
}
 

你可能感兴趣的:(A*,最短路/最短路数目等,(可并)堆,k短路,学习文章)