题目地址:http://poj.org/problem?id=2449
本文参考了http://www.cnblogs.com/n-u-l-l/archive/2012/07/29/2614194.html在这里向其表示感谢。
所谓第k最短路,就是从一个点到另一个点的所有的店中,第k个最短的。那么也就是说我们经常用的最短路就是我们要说的第1最短路。第二最短路也就是第k最短路。
那么怎样求解第k最短路呢?
因为这个题目中已经说了,每个点是允许走多次的,这就大大降低了本题的难度。首先是一种很常用的方法就是BFS加优先队列。这种方法就是由搜索出最短的,每当搜索到一次终点,就证明找到了一次最短路,搜索到k次,也就是第k个最短路,即第k最短路。但是这种方法的里昂特别大,很容易就爆栈了,所以我们要在这个上面做出一个优化,使得能够在最快的情况下找到第k个最短路。
比较常用的一种就是带启发式函数的搜索。我们命名这个函数为f(e) = g(e)+h(e)。这个函数是什么意思呢?就是你现在从出发点到自己的距离加上你到终点的最短距离。这个值越小,就说明能够越快的找到终点,也就是最短的了。这个是可以用反证证明的,我就不赘述的。那么在这个的基础上加上优先队列就可以解决了。因为我们都可以知道BFS就是求出最短的,那么每找到一次,就把次数加1,到第k次的时候,就返回当前点的g,就是我们要求的那个值。我们可以发现g这个函数很好弄,因为你在搜索的时候,就不断的累加了。但是h这个函数怎么解决呢?很简单,我们只要以终点为起点求终点到这个点的最短路径就搞定的。当然如果是单向图的话,我们还得把图反置后再求。把这几个搞清楚之后,我们就可以求出第k最短路了。下面上代码:
#include<iostream>
#include<queue>
#include<cstdio>
using namespace std;
#define MAXN 101000
const __int64 INF = 999999999;
struct edge
{
int to;
int w;
edge *next;
}v1[MAXN],v2[MAXN]; //自己写邻接表
//v2是用来保存反图的,在启发搜索的时候要用到
struct node
{
int to;
int f,g;
bool operator < (const node &t)const //为后面的优先队列用的,将f小的放在队列的前面
{
if( f == t.f)
return g>t.g;
return f>t.f;
}
};
int n,m;
__int64 dist2[MAXN];
bool is_In[MAXN];
unsigned int q[1000*MAXN]; //自己做队列
void SPFA(int x)
{
int i;
for(i=1;i<=n;i++)
{
dist2[i] = INF;
is_In[i] = false;
}
//queue<int> q;
dist2[x] = 0;
int front = 0;
int tail = 0;
q[tail++] = x;
//q.push(x);
is_In[x] = true;
while(front<tail)
{
int u = q[front++];
//int u = q.front();
//q.pop();
is_In[u] = false;
edge *p = v2[u].next;
while(p!=NULL)
{
int t = p->to;
int w = p->w;
if(dist2[t] > dist2[u]+w)
{
dist2[t] = dist2[u]+w;
if(!is_In[t])
{
//q.push(t);
q[tail++] = t;
is_In[t] = true;
}
}
p = p->next;
}
}
}
void init_map()
{
int i;
int a,b,d;
for(i=1;i<=m;i++)
{
v1[i].next = NULL;
v2[i].next = NULL;
}
//注意这里是正和反两张图
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&d);
edge *p = new edge;
p->to = b;
p->w = d;
p->next = v1[a].next;
v1[a].next = p;
p = new edge;
p->to = a;
p->w = d;
p->next = v2[b].next;
v2[b].next = p;
}
}
void clearmap()
{
int i;
for(i=1;i<n;i++)
{
edge *p = v1[i].next;
while(p != NULL)
{
v1[i].next = p->next;
delete p;
p = v1[i].next;
}
p = v2[i].next;
while(p != NULL)
{
v2[i].next = p->next;
delete p;
p = v2[i].next;
}
}
}
int A_star(int start,int end,int which_k)
{
node s;
priority_queue<node> PQ;
int cnt = 0; //记录访问到终点多少次,从而来确定是第多少的最短路
if(start == end) //如果初始点和终点一样,则第一条不算
which_k++;
if(dist2[start] == INF)
return -1;
s.to = start;
s.g = 0;
s.f = s.g+dist2[s.to];
PQ.push(s);
node cur,ne;
while(!PQ.empty())
{
cur = PQ.top();
PQ.pop();
if(cur.to == end)
cnt++;
if(cnt == which_k)
return cur.g;
edge *p = v1[cur.to].next;
while( p!=NULL)
{
ne.to = p->to;
ne.g = cur.g+p->w;
ne.f = ne.g+dist2[ne.to];
PQ.push(ne);
p=p->next;
}
}
return -1;
}
int main()
{
while(cin>>n>>m)
{
init_map();
int s,e,k;
scanf("%d%d%d",&s,&e,&k);
SPFA(e);
int ans = A_star(s,e,k);
clearmap();
cout<<ans<<endl;
}
return 0;
}
本文介绍了一种求解第K最短路径的有效方法,利用BFS加优先队列及启发式搜索,通过自定义启发函数f(e)=g(e)+h(e),其中g(e)为从起点到当前点的距离,h(e)为当前点到终点的预估距离,实现了快速查找。适用于允许节点重复访问的图。
421

被折叠的 条评论
为什么被折叠?



