我们来说一下图论的最短路问题
这一部分主要有三种做法:Bellman-Ford算法、Dijkstra算法、Floyd-Warshall算法
我们先来看Ford:
Ford和Dijkstra一样,都是考虑单元最短路问题,我们用一个图来理解一下最短路的思想:
现在我们给这些边赋上权值,我们要找到从左到右的最短路,假设每条边的权值都是1,那么我们能直观地得出它的最短路,是最下面的一条,但是计算机可没有我们这么好用的大脑,我们用计算机的运行方式来模拟一遍:
我们要从左到右找最短路,以左端点为起点,向上走,接着走上方的边,走到右端点回溯,得到的路径长度是4;
重新以左端点为起点,向上走,转向向下的边,接着走下方的边,走到右端点回溯,得到的路径长度是4;
重新以左端点为起点,向下走,接着走下方的边,走到右端点回溯,得到的路径长度是4;
比较三段路径长度,得到最短路是3。
懂得了计算最短路的方法以后,我们开始看ford算法的精髓:
记从起点S出发到顶点i的最短距离为d[i],则下列等式成立。
d[i]=min{d[j]+(i到j的权值)|e=(j,i)∈E}
即,d[i]=min(d[i],d[j]+e,cst[j]);
如果给定的图是一个DAG,就可以按拓扑序给顶点编号,并利用这条递推关系式计算出d。但是,如果图中有环,就无法依赖这样的顺序进行计算。在这种情况下,记当前到顶点i的最短路长度为d[i],并设处置d[s]=0,d[i]=INF,在再断使用这条递推关系式更新d的值,就可以计算出新的d。只要图中不存在负环,这样的更新操作就是有限的,也就是说,这种更新操作一定是有终点的。结束之后的d就是所求的最短距离了。
struct edge{int frm,to,cst;}es[MAXN];
int d[MAXN];
int v,e;
void Bellman-Ford(int s){
for(int i=0;i<v;i++) d[i]=INF;
d[s]=0;//起点到本身的最短路为0
while(true){
bool updt=false;
for(int i=0;i<e;i++){
edge eg=es[i];
if(d[eg.frm]!=INF && d[eg.to]>d[eg.frm]+eg.cst){
d[eg.to]=d[eg.frm]+eg.cst;
updt=true;
}
}
if(!updt) break;
}
}
看懂了这段代码,我们就可以考虑刚才说过的一个概念:对d[i]的更新操作,终点是什么?
根据这段代码的原理我们可以看出,如果在图中不存在从S可达的负环,那么最短路不会经过一个顶点两次,也就是说,while循环最多执行|V|-1次,即最短路最多经过|V|-1条边。如果存在从S可达的负环,那么在第|V|次操作中也会更新d的值,因此,可以用这个性质来检验图中是否存在负环。如果一开始对于所有的顶点i,都把d[i]初始化城0,那么可以检查出图中所有的负环。
bool find_nagtive_loop(){
memset(d,0,sizeof(d));
for(int i=0;i<=v;i++) for(int j=0;j<e;j++){
edge eg=es[j];
if(d[eg.to]>d[eg.frm]+eg.cst){d[eg.to]=d[eg.frm]+eg.cst;if(i==v-1) return true;}
//如果第n次更新了d的值,则存在负环
}
return false;
}
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
(人造分隔线)