简而言之,您不需要超过一个标志。
实际上,您可以通过显式地执行编译器在运行时堆栈中执行的操作,将递归DFS转换为迭代。该技术使用goto
模拟调用和返回,但这些可以转换为更易读的循环。我将使用C进行工作,因为您实际上可以编译中间结果:
#include <stdio.h>
#include <stdlib.h>
#define ARITY 4
typedef struct node_s {
struct node_s *child[ARITY];
int visited_p;
} NODE;
void dfs(NODE *p) {
p->visited_p = 1;
for (int i = 0; i < ARITY; ++i)
if (p->child[i] && !p->child[i]->visited_p)
dfs(p->child[i]);
}
typedef struct stack_frame_s {
int i;
NODE *p;
} STACK_FRAME;
void idfs1(NODE *p) {
STACK_FRAME stack[100];
int i, sp = 0;
start:
p->visited_p = 1;
i = 0;
while (i < ARITY) {
if (p->child[i] && !p->child[i]->visited_p) {
stack[sp].i = i;
stack[sp++].p = p;
p = p->child[i];
goto start;
rtn: ;
}
++i;
}
if (sp) {
i = stack[--sp].i;
p = stack[sp].p;
goto rtn;
}
}
现在对代码进行一些“代数”操作,开始摆脱goto
。
void idfs2(NODE *p) {
STACK_FRAME stack[100];
int i, sp = 0;
start:
p->visited_p = 1;
i = 0;
loop:
while (i < ARITY) {
if (p->child[i] && !p->child[i]->visited_p) {
stack[sp].i = i;
stack[sp++].p = p;
p = p->child[i];
goto start;
}
++i;
}
if (sp) {
i = stack[--sp].i + 1;
p = stack[sp].p;
goto loop;
}
}
不断变革,我们最终到达这里:
void idfs3(NODE *p) {
STACK_FRAME stack[100];
int i, sp = 0;
p->visited_p = 1;
i = 0;
for (;;) {
while (i < ARITY) {
if (p->child[i] && !p->child[i]->visited_p) {
stack[sp].i = i;
stack[sp++].p = p;
p = p->child[i];
p->visited_p = 1;
i = 0;
} else {
++i;
}
}
if (!sp) break;
i = stack[--sp].i + 1;
p = stack[sp].p;
}
}
这很好。还有一个可选的“polish”步骤。我们可以最初将根节点推入堆栈,以简化外部循环:
void idfs3(NODE *p) {
STACK_FRAME stack[100];
p->visited_p = 1;
stack[0].i = 0
stack[0].p = p;
int sp = 1;
while (sp > 0) {
int i = stack[--sp].i;
p = stack[sp].p;
while (i < ARITY) {
if (p->child[i] && !p->child[i]->visited_p) {
stack[sp].i = i + 1;
stack[sp++].p = p;
p = p->child[i];
p->visited_p = 1;
i = 0;
} else {
++i;
}
}
}
}
现在很明显,您确实正在使用一堆迭代器:指向节点的指针和反映搜索该节点子项当前进度的索引。使用像Java这样的语言,我们可以使其明确化。(缺点是在处理子项时失去了对父项的访问权限,这可能在某些情况下是一个问题。)
在这里,我将使用单独的集合来保存已访问的节点。这更可取,因为它使得多个搜索和部分搜索变得更加简单。
void search(Node p) {
Set<Node> visited = new HashSet<>();
Deque<Iterator<Node>> stack = new ArrayDeque<>();
visited.add(p);
stack.push(p.children.iterator());
while (!stack.isEmpty()) {
Iterator<Node> i = stack.pop();
while (i.hasNext()) {
Node child = i.next();
if (!visited.contains(child)) {
stack.push(i);
visited.add(child);
i = child.children.iterator();
}
}
}
}
作为最后的微小优化,您可以跳过将耗尽的迭代器推入堆栈(在 C 中,即
child
数组末尾的
i
值),因为它们在弹出后只是被忽略。
void search(Node p) {
Set<Node> visited = new HashSet<>();
Deque<Iterator<Node>> stack = new ArrayDeque<>();
visited.add(p);
if (!p.children.isEmpty()) stack.push(p.children.iterator());
while (!stack.isEmpty()) {
Iterator<Node> i = stack.pop();
while (i.hasNext()) {
Node child = i.next();
if (!visited.contains(child)) {
if (i.hasNext()) stack.push(i);
visited.add(child);
i = child.children.iterator();
}
}
}
}
if S.peek().visited == True
替换if S.peek().done == True
即可)。在你的例子中,你不会处理两次C,因为当从D处理C时,你会将visited = True
。 - Bernhard Barker