classic algorithm - decoqt/mywiki GitHub Wiki
#经典算法
##目录
- 图的存储结构
-
图的遍历
2.1 BFS 2.2 DFS -
最小生成树
3.1 prime 3.2 kruskal -
最短路径
4.1 Dijkstra 4.2 Floyd 4.3 Bellman-Ford 4.4 SPFA - 拓扑排序
- 关键路径
- 小结
- kmp
###邻接矩阵 适合稠密图
#define MaxVertices 100
typedef struct {
Vertextype vex[MaxVertices];
int arc[MaxVertices][MaxVertices];
int vexnum,arcnum;
}MGraph;
###邻接表 适用于稀疏图
#define MaxVertices 100
typedef struct {
int adjvex;
//Weighttype cost ;
struct ArcNode *nextarc;
}ArcNode;
typedef struct{
VertexType data;
ArcNode *firstarc;
}Vnode,AdjList[Maxvertices];
tyepdef struct{
int vexnum,arcnum;
Adjlist vertices;
}AlGraph;
###邻接多重表 领接表在做边处理的时候,不够方便,无向图使用领接多重表
#define MaxVertices 100
enum VisitIf{unvisited,visited};
tyepdef struct EBox {
VisitIf mark; // 访问标记
int ivex,jvex; // 该边依附的两个顶点的位置
EBox *ilink,*jlink; // 分别指向依附这两个顶点的下一条边
InfoType *info; // 该边信息指针
}EBox;
typedef struct VexBox{
VertexType data;
EBox *firstedge; // 指向第一条依附该顶点的边
}VexBox;
typedef struct {
VexBox adjmulist[MaxVertices];
int vexnum,edgenum; // 无向图的当前顶点数和边数
}AMLGraph;
###十字链表 领接表在做边处理的时候,不够方便,有向图使用十字链表
#define MaxVertices 100
tyepdef struct ArcBox {
int tailvex,headvex; // 该边依附的两个顶点的位置
struct ArcBox *hlink,*tlink; // 分别为弧头相同和弧尾相同的弧的链域
InfoType *info; // 该边信息指针
}Arcbox;
typedef struct VexNode{
VertexType data;
Arcbox *firstin,firstout; // 指向第一条依附该顶点的边
}VexNode;
typedef struct {
VexNode xlist[MaxVertices];
int vexnum,edgenum; // 无向图的当前顶点数和边数
}OLGraph;
##图的遍历 从图中某一个顶点出发,某种搜索方式访问其余顶点,且每个顶点只被访问一次
复杂度为O(V+E)
###BFS ####目的 这种搜索方法尽可能“广”的搜索一个图####算法 对于每一个图G=(V,E),首先访问某个指定的起始顶点s,然后依次访问与s邻接且未被访问过的顶点w,t。。。;然后再依次访问与w邻接且未被访问的顶点。。。重复
void BFSTraverse(Graph G)
{
for (i = 0; i<G.vexnum; i++)
visited[i] = 0;
for(i=0; i<G.vexnum;i++){
if(!visited[i])
BFS(G,i);
} }
-
邻接矩阵储存:
void BFS_M(Graph G,int i) { visit(i); visited[i] = 1; InitQueue(Q); EnQueue(Q,i); while(Is_Empty(Q)){ DeQueue(Q,i); for (j =0; j < G.vexnum; j++) if(!visited[j]&&G.arc[i][j] == 1){ visited(j); visited[j] = 1 EnQueue(Q,j) } } }
-
邻接表储存
void BFS_L(Graph G,int i) { visit(i); visited[i] = 1; InitQueue(Q); EnQueue(Q,i); while(Is_Empty(Q)){ DeQueue(Q,i); for (ArcNode *p = G.verticles[i].firstarc;p;p = p->nextarc){ int w = p->adjvex; if(!visited[w]){ visit(w); visited[w] = 1 EnQueue(Q,w); } } } }
####分析 基于邻接矩阵得到的生成树是唯一的 ####代码实现
###DFS: ####目的 这种搜索方法尽可能“深”的搜索一个图
####算法 对于每一个图G=(V,E),首先访问某个指定的起始顶点s,然后访问与s邻接且未被访问过的顶点w,再访问顶点w未被访问的顶点t,。。。直到不能访问为止
void DFSTraverse(Graph G)
{
for (i = 0; i<G.vexnum; i++)
visited[i] = 0;
for(i=0; i<G.vexnum;i++){
if(!visited[i])
DFS(G,i);
} }
-
邻接矩阵储存:
void DFS_M(Graph G,int i) { visit(i); visited[i] = 1; for (j =0; j < G.vexnum; j++){ if(!visited[j]&&G.arc[i][j] == 1) DFS_M(G,j) } }
-
邻接表储存
void DFS_L(Graph G,int i) { visit(i); visited[i] = 1; for (ArcNode *p = G.verticles[i].firstarc;p;p = p->nextarc){ int w = p->adjvex; if(!visited[w]) DFS_L(G,w); } }
####分析 基于邻接矩阵得到的生成树是唯一的
##最小生成树 生成树是连通图的极小连通子图,权值最小的生成树成为最小生成树
权值互不相等的时候得到的最小生成树唯一
###Prime ####目的 获得有权图的最小生成树 ####算法: 通过每次加一条边的方式
void(Graph G,Tree T){
T={};U={w};
while(!(V-U))
if(weight(u,v) is minimum among u belongs U,and v belongs(V-U)){
T = T plus {(u,v)}
U= U plus v
}
}
####分析 时间复杂度为O(V^2),与边无关,适合稠密图 ####代码实现:
###Kruskal: ####目的 从选择权值最小的边开始####算法
void Kruskal(Graph G,Tree T)
{
T = V;
numS = n;//不连通分量
while(numS>1){
select minimum weight (u,v) from E;
if(u,v belongs different connected components){
T = T plus (u,v);
numS--;
}
}
}
####分析 算法复杂度O(ElogE),只与边有关,适合稀疏图 ####代码实现:
##最短路径 带权有向图最短路径问题###Dijkstra:####目的 求单源、无负权的最短路。时效性较好
####算法 Dijkstra 算法的输入包含了一个有权重的有向图 G,以及G中的一个来源顶点 S。我们以 V 表示 G 中所有顶点的集合,以 E 表示G 中所有边的集合。 (u, v) 表示从顶点 u 到 v 有路径相连,而边的权重则由权重函数 w: E → [0, ∞] 定义。因此,w(u, v) 就是从顶点 u 到顶点 v 的非负花费值(cost),边的花费可以想像成两个顶点之间的距离。任两点间路径的花费值,就是该路径上所有边的花费值总和。
已知有 V 中有顶点 s 及 t,Dijkstra 算法可以找到 s 到 t 的最低花费路径(例如,最短路径)。这个算法也可以在一个图中,找到从一个顶点 s 到任何其他顶点的最短路径。
Dijkstra 算法的实现一(维基百科):
u := Extract_Min(Q) 在顶点集合 Q 中搜索有最小的 d[u] 值的顶点 u。 这个
顶 点被从集合 Q 中删除并返回给用户。
function Dijkstra(G, w, s)
for each vertex v in V[G] // 初始化
d[v] := infinity
previous[v] := undefined
d[s] := 0
S := empty set
Q := set of all vertices
while Q is not an empty set // Dijkstra演算法主體
u := Extract_Min(Q)
S := S union {u}
for each edge (u,v) outgoing from u
if d[v] > d[u] + w(u,v) // 拓展边(u,v)
d[v] := d[u] + w(u,v)
previous[v] := u
如果我们只对在 s 和 t 之间寻找一条最短路径的话,我们可以在第9行添加条件如果满足 u = t 的话终止程序。现在我们可以通过迭代来回溯出 s 到 t 的最短路径:
s := empty sequence
u := t
while defined u
insert u to the beginning of S
u := previous[u]
现在序列 S 就是从 s 到 t 的最短路径的顶点集.
Dijkstra 算法的实现二(算法导论):
DIJKSTRA(G, w, s)
INITIALIZE-SINGLE-SOURCE(G, s)
S ← Ø
Q ← V[G] //V*O(1)
while Q ≠ Ø
do u ← EXTRACT-MIN(Q) //EXTRACT-MIN,V*O(V),V*O(lgV)
S ← S ∪{u}
for each vertex v ∈ Adj[u]
do RELAX(u, v, w) //松弛技术,E*O(1),E*O(lgV)。
因为Dijkstra算法总是在V-S中选择“最轻”或“最近”的顶点插入到集合S中,所以我们说它使用了贪心策略。 ####分析
-
Dijkstra 算法最简单的实现方法是用一个链表或者数组来存储所有顶点的集合 Q,所以搜索 Q 中最小元素的运算(Extract-Min(Q))只需要线性搜索 Q 中的所有元素。这样的话算法的运行时间是 O(E^2)。
-
对于边数少于 E^2 的稀疏图来说,我们可以用邻接表来更有效的实现迪科斯彻算法。同时需要将一个二叉堆或者斐波纳契堆用作优先队列来寻找最小的顶点(Extract-Min)。
-
当用到二叉堆时候,算法所需的时间为O(( V+E )logE),斐波纳契堆能稍微提高一些性能,让算法运行时间达到O(V+ElogE)。
结论:
- 时间复杂度为O(V*V+E)
- 当是稀疏图的情况时,此时E=V*V/lgV,所以算法的时间复杂度可为O(V^2) 。
- 若是斐波那契堆作优先队列的话,算法时间复杂度,则为O(V*lgV + E)。
- 源点可达的话,O(V*lgV+E*lgV)=>O(E*lgV)。
####代码实现
###Floyd: ####目的 求多源、有负权边(无负权边的回路)的最短路用矩阵记录图。 Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题。
####算法 Floyd-Warshall的原理是动态规划: 设Di,j,k为从i到j的只以(1..k)集合中的节点为中间节点的最短路径的长度。 若最短路径经过点k,则Di,j,k = Di,k,k-1 + Dk,j,k-1; 若最短路径不经过点k,则Di,j,k = Di,j,k-1。 因此,Di,j,k = min(Di,k,k-1 + Dk,j,k-1 , Di,j,k-1)。 在实际算法中,为了节约空间,可以直接在原来空间上进行迭代,这样空间可降至二维。 Floyd-Warshall算法的描述:
for k ← 1 to n do
for i ← 1 to n do
for j ← 1 to n do
if (Di,k + Dk,j < Di,j) then
Di,j ← Di,k + Dk,j;
其中Di,j表示由点i到点j的代价,当Di,j为 ∞ 表示两点之间没有任何连接。 经过n次迭代后的矩阵就保存了任意一对顶点之间的最短路径。 ####分析 Floyd-Warshall算法的时间复杂度为O(V^3),空间复杂度为O(V^2)。
####代码实现
####Bellman-Ford ####目的 求单源最短路,可以判断有无负权回路(若有,则不存在最短路), 时效性较好,时间复杂度O(VE)。Bellman-Ford算法是求解单源最短路径问题的一种算法。 单源点的最短路径问题是指: 给定一个加权有向图G和源点s,对于图G中的任意一点v,求从s到v的最短路径。 与Dijkstra算法不同的是,在Bellman-Ford算法中,边的权值可以为负数。 设想从我们可以从图中找到一个环路(即从v出发,经过若干个点之后又回到v)且这个环路中所有边的权值之和为负。那么通过这个环路,环路中任意两点的最短路径就可以无穷小下去。如果不处理这个负环路,程序就会永远运行下去。 而Bellman-Ford算法具有分辨这种负环路的能力。
####算法 Bellman-Ford 算法的具体描述
BELLMAN-FORD(G, w, s)
INITIALIZE-SINGLE-SOURCE(G, s) //对每个顶点初始化 ,O(V)
for i ← 1 to |V[G]| - 1
do for each edge (u, v) ∈ E[G]
do RELAX(u, v, w) //针对每个顶点(V-1个),都运用松弛技术O(E),计为O((v-1)*E))
for each edge (u, v) ∈ E[G]
do if d[v] > d[u] + w(u, v)
then return FALSE //检测图中每条边,判断是否包含负权回路,
//若d[v]>d[u]+w(u,v),则表示包含,返回FALSE,
return TRUE //不包含负权回路,返回TRUE
####分析 Bellman-Ford 算法的时间复杂度,由上可得为O(V*E)。
####算法实现
##拓扑排序 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若 ∈E(G),则u在线性序列中出现在v之前。 满足拓扑次序(Topological Order)的序列,简称拓扑序列 从一个有向无回路图(AOV)中找出这样一个拓扑序列称之为拓扑排序 ###算法 AOV网排序过程:
- 从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它.
- 从网中删去该顶点,并且删去从该顶点发出的全部有向边.
- 重复上述两步,直到剩余的网中不再存在没有前趋的顶点为止.
###分析 一个点若是有多个后继,那么拓扑序列可能不唯一; 算法复杂度为O(n+e)
###代码实现
#include "stdio.h"
#define MAX_VERTEX_NUM 20
#include "conio.h"
#include "stdlib.h"
#define STACK_INIT_SIZE 16
#define STACKINCREMENT 5
typedef int SElemType;
typedef char VertexType;
typedef struct{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
//我们依然用邻接表来作图的存储结构
typedef struct ArcNode{
int adjvex;
struct ArcNode *nextarc;
int info;
}ArcNode; //表结点类型
typedef struct VNode{
VertexType data;
int count;
ArcNode *firstarc;
}VNode,AdjList[MAX_VERTEX_NUM]; //头结点
typedef struct{
AdjList vertices; //邻接表
int vexnum,arcnum;
}ALGraph;
int InitStack(SqStack &S)
{
S.base=(SElemType*)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!S.base) exit(-1);
S.top=S.base;
S.stacksize=STACK_INIT_SIZE;
return 1;
}//InitStack
int Push(SqStack &S,SElemType e)
{
if((S.top-S.base)>=S.stacksize){
S.base=(SElemType*)realloc(S.base,(S.stacksize+STACKINCREMENT)*sizeof(SElemType));
if(!S.base) exit(-1);
S.top=S.base+S.stacksize;
S.stacksize+=STACKINCREMENT;
}//if
*(S.top++)=e;
return 1;
}//Push
int Pop(SqStack &S,SElemType &e)
{
if(S.top==S.base) return 0;
e=*(S.top--);
return 1;
}//Pop
int StackEmpty(SqStack &S)
{
if(S.top==S.base) return 1;
else return 0;
}//StackEmpty
int LocateVex(ALGraph G,char u)
{
int i;
for (i=0;i<G.vexnum;i++){
if(u==G.vertices[i].data)
return i;
}
if (i==G.vexnum)
printf("Error u!\n");exit(1);
return 0;
}
void CreateALGraph_adjlist(ALGraph &G)
{
int i,j,k,w;
char v1,v2,enter;
ArcNode *p;
printf("Input vexnum & arcnum:\n");
scanf("%d",&G.vexnum);
scanf("%d",&G.arcnum);
printf("Input Vertices(以回车隔开各个数据):\n");
for (i=0;i<G.vexnum;i++){
scanf("%c%c",&enter,&G.vertices[i].data);//注意点
G.vertices[i].firstarc=NULL;
}//for
printf("Input Arcs(v1,v2,w)以回车分开各个数据:\n");
for (k=0;k<G.arcnum;k++){
scanf("%c%c",&enter,&v1);
scanf("%c%c",&enter,&v2);
i=LocateVex(G,v1);
j=LocateVex(G,v2);
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=j;
p->nextarc=G.vertices[i].firstarc; //前插法,即每次都插入到头结点的后面
G.vertices[i].firstarc=p;
printf("Next\n");
}//for
return;
}//CreateALGraph_adjlist
void FindInDegree(ALGraph &G)
{
int i,j;
for(i=0;i<G.vexnum;i++){
G.vertices[i].count=0;
}//for
for(j=0;j<G.vexnum;j++){
for(ArcNode *p=G.vertices[j].firstarc;p;p=p->nextarc)
G.vertices[p->adjvex].count++;
}//for
}//FindInDegree
int TopoSort(ALGraph &G)
{
SqStack S;
FindInDegree(G);
InitStack(S);
for(int i=0;i<G.vexnum;i++)
if(G.vertices[i].count==0)
Push(S,i);
int countt=0;
while(!StackEmpty(S)){
int i,m;
m=Pop(S,i);
printf(" %c",G.vertices[i].data); ++countt;
for(ArcNode *p=G.vertices[i].firstarc;p;p=p->nextarc){
int k=p->adjvex;
if(!(--G.vertices[k].count))
Push(S,k);
}//for
}//while
if(countt<G.vexnum)
return false;
else
return true;
}//TopoSort
int TopoSort_DFS(ALGraph &G)
{
SqStack S;
InitStack(S);
for(int i=0;i<G.vexnum;i++){
visited[i] = 0;
}
for(int i=0;i<G.vexnum;i++)
if(!visited[i])
DFS_L(G,i)
int countt=0;
while(!StackEmpty(S)){
Pop(S,i);printf(" %c",G.vertices[i].data); ++countt;
}//while
if(countt < G.vexnum) return false;
else return true;
}//TopoSort_DFS
void DFS_L(Graph G,int i)
{
visited[i] = 1;
for (ArcNode *p = G.verticles[i].firstarc;p;p = p->nextarc){
int w = p->adjvex;
if(!visited[w])
DFS_L(G,w);
}
push(S,i);
}
int main()
{
ALGraph G;
CreateALGraph_adjlist(G);
if(TopoSort(G))
return true;
}
##关键路径
用顶点表示事件,弧表示活动,弧上的权值表示活动持续的时间的有向图叫AOE(Activity On Edge Network)网
AOE 网具有的性质
- 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始。
- 只有在进入某一顶点的各有向边所代表的活动都已经结束,该顶点所代表的事件才能发生。表示实际工程计划的AOE网应该是无环的,并且存在唯一的入度过为0的开始顶点和唯一的出度为0的完成顶点
最早发生时间和最晚发生时间的定义
可以采取如下步骤求得关键活动:
-
从开始顶点 v 1 出发,令 ve(1)=0,按拓朴有序序列求其余各顶点的可能最早发生时间。 Ve(k)=max{ve(j)+dut(<j,k>)} j ∈ T 其中T是以顶点vk为尾的所有弧的头顶点的集合(2 ≤ k ≤ n)。 如果得到的拓朴有序序列中顶点的个数小于网中顶点个数n,则说明网中有环,不能求出关键路径,算法结束。
-
从完成顶点 v n 出发,令vl(n)=ve(n),按逆拓朴有序求其余各顶点的允许的最晚发生时间: vl(j)=min{vl(k)-dut(<j,k>)} k ∈ S 其中 S 是以顶点vj是头的所有弧的尾顶点集合(1 ≤ j ≤ n-1)。
-
求每一项活动ai(1 ≤ i ≤ m)的最早开始时间e(i)=ve(j);最晚开始时间: l(i)=vl(k)-dut(<j,k>) 若某条弧满足 e(i)=l(i) ,则它是关键活动。
-
从开始顶点到达完成顶点的所有路径都是关键路径。一个AOE网的关键路径可以不止一条
###目的 AOE网研究的问题
- 完成整个工程至少需要多少时间;
- 哪些活动是影响工程的关键。
###算法 关键路径的算法
- 输入e条弧<j,k>;,建立AOE网的存储结构。
- 从源点v1出发,令ve(1)=0,求 ve(j) 2<=j<=n。
- 从汇点vn出发,令vl(n)=ve(n),求 vl(i) 1<=i<=n-1。
- 根据各顶点的ve和vl值,求每条弧s(活动)的最早开始时间e(s)和最晚开始时间l(s)
- 其中e(s)=l(s)的为关键活动,所有d()=0的活动构成关键路径 ###分析 算法时间复杂度为O(V+E)
- 求关键路径必须在拓扑排序的前提下进行,有环图不能求关键路径
- 只有缩短关键活动的工期才有可能缩短工期;
- 若一个关键活动不在所有的关键路径上,减少它并不能减少工期;
- 只有在不改变关键路径的前提下,缩短关键活动才能缩短整个工期。
###实现
//S is vertices of 0 degree ;T stands for vertices
int ToplogicalOrder(ALGraph &G,Stack &T)
{
SqStack S;
FindInDegree(G);
InitStack(S);
ve[0..G.vexnum-1]=0;//**changes: calculate ve[k]**
for(int i=0;i<G.vexnum;i++)
if(G.vertices[i].count==0)
Push(S,i);
int countt=0;
while(!StackEmpty(S)){
Pop(S,j);Push(T,j);++countt;
for(ArcNode *p=G.vertices[j].firstarc;p;p=p->nextarc){
int k=p->adjvex;
if(!(--G.vertices[k].count))
Push(S,k);
if(ve[j]+*(p->info)>ve[k])
ve[k]=ve[j]+*(p->info);
}//for *(p->info)=dut(<j,k>)
}//while
if(countt<G.vexnum)
return false; // 该有向网有回路
else
return true;
}//TopoSort
status CriticalPath(ALGraph G)
{
Stack T;
inta,j,k,el,ee,dut;
chartag;
if(!ToplogicalOrder(G,T)) return ERROR;
vl[0..G.vexnum-1]=ve[G.vexnum-1];
while(!StackEmpty(T))
for(Pop(T,j),p=G.vertices[j].firstarc;p;p=p->nextarc){
k=p->adjvex; dut=*(p->info);
if(vl[k]-dut<vl[j])
vl[j]=vl[k]-dut;
}
for(j=0;j<G.vexnum;++j)
for(p=G.vertices[j].firstarc;p;p=p->nextarc){
k=p>adjvex; dut=*(p->info);
ee=ve[j]; el=vl[k];
tag=(ee==el)?’*’:’’;
printf(j,k,dut,ee,el,tag);
}
}
##小结
more info in blog
仔细观察可以发现,DFS、BFS、Prim算法和Dijkstra算法,这4种图搜索算法都具有相同的模式
- 它们都遍历了所有顶点,并且在遍历的过程中维护了一个顶点集,搜索一个顶点时把相邻的顶点按照某种规则加入到这个集合中,搜索下一个顶点时再按照某种规则从这个顶点集中取出顶点;
- 取出顶点的规则是第二个共同点——它们都是贪心算法,在选择下一个顶点的时刻都依据某种优先级,选择优先级最大的顶点。
换言之,其实都是优先级优先搜索(priority-first search,PFS),它们依赖预定义的优先级执行搜索,而存放顶点的顶点集就是广义优先队列(priority queue,PQ)。显然,搜索算法的效率严重依赖于优先队列的操作效率。 既然这4种图搜索算法都有共同的思想,理所当然的,它们具有共同的代码接口(PQ函数调用包含与优先队列相关的操作):
void GraphPFS(Graph G, int src)
{
PQinit(G, src);
while (!PQempty()) {
int v = PQdelmax(); // 取出优先级最大的顶点
Update(v);
Link t;
for (t = G->adj[v]; t != NULL; t = t->next) {
if (ShouldInsert(t))
PQinsert(t, v); // 顶点t->v加入优先队列
else if (ShouldChange(t))
PQchange(t, v); // 改变顶点t->v的优先级
}
}
}
function | BFS | DFS | prime | Dijkstra |
---|---|---|---|---|
PQinit | ||||
PQempty | QueueEmpty | StackEmpty | ||
PQdelmax | QueueGet | Pop | ||
Update | ||||
ShouldInsert | pl[t->v] == -1 | |||
PQinsset | Push | |||
ShouldChange | 0 | |||
PQchange |
##KMP:目的:算法:分析:代码实现:
##SPFA 是Bellman-Ford的队列优化,时效性相对好,时间复杂度O(kE)。(k<<V)。与Bellman-ford算法类似,SPFA算法采用一系列的松弛操作以得到从某一个节点出发到达图中其它所有节点的最短路径。所不同的是,SPFA算法通过维护一个队列,使得一个节点的当前最短路径被更新之后没有必要立刻去更新其他的节点,从而大大减少了重复的操作次数。 SPFA算法可以用于存在负数边权的图,这与dijkstra算法是不同的。与Dijkstra算法与Bellman-ford算法都不同,SPFA的算法时间效率是不稳定的,即它对于不同的图所需要的时间有很大的差别。 在最好情形下,每一个节点都只入队一次,则算法实际上变为广度优先遍历,其时间复杂度仅为O(E)。另一方面,存在这样的例子,使得每一个节点都被入队(V-1)次,此时算法退化为Bellman-ford算法,其时间复杂度为O(VE)。 SPFA算法在负边权图上可以完全取代Bellman-ford算法,另外在稀疏图中也表现良好。但是在非负边权图中,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法,以及它的使用堆优化的版本。通常的SPFA算法在一类网格图中的表现不尽如人意。