出队时在 BFS 上将节点标记为已访问
Posted
技术标签:
【中文标题】出队时在 BFS 上将节点标记为已访问【英文标题】:marking node as visited on BFS when dequeuing 【发布时间】:2018-01-19 07:03:10 【问题描述】:只是一个简单而愚蠢的问题,关于图上的 BFS 遍历
我在许多网站上发现 BFS 的伪代码几乎是这样的:
BFS (Graph, root):
create empty set S
create empty queue Q
add root to S //mark as visited here
Q.enqueue(root)
while Q is not empty:
current = Q.dequeue()
if current is the goal:
return current
for each node n that is adjacent to current:
if n is not in S:
add n to S //mark as visited here
Q.enqueue(n)
我只是发现在出队后将给定节点标记为已访问比在入队时更简单,因为在后面的方法中我们需要编写一个额外的步骤。我知道这不是一件大事,但我想将节点标记为在一个地方而不是两个地方访问更有意义。更简洁,更容易记住,甚至更容易学习。
修改后的版本是这样的:
BFS (Graph, root):
create empty set S
create empty queue Q
Q.enqueue(root)
while Q is not empty:
current = Q.dequeue()
add current to s //just add this and remove the other 2 lines
if current is the goal:
return current
for each node n that is adjacent to current:
if n is not in S:
Q.enqueue(n)
我只想知道这个改变是否正确,据我所知这根本不应该改变遍历的行为,是吗?
【问题讨论】:
【参考方案1】:等待将顶点标记为已访问直到出队之后将改变 BFS 的行为。考虑下图:
A
/ \
/ \
B C
\ /
\ /
D
/|\
/ | \
E F G
在每一步,Q
和 S
将如下所示:
Q S
===== =======
A
B,C A
C,D A,B
D,D A,B,C // dequeue C; D is not in the visited list, so enqueue D again
如您所见,我们有两次D
在队列中。 D
的所有孩子也将被添加到队列中两次...
Q S
============= ========
D,E,F,G A,B,C,D
E,F,G,E,F,G A,B,C,D
...等等。如果队列中的另外两个节点再次指向同一个(未访问的)节点,您将获得更多重复。
除了不必要的处理之外,如果您使用前驱列表或记录发现顺序来跟踪从一个节点到另一个节点的路径,您的结果可能与您的预期不同。当然,这是否是一个问题取决于您的具体问题。
显然,这种情况只能发生在一般图而不是树中,但是 BFS 是一种图算法,并且记住两种不同的实现,一种用于图,一种用于树,比只记住一种更简洁易记适用于所有情况的实现。
【讨论】:
那么dfs呢?入队或出队时将节点标记为已访问有什么不同吗? @smilingpoplar 同样的事情。如果您将一个节点加入队列但未将其标记为已发现,则您将面临再次加入队列的风险。 我认为dfs在出列时标记已访问是可以的。它只是更深入,然后回来,标记访问。很难想象它什么时候会再次排队。 @smilingpoplar 在一个循环的情况下可以入队两次,如 A->B, A->D, B->C, B->D
。
@Iamanon 是的,这会奏效,这就是我所看到的一般情况。 (参见en.wikipedia.org/wiki/Depth-first_search#Pseudocode)我的观点(和上面讨论的迭代情况的等价物)是,将节点标记为从递归调用返回后返回将不起作用。【参考方案2】:
对于相邻节点,如果要更改,条件应该更严格:
if n is not in S and n is not in Q:
【讨论】:
以上是关于出队时在 BFS 上将节点标记为已访问的主要内容,如果未能解决你的问题,请参考以下文章
PHP:从 POP3 或 IMAP 下载传入的电子邮件,对其进行解析,并在服务器上将其标记为已读/删除
使用 Exchange Web Services 2007 将电子邮件标记为已读