图论 之 最短路
最短路问题(short-path problem)
若网络中的每条边都有一个数值(长度、成本、时间等),则找出两节点(通常是源节点和阱节点)之间总权和最小的路径就是最短路问题。最短路问题是网络理论解决的典型问题之一,可用来解决管路铺设、线路安装、厂区布局和设备更新等实际问题。
最短路问题,我们通常归属为三类:

第一次: 沿着深红色路径先找到V1,W1= 3;
第二次: 由V1更新V0到其他顶点的距离,沿着黄色路径找到V2,W2= 3+2=5;
第三次: 由V2更新V0到其他顶点的距离,沿着蓝色路径找到V3=10(因为V2拓展出的路径长度都大于10);
第四次: 由V3更新V0到其他顶点的距离,沿着褐色路径找到V4,W4= 10+1=11;
第五次:有V4更新V0到其他顶点的距离,沿着粉色路径找到V5,W5 = 10+3;
这样下来所有顶点都已经访问完毕,算法结束。
整理代码:
我们发现整个过程其实就是以起点开始,每次在未访问过的顶点中寻找起点到该点距离最短的,并且以这个点来更新其他顶点,然后循环n-1次(因为顶点不需要操作么~)
那么代码也就很显然了:
1 void dij() //matrix数组存放i到j的距离,不可达为无穷大 2 { 3 int i, j, minx, pos; 4 for(i=1; i<=n; ++i){ 5 vist[i] = false; //vist数组记录顶点是否被访问过 6 d[i] = matrix[1][i]; //d数组是dijkstra的核心 7 } 8 9 vist[1] = 1; //初始化默认起点被访问过 10 for(i=1; i<n; ++i){ //遍历n-1次 11 minx = inf; 12 for(j=1; j<=n; ++j)if(!vist[j]){ //每次从未被访问过的顶点中寻找距离最小的 13 if(minx > d[j]){ 14 minx = d[j]; 15 pos = j; 16 } 17 } 18 vist[pos] = 1; //标记这个顶点被访问过 19 for(j=1; j<=n; ++j){ //更新其他顶点到起点的距离 20 if(!vist[j] && matrix[pos][j] && minx+matrix[pos][j]<d[j]) 21 d[j] = minx+matrix[pos][j]; 22 } 23 } 24 }
(二)、dijkstra+优先队列
学习完优先队列是很早之前,那个时候merlininice师父坚持要我学习优先队列,其实也就是学习了STL的优先队列的用法而已,不过,真的挺好用的! 而且用在dijkstra算法上我觉得很贴切!
了解了上述的过程,想必你会觉得,咦,每次不是都是寻找距离起点最短的顶点嘛?(从未标记的点中)
Bingo!
因此我们借助priority_queue这个武器不是既优化了时间效率(因为他的查找是O(logn)的),又简洁了代码。
具体如下:
1 vector <int> e[MAXN]; 2 vector <int> l[MAXN]; //用邻接表存储 3 4 void Initial() //邻接表的初始化 5 { 6 for(int i=0; i<MAXN; ++i){ 7 e[i].clear(); 8 l[i].clear(); 9 } 10 } 11 12 struct node //dis代表顶点标号为id的点到起点的距离 13 { 14 int dis, id; 15 bool operator < (const node &x) const{ //重载小于号 16 return dis>x.dis; 17 } 18 }; 19 20 int dij(int n) 21 { 22 priority_queue<node>que; //创建优先队列 23 int i, j, dis, id; 24 node t, nt; 25 26 for(i=1; i<=n; ++i){ //d是核心! 27 vist[i] = false; //vist代表顶点有没有被访问过 28 d[i] = inf; 29 } 30 31 for(i=0; i<e[1].size(); ++i){ //将与起点直接相连的点压入优先队列中 32 t.id = e[1][i]; 33 t.dis = l[1][i]; 34 d[e[1][i]] = t.dis; 35 que.push(t); 36 } 37 38 vist[1] = true; 39 while(!que.empty()){ 40 t = que.top(); //取出于起点距离最小的点 41 id = t.id; 42 dis = t.dis; 43 que.pop(); 44 if(id==n) { //如果已经是我们所要求的终点,返回(这里可做修改而实现其他的功能) 45 return dis; 46 } 47 if(vist[id]) continue; //假如是已经访问过的顶点,则不需要更新其他顶点 48 vist[id] = true; //否则标记并更新其他顶点 49 for(i=0; i<e[id].size();++i){ 50 if(d[id]+l[id][i]<d[e[id][i]]){ 51 d[e[id][i]] = d[id] + l[id][i]; 52 nt.id = e[id][i]; 53 nt.dis = d[e[id][i]]; 54 que.push(nt); 55 } 56 } 57 } 58 }
顺带提及下:在图的问题中,如果是稀疏、顶点数又较多的图,则建议用邻接表存储,防止mle或者数组开不下...
(三)、spfa
求单源最短路的SPFA算法的全称是:Shortest Path Faster Algorithm。
SPFA算法是西南交通大学段凡丁于1994年发表的.
从名字我们就可以看出,这种算法在效率上一定有过人之处。
很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。
简洁起见,我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。
我们用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。我们采取的方法是松弛:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
——摘自百度百科
摘用这段话的目的其实就是为了说明spfa是Shortest Path Faster Algorithm的缩写...(因为之前总是记岔了,被bs了n次...)
spfa也是用队列来维护d数组(一般流行用dis数组~),不同于dijkstra+优先队列,spfa只需要用普通队列来维护即可。 他的原理是每次如果发现起点可以通过其他的点到达该点,并且路径比原路径短的话,就更新该点,并且该点有可能去更新其他的点。那么我们用一个队列将这些可能更新其他点的点保存下来,一直循环更新,直到所有的路径都已经不能被任何点更新了,也就是不存在可以更新其他点的点了,亦即队列为空的时候。
spfa可以解决负权边的问题,这是dijkstra做不到的。(注意,图中肯定是没有负环的,如果存在负环,也就没有最短路这一个说法了~)
1 void spfa() 2 { 3 int i, j, v; 4 queue <int> que; //创建一个普通队列 5 for(i=1; i<=n; ++i){ //初始化 6 d[i] = inf; 7 inque[i] = false; //inque数组标记该点是否在队列中 8 } 9 d[a] = 0; 10 inque[a] = true; 11 que.push(a); //初始化将顶点推入队列中 12 while(!que.empty()){ 13 v = que.front(); //推出队头 14 que.pop(); 15 inque[v] =false; 16 for(i=0; i<e[v].size(); ++i){ //用队头顶点更新其他顶点 17 if(d[e[v][i]] > d[v] +mat[v][e[v][i]]){ 18 d[e[v][i]] = d[v]+mat[v][e[v][i]]; 19 if(!inque[e[v][i]]){ //假如该顶点未在队列中,那么如队列 20 inque[e[v][i]] = true; 21 que.push(e[v][i]); 22 } 23 } 24 } 25 } 26 }
(三)、floyed
floyed算法解决全局问题,其实就是dp的思想,很好理解,代码也很死。 自学之后终于明白为啥师父当年怎么也不肯讲floyed了,因为,真的很简单。
算法的核心在于:寻找一个中转站,也就是i->j的最短路径必然是:Vi->....->Vj(....也可能是空),我们只要每次拿其他的点来更新i到j的路径就好了。
1 void floyd(int n) 2 { 3 int i, j, k, tmp; 4 5 for(i=1; i<=n; ++i) 6 for(j=1; j<=n; ++j) 7 path[i][j] = j; 8 9 for(k=1; k<=n; ++k) //k是中转站,并且一定要放在外层! 10 for(i=1; i<=n; ++i) 11 for(j=1; j<=n; ++j){ 12 if(d[i][k]!=-1 && d[k][j]!=-1){ 13 tmp = d[i][k] + d[k][j] + tax[k]; 14 if(tmp<d[i][j] || d[i][j]==-1){ 15 d[i][j] = tmp; 16 path[i][j] = path[i][k]; 17 } 18 if(tmp==d[i][j] && path[i][j]>path[i][k]) 19 path[i][j] = path[i][k]; 20 } 21 } 22 }
浙公网安备 33010602011771号