所以当队列实现时,BFS的复杂度为O(| V | + | E |):
ENQUEUE(Q,source_vertex)
while Q != NULL
u=DEQUEUE(Q)
for each v in AdjacencyList[u]
if v not yet visited
Make v visited
ENQUEUE(Q,v)
如果我修改代码,将u的邻接列表中的所有顶点添加到队列中,如下所示:
ENQUEUE(Q,source_vertex)
while Q != NULL
u=DEQUEUE(Q)
for each v in AdjacencyList[u]
if v not finalized
ENQUEUE(Q,v)
make u finalized
运行时间是否仍为O(| V | + | E |)?
提前致谢。
答案 0 :(得分:3)
想象一下你有一个n个节点的集团(让我们将它们编号为1,2,...,n并假设邻接列表按此顺序存储它们)并从节点1开始运行修改后的算法。节点1将入队节点2,3,...,n用于总共Θ(n)的工作。队列现在看起来像这样:
2,3,4,...,n
当您处理节点2时,它会查看其所有边缘的Θ(n)更多工作,然后将队列中的节点3,4,5,...,n排队。我们的队列现在看起来像这样:
3,4,5,...,n,3,4,5,...,n
我们现在处理节点3,它查看其所有边缘的Θ(n)工作,然后将节点的4,5,6,...,n排队,所以我们的队列如下所示:
4,5,6,...,n,3,4,5,...,n,4,5,6,...,n
这里的模式是我们最终将图中每个节点的许多副本排入队列。实际上,随着时间的推移,我们最终会在队列中总共有Θ(n 2 )总节点,并且我们每个节点执行Θ(n)工作。这意味着该图完成的总工作量为Θ(n 3 ),超过了原始BFS实现的O(m + n)时间限制。
因此,这个新实现可能比常规BFS渐近地慢。
答案 1 :(得分:1)
让我们更改你的算法以向队列添加边缘(因为这是隐含的你正在做的事情 - 除了你只是在将它添加到队列而不是整个边缘时查看相反的顶点):
ENQUEUE( Q,(NULL->source_vertex) ) # start with a dummy edge
WHILE Q != NULL
(s->u)=DEQUEUE(Q)
for each (u->v) in AdjacencyList[u]
if v not finalized
ENQUEUE(Q,(u->v))
make u finalized
每个边缘将被视为(u->v)
和(v->u)
两次排队。当访问边u
的第一个顶点时,每个相邻边将被放入队列,然后u
完成。访问v
时,将考虑边(v->u)
,但由于u
已经完成,因此不会添加到队列中;所以每个边缘只会排队一次(在一个方向但不在另一个方向)。
该算法的问题在于它不会检查它要处理的顶点是否已经完成,并将为队列中的每个边缘重新处理它,再次迭代所有相邻边缘,使您的算法成为O(|V||E|)
(而不是O(|V| + |E|)
)。
一个简单的解决方法是:
ENQUEUE( Q,(NULL->source_vertex) ) # start with a dummy edge
WHILE Q != NULL
(s->u)=DEQUEUE(Q)
if u not finalized
for each (u->v) in AdjacencyList[u]
if v not finalized
ENQUEUE(Q,(u->v))
make u finalized
此外,算法的两个版本都将从一个顶点开始,然后处理同一个连接组件中的每个边缘 - 但是它们只会在连接的图形上执行完整的BFS。如果您有多个断开连接的组件,则需要使用:
for each source_vertex in Graph
if source_vertex not visited/finalised
call your_algorithm( source_vertex )
然后O(|V| + |E|)
将会访问所有顶点,无论图表是否已连接。
答案 2 :(得分:0)
据我了解,运行时复杂性也是O(|V|+|E|)
。第二个版本只是将 finalization 步骤(也可以称为访问)推迟到下一次迭代;递归调用的数量和顶点序列都没有改变。