一. BFS算法
BFS算法是基于图的广度优先遍历来求解指定节点到其余各个节点的最短路径问题,其基本思想在于,根据广度优先遍历先确定与目标节点直接相连的节点,目标节点到这些节点的路径长度为1,之后,再对与之相邻的节点进行相同的操作,具体实现的伪代码如下:
void BFS_Min_Distance(Graph G,int i){//求解节点i到其他节点的单源最短路径
for(int i=0;i<G.MAX_NUM;i++){
path[i] = -1;//初始化path数组存储节点的的来源
d[i] = ∞;//初始化路径长度
}
d[i] = 0;
visited[i] = true;
EnQueue(Q,i);
while(!isEmpty(Q)){
DeQueue(Q,i);
for(w = FirstNeighbor(G,i);w>0;w = NextNeighbor(G,i,w)){
if(!visited[w]){
path[w] = i;
d[w] = d[i]+1;
visited[w] = true;
EnQueue(w);
}//end if
}
}//end while
}
在这段代码中,我们初始化了两个数组,分别用于存储目标节点到其余节点的最短路径长度,和其余节点的直接前驱,思想同图的BFS遍历,我们同样利用了辅助队列来完成,该算法的时间开销主要来源于对图中节点的访问以及对边的探索,具体又分为两种情况:
1. 如果图的存储模式基于邻接矩阵,对于节点的访问需要O(V)的时间复杂度,在每个节点上,对于边的访问需要O(V)的时间复杂度,因此总的时间复杂度是O(V2)
2. 如果图的存储模式基于邻接表,对于节点的访问需要O(V)的时间复杂度,对于边的访问仅仅需要访问所有的边节点,即O(2E)或O(E)的时间复杂度(这里分别对应于无向图和有向图),因此,总的时间复杂度为O(V+E)
但是,BFS算法也有相应的缺点,它仅仅适用于无权图,或者所有边权值都相同的图
二. Dijkstra算法
简单的说,Dijkstra算法起始集合中只存放目标节点,之后,这个集合会不断的并入新的节点,每并入一个节点,都会修改目标节点到各个节点的最短路径信息,每次循环遍历所有的节点,找到还没有确定最短路径,且dist最小的顶点。在构造的过程中,使用了如下的辅助数组:
1. final[]:存储bool值,表示各个节点是否已找到最短路径,之后每并入一个节点,会不断更新
2. dist[]:存放目标节点到各个节点的当前的最短路径长度,之后每并入一个节点,会不断更新
3. path[]:同BFS算法,表示从目标节点到当前节点之间的最短路径的前驱节点,之后每并入一个节点,会不断更新。其作用在于,当我们找出最短路径后,可根据该数组回溯找到最短路径
在手算练习中需要注意的是,每并入一个节点后,只需要分析目标节点与新并入的这个节点相连的节点路径是否发生变化,并且相邻的节点的final值如果为true,也不需要再考虑,不需要从头分析一遍,例题如下:
该算法的时间复杂度:每有一个节点并入集合后,都需要遍历该节点,找到还没有确定最小路径,而且dist最小的节点。对比所连接的节点与目标节点的最短路径,而并入节点的操作需要进行V-1次,因此总的时间复杂度为O(V2)
三. Floyd算法
Floyd算法使用的是动态规划的思想,将问题的求解分为多个阶段,一般使用邻接矩阵的方式存储。需要使用辅助数组:
path[][]:表示节点i和节点j之间的中转节点
1. 阶段一:自食其力阶段
就是说一个节点想要到达另一个节点,只能通过自身与另一个节点是否直接相连,不能借助中间节点,如果该节点不能直接到达另一个节点,那么两个节点间的路径长度设为∞
2. 阶段二:求亲靠友阶段
这个意思就是:Floyd算法是一个迭代的过程,每迭代一次,在从vi到vj节点的路径上就多考虑了一个节点,如果vi无法直接连接vj,那么可以靠这个中间节点vw,vi→vj→vw
具体手算案例如下:
核心部分代码段如下:
for(int k=0;k<G.MAX_NUM;k++){//依次选取中转节点
for(int i=0;i<G.MAX_NUM;i++){
for(int j=0;j<G.MAX_NUM;j++){
if(A[i][j]<A[i][k]+A[k][j]){
A[i][j] = A[i][k]+A[k][j];
path[i][j] = k;//i和j之间的中转节点为k
}
}
}
}
时间复杂度:O(V3)(很显然,代码里面有三个嵌套for循环)