通常在解决问题的时候,我们需要用到搜索算法,由已知状态推出新的状态,然后检验新的状态是不是就是我们要求的最优解。检验完所有的状态实际上就相当于遍历了一张隐式图。遗憾的是,所有的状态组成的状态空间往往是成指数级别增长的,也就造成了遍历需要用到指数级别的时间,因此,纯粹的暴力搜索,时空效率都比较低。当然,我们在生活中遇到了类似于搜索的问题,我们并不会盲目地去搜寻每一种状态,我们会通过我们的思维,选择一条最接近于目标的路径或者是近似于最短的路径去完成搜索任务。当我们想要计算机去完成这样一项搜索任务的时候,就得让计算机像人一样能够区分尽量短的路径,以便高效地找到最优解。这时可以把计算机看作是一种智能体(agent)可以实现由初始状态向目标状态的转移。
有一种贪心策略,即每一步转移都由计算机选择当前的最优解生成新的状态,一直到达目标状态为止。这样做的时间效率虽然较高,但是贪心的策略只是用到了局部的最优解,并不能保证最后到达目标状态得到的是全局最优解。在能保证全局最优解的范围内,贪心算法还是很有用的。比如说我们熟知的Dijkstra算法求单源最短路。每次选择距离源节点最短距离的待扩展节点进行扩展,最后就能生成源节点到所有节点的最短路径。下面会讲到Dijkstra的扩展,当理解了这个算法之后,我想,你会对Dijkstra有更深入的理解。
这就是A*算法。定义初始状态S,目标状态t,g(s)是由初始状态转移到当前状态s所经过的路径长度,h*(s)是当前状态s距离目标状态t的实际长度,但是一般情况下我们是不知道h*(s)的值的,所以还要定义一个估价函数h(s),是对h*(s)函数值的下界的估计,也就是有h(s)<=h*(s),这样需要一个条件,使得由s1生成的每状态s2,都有h(s1)<=h(s2),这是一个相容的估价函数。再定义f(s)=g(s)+h(s)为启发函数,因为h(s)是单调递增的,所以f(s)也是单调递增的。这样f(s)就估计出了由初始状态的总体代价。A*算法就通过构造这样一个启发函数,将所有的待扩展状态加入到队列里,每次从队列里选择f(s)值最小的状态进行扩展。由于启发函数的作用,使得计算机在进行状态转移的时候尽量避开了不可能产生最优解的分支,而选择相对较接近最优解的路径进行搜索,提高了搜索效率。
讲到这里,可能已经对A*算法的概念有点眉目了。下面我再来做一个比较,就用上面讲到的Dijkstra的例子。Dijkstra算法说的是每次选择距离源点最短距离的点进行扩展。当然可以看做事先将源点到所有节点距离的值保存在一个优先队列里,每次从优先队列里出队最短距离的点扩展,每个点的扩展涉及到要更新队列里所有待扩展节点的距离值,每个节点只能进队一次,就需要有一个表来记录每个节点的入队次数(就是算法中用到的标记数组)。将Dijkstra求最短路的方法扩展,这道题目要求的是两点间第k短路。类比于Dijkstra算法可以首先确定下面几个搜索策略:
1、用优先队列保存节点进行搜索。
2、放开每个节点的入队次数,求k短路,每个节点可以入队k次。
首先看第一个策略,在A*算法中用优先队列就是要用到启发函数f(s)确定状态在优先队列里面的优先级。其实Dijkstra用到的优先队列实际上就是估价函数值为0,启发函数f(s)=g(s),即是选取到源点距离最近的点进行扩展。因为h(s)=0满足了估价函数相容这个条件。这题求k短路就不能单纯的使用h(s)=0这个估价函数。解决这道题的时候选取h(x)=dt(x), dt(x)是x节点到目标节点的最短距离。最短距离可以开始由Dijkstra直接求得。
再看第二个策略,控制每个节点的入队(或出队)次数为k次,可以找到第k短路径。可能这样想有点主观的套用,那么我就先来证明这样一个结论:
如果x是s到t的第k短路径上的一个节点,那么由这条路径s到x是s到x的第m短路径,则不可能有m>k。用反证法很容易得出:如果这条路径是s到x的第m短路径,如果m>k,那么经过x到t的路径就有m-1条比当前路径要短,不符合当前路径是s到t的第k短路径。
本题:首先求出其他点到T的最短距离,然后用基于BFS的优先队列A*算法求,f(i)=g(i)+h(i) 其中h(i)表示i到des的最短路,g(i)表示从S到i的
路径长度每次取出f(i)值最小的,当第k次取出T时即求出第k短路。
自己的代码。
#include <iostream> #include <cstdio> #include <algorithm> #include <string> #include <cmath> #include <cstring> #include <queue> #include <set> #include <vector> #include <stack> #include <map> #include <iomanip> #define PI acos(-1.0) #define Max 100005 #define inf 1<<28 #define LL(x) (x<<1) #define RR(x)(x<<1|1) using namespace std; int S,T,K,n,m; int head[Max],rehead[Max]; int num,renum; int dis[Max]; bool visit[Max]; int ans[Max]; int qe[Max*10]; struct kdq { int v,len,next; } edge[Max],reedge[Max]; struct a_star //A*搜索时的优先级队列 { int v; int len; bool operator<(const a_star &a)const //f(i)=d[i]+g[i] { return len+dis[v]>a.len+dis[a.v]; } }; void insert(int u,int v,int len)//正图和逆图 { edge[num].v=v; edge[num].len=len; edge[num].next=head[u]; head[u]=num; num++; reedge[renum].v=u; reedge[renum].len=len; reedge[renum].next=rehead[v]; rehead[v]=renum; renum++; } void init() { memset(ans,0,sizeof(ans)); for(int i=0; i<=n; i++) head[i]=-1,rehead[i]=-1; num=1,renum=1; } void spfa()//从T开始求出T到所有点的 dis[] { int i,j; for(i=1; i<=n; i++) dis[i]=inf; dis[T]=0; visit[T]=1; int num=0,cnt=0; qe[num++]=T; while(num>cnt) { int temp=qe[cnt++]; visit[temp]=0; for(i=rehead[temp]; i!=-1 ; i=reedge[i].next) { int tt=reedge[i].v; int ttt=reedge[i].len; if(dis[tt]>dis[temp]+ttt) { dis[tt]=dis[temp]+ttt; if(!visit[tt]) { qe[num++]=tt; visit[tt]=1; } } } } } int A_star() { if(S==T) K++; if(dis[S]==inf) return -1; a_star n1; n1.v=S; n1.len=0; priority_queue <a_star> q; q.push(n1); while(!q.empty()) { a_star temp=q.top(); q.pop(); ans[temp.v]++; if(ans[T]==K)//当第K次取到T的时候,输出路程 return temp.len; if(ans[temp.v]>K) continue; for(int i=head[temp.v]; i!=-1; i=edge[i].next) { a_star n2; n2.v=edge[i].v; n2.len=edge[i].len+temp.len; q.push(n2); } } return -1; } int main() { int i,j,k,l; int a,b,s; scanf("%d%d",&n,&m); init(); while(m--) { scanf("%d%d%d",&a,&b,&s); insert(a,b,s); } scanf("%d%d%d",&S,&T,&K); spfa(); printf("%d\n",A_star()); return 0; }