最短路问题的扩展与应用

  下面简要介绍k短路、差分约束系统、有向无环图上的最短路和Floyd算法求最小环的求解方法。

  1.k短路

  k短路就是指次短路、第三最短路……等问题。其在实际生活中有颇多用处。

  其基本算法为:

  目前使用较多的算法是单源最短路配合A*。A*是搜索中比较高级的方式,A*算法结合了启发式搜索(这种方法通过充分利用图给出的信息来动态地做出决定而使搜索次数大大降低)和形式化方法(这种方法不利用图给出的信息,而仅通过数学的形式分析)。他通过一个估价函数f(h)来估计图中的当前点p到终点的距离,并由此决定他的搜索方向,当这条路径失败时,他会尝试其他路径。对于A*,估价函数=当前值+当前位置到终点的最短距离,即f(p)=g(p)+h(p),每次扩展估价函数值最小的一个。对于k短路算法来说,g(p)为当前从s到p所走路径的长度,h(p)为从点p到t的最短路径的长度,则f(p)的意义就是从s按照当前路径走到p后再走到终点t一共至少要走多远。也就是每次扩展都是有方向的,这样对提高出解的速度还是降低扩展的状态数目都有好处。为了加速计算,h(p)需要在A*搜索之前进行预处理,只要将原图的所有边反向,再从终点t做一次单源最短路就能够得到每个点的h(p)了。

  具体实现如下:

  采用链式前向星来存图。

 1 struct NODE1
 2 {
 3     int to;
 4     int g,f;
 5     bool operator<(const NODE1 &r)const
 6     {
 7         if(r.f==f)return r.g<g;
 8         else return r.f<f;
 9     }
10 };
11 
12 bool spfa(int s,int n,int head[maxn],NODE edge[maxm],int dist[maxn])
13 
14 int a_star(int start,int end,int n,int k,int head[maxn],NODE edge[maxm],int dist[maxn])
15 {
16     NODE1 e,ne;
17     int cnt=0;
18     priority_queue<NODE1>que;
19     if(start==end)k++;
20     if(dist[start]==INF)return -1;
21     e.to=start;
22     e.g=0;
23     e.f=e.g+dist[e.to];
24     que.push(e);
25     while(!que.empty())
26     {
27         e=que.top();
28         que.pop();
29         if(e.to==end)
30         {
31             cnt++;
32         }
33         if(cnt==k)
34         {
35             return e.g;
36         }
37         for(int i=head[e.to];i!=-1;i=edge[k].next)
38         {
39             ne.to=edge[i].to;
40             ne.g=e.g+edge[i].w;
41             ne.f=ne.g+dist[ne.to];
42             que.push(ne);
43         }
44     }
45     return -1;
46 }
47 int main()
48 {
49     //先读取图信息存在head和edge中,并生成所有边反向的图存在head1和edge1中,
50     //然后进行下面的操作
51     spfa(t,n,head1,edge1,dis);
52     int kthlength=a_star(s,t,n,k,head,edge,dis);
53 }
View Code

  SPFA+A*求k短路应该说是比较高效的一种算法,但是比较遗憾的是由于A*的特殊性,他的效率无法计算。

  NOTE:当s=t时,k=k+1;因为s到t的这条距离为0的路不能算在这k短路中。

 

  2.差分约束系统

  差分约束系统是线性规划问题的一种。在一个差分约束系统中,线性规划矩阵A的每一行包含一个1和一个-1,A的所有其他元素都为0.因此,由AX≤B给出的约束条件是m个差分约束集合,其中包含n个未知元。每个约束条件为如下形式的简单线性不等式:xj-xi≤bk,其中1≤i,j≤n,1≤k≤m。

  基本算法:

  通过观察不难发现:每个约束条件的不等式与求单源最短路径算法中的松弛操作极为相似。

  将图形理论与差分约束系统AX≤B加以联系:m•n的线性规划矩阵A可被看做n个顶点、m条边的图的关联矩阵(http://baike.baidu.com/link?url=1A5wjZAsr6yVU667eByAlpLvP0CQBtHiz9E-5ULcPkIo4Mu3VsUffc2_B4Mr9K5w)的转置。对于每个顶点Vi对应着n个未知量中的一个xi。图中的每个有向边对应于两个未知变量的m个不等式的其中一个。

  这样,通过求解新建图的单源最短路径问题就能得到差分约束系统的一组解。

  为保证图的连通,在图中引入附加节点Vs,使得图中每个顶点都能从Vs可达,并设弧的权值为0.对于每个差分约束xj-xi≤bk,则弧<xi,xj>的权为bk

  初始化dist[Vs]=0,dist[Vi]=INF(i≠s)。

  求解以Vs为源点的单源最短路径。此时使用的算法一般为SPFA算法,因为差分约束系统中一般都存在负值。另外在这里有个技巧,就是如果使用SPFA的时候不想添加附加节点的话,可以在初始化的时候把所有节点都加入队列中,其实就是相当于源点入队,开始算法后Vs出队更新所得到的队列,因为没有边指向Vs,所以后面的更新不会涉及Vs。

  需要说明的是:如果图中存在负权回路,则不存在可行解。Vs如果到某点不存在最短路径,则对于该点所表示的变量可以去任意值。

  其实,该算法在运行结束后Vs到各点的距离就为一组可行解。

 

  3.DAG图上的单源最短路径问题

  在无回路的有向图上,一定有某些点不能由其他的点所到达,如果有多个这样的点,可以考虑加入一个新的点u,使u到这些点有一条有向边,并且权值为0,即可转化为一个源点的DAG图。这时有更高效的特殊解法。

  基本算法:

  在DAG上的单源最短路径算法,将用到拓扑排序的内容。DAG图一定存在拓扑排序,且若在有向图G中从顶点Vi到顶点Vj有一条路径,则在拓扑排序中顶点Vi必须在Vj之前,而因为在DAG图中没有环,所以按照DAG图的拓扑排序进行序列的最短路径的更新,一定能更新出最短路径。处理顶点v时,对每条离开的边<v,u>执行松弛运算,如果<v,u>给出从源点到u的一条更短路径,则更新到u的最短路径。这个过程将检查从源点到图中每个顶点的所有路径,同时,拓扑排序按照正确的顺序处理顶点。根差分约束系统一样,对于多源点向单源点转化时新加入的节点可以通过预处理来避免在实际代码中加入这个节点,在DAG图上的单源最短路径的代码中,是将入度为0的节点的dist设为0,其他的设为inf。

  NOTE:DAG上的单源最短路径算法和SPFA算法一样,都相当于在Bellman-Ford算法的基础上,在点的更新顺序上的特殊优化,所以DAG上的单源最短路径算法也可以处理负权边。

  代码如下:

 1 //由拓扑排序算法得到拓扑序列数组queue,然后继续下面的代码
 2 int dist[maxn];
 3 for(i=1;i<=n;i++)dist[i]=inf;
 4 dist[1]=0;
 5 for(i=0;i<iq;i++)
 6 {
 7     for(k=head[queue[i]];k!=-1;k=edge[k].next)
 8     {
 9         if(dist[edge[k].to]>dist[queue[i]]+edge[k].w)
10         dist[edge[k].to]=dist[queue[i]]+edge[k].w;
11     }
12 }
View Code

  整个算法的时间复杂度为O(m)。

  

  4.Floyd求最小环

  基本算法:

  首先,理解Floyd算法是按照点的编号增加的顺序更新最短路径的,那么如果存在这个最小环,会在这个环中的点编号最大的那个点u跟新最短路径之前发现这个环,也就是说,当u点被拿来更新i到j的最短路径时,可以发现这个闭合环路,发现的方法是更新最短路径前,遍历i、j点对,一定会发现某对i到j的最短路径长度dist[i][j]+map[j][u]+map[u][i]!=inf,这时的i和j是当前环中挨着点u的两个点。因为在之前的最短路径更新过程中,u没有参与更新,所以dist[i][j]所表示的路径中不会有点u,所以如果上面的式子成立,一定是一个环。而如果在每个新的点拿来更新最短路径之前遍历i和j验证上面的式子,虽然不一定能遍历图中所有的环,但是由于dist[i][j]是i点到j点的最短路径,会发现最小的环。之所以用dist[i][j]与点u来判断,是因为同在这个最小环上的两个点,他们之间的最短路径一定是这个环的一部分。另外,这个算法对于负环会失效,因为包含负环的图上,dist[i][j]已经不能保证i到j的路径上不会经过同一个点多次了。

  代码如下:

 1 int mincircle=inf;
 2 for(k=1;k<=n;k++)
 3 {
 4     //最小环的判断
 5     for(i=1;i<=n;i++)
 6     {
 7         for(j=1;j<=n;j++)
 8         {
 9             if(dist[i][j]!=inf&&map[j][k]!=inf&&map[k][i]!=inf)
10             mincircle=min(mincircle,dist[i][j]+map[j][k]+map[k][i]);
11         }
12     }
13     //Floyd部分
14     for(i=1;i<=n;i++)
15     {
16         for(j=1;j<=n;j++)
17         {
18             if(dist[i][k]!=inf&&dist[k][j]!=inf&&dist[i][k]+dist[k][j]<dist[i][j])
19             {
20                 dist[i][j]=dist[i][k]+dist[k][j];
21                 pre[i][j]=pre[k][j];
22             }
23         }
24     }
25 }
View Code

  时间复杂度O(n^3)。

 

参考文献:《图论及应用》哈尔滨工业大学出版社

特此申明:严禁转载

 2014-02-22

posted @ 2014-02-22 17:51  EtheGreat  阅读(575)  评论(0编辑  收藏  举报