Algorithm 非递归DFS实现
最近,我需要实现非递归DFS,作为更复杂算法的一部分,确切地说是Tarjan算法。递归实现非常优雅,但不适用于大型图。当我实现迭代版本时,我震惊于它最终是多么不优雅,我想知道我是否做错了什么 迭代DFS有两种基本方法。首先,您可以一次将节点的所有子节点推送到堆栈上(似乎更常见)。或者你可以推一个。我将集中讨论第一个问题,因为每个人似乎都是这样做的 我在这个算法中遇到了各种各样的问题,最终我意识到要有效地完成它,我需要的不是1,不是2,而是3个布尔标志(我的意思不是说你需要三个显式布尔变量,你可以通过变量的特殊值(通常是整数)间接存储信息,但你需要以某种方式访问这三条信息。三个标志是:1)访问。这是为了防止孩子们被过度地推到堆栈上。2)完成。防止对同一节点进行冗余处理。3) 上升/下降。指示子项是否已被推到堆栈上。伪代码如下所示:Algorithm 非递归DFS实现,algorithm,depth-first-search,iteration,non-recursive,Algorithm,Depth First Search,Iteration,Non Recursive,最近,我需要实现非递归DFS,作为更复杂算法的一部分,确切地说是Tarjan算法。递归实现非常优雅,但不适用于大型图。当我实现迭代版本时,我震惊于它最终是多么不优雅,我想知道我是否做错了什么 迭代DFS有两种基本方法。首先,您可以一次将节点的所有子节点推送到堆栈上(似乎更常见)。或者你可以推一个。我将集中讨论第一个问题,因为每个人似乎都是这样做的 我在这个算法中遇到了各种各样的问题,最终我意识到要有效地完成它,我需要的不是1,不是2,而是3个布尔标志(我的意思不是说你需要三个显式布尔变量,你可以
while(S)
if S.peek().done == True
S.pop()
continue
S.peek().visited = True
if S.peek().descending == True
S.peek().descending = False
for c in S.peek().children
if c.visited == False
S.push(c)
doDescendingStuff()
else
w = S.pop()
w.done = True
doAscendingStuff()
一些注意事项:1)从技术上讲,您不需要升序/降序,因为您可以看到孩子们是否都完成了。但在稠密图中效率很低
2) ,主攻方:访问/完成的事情似乎没有必要。这就是为什么(我认为)你需要它。在堆栈上访问对象之前,无法标记已访问的对象。如果你这样做,你可能会以错误的顺序处理事情。例如,假设A链接到B和C,B链接到D,D链接到C。然后从A开始,将B和C推到堆栈上。从B开始在堆栈上按D。。。然后呢?如果在堆栈上推送对象时标记已访问的对象,则不会在此堆栈上推送C。但这是错误的,C应该从D访问,而不是从图中的A访问(假设A在C之前访问B)。所以,在处理之前,您不会标记访问的内容。但是,在堆栈上有两次C。所以您需要另一个标志来表明您已经完全完成了它,这样您就不会再次处理C
我看不出如何避免所有这些,以获得一个完全正确的非递归DFS,该DFS支持缠绕和展开操作。但本能地感觉很粗糙。有更好的办法吗?我在网上咨询过的几乎每个地方都对如何实际实现非递归DFS进行了掩饰,说这是可以做到的,并提供了一个非常基本的算法。当算法是正确的(就正确地支持到同一节点的多条路径而言)时(这种情况很少见),它很少正确地支持在卷取和放卷时进行操作 我认为最优雅的基于堆栈的实现应该在堆栈上有子迭代器,而不是节点迭代器。将迭代器视为在其子节点中存储节点和位置
while (!S.empty)
Iterator i = S.pop()
bool found = false
Iterator temp = null
while (i.hasNext())
Node n = i.next()
if (n.visited == false)
n.visited = true
doDescendingStuff(n)
temp = n.getChildrenIterator()
break
if (!i.hasNext())
doAscendingStuff(i.getNode())
else
S.push(i)
if (temp != null)
S.push(temp)
通过将节点和位置分离到两个堆栈上,可以优化上述i.t.o存储空间。为了使用堆栈进行DFS遍历,请从堆栈中弹出一个节点(记住在堆栈中推送初始节点),并检查它是否已被访问。如果已经访问,则忽略并弹出next,否则输出弹出节点,标记它已访问并将其所有邻居推送到堆栈上。继续这样做,直到堆栈为空。罗伯特·塞吉威克(Robert Sedgewick)在cpp书中的算法谈到了一种特殊的堆栈,它只保存一个项目的副本,而忘记了旧的副本。不完全确定如何做到这一点,但它消除了堆栈中有多个项的问题 您的代码没有完全模拟递归DFS实现所发生的情况。 在递归DFS实现中,每个节点在任何时候都只在堆栈中出现一次 Dukeling给出的解决方案是一种迭代的方法。基本上,一次只能在堆栈中推送一个节点,而不是一次推送所有节点 您关于这将需要更多存储的断言是错误的:在您的实现中,一个节点可以在堆栈上多次运行。事实上,如果从一个非常密集的图(所有顶点上的完整图)开始,就会发生这种情况。 对于Dukeling解决方案,堆栈的大小为O(顶点数)。在您的解决方案中,它是O(边数) 算法BFS(G,v) 算法DFS(G,v)
Tl;dr是指您不需要多个标志 实际上,您可以通过明确执行编译器对运行时堆栈所做的操作,将递归DFS转换为迭代DFS。该技术使用
goto
s来模拟调用和返回,但这些可以转换为更可读的循环。我将使用C语言,因为您实际上可以编译中间结果:
#include <stdio.h>
#include <stdlib.h>
#define ARITY 4
typedef struct node_s {
struct node_s *child[ARITY];
int visited_p;
} NODE;
// Recursive version.
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]);
}
// Model of the compiler's stack frame.
typedef struct stack_frame_s {
int i;
NODE *p;
} STACK_FRAME;
// First iterative version.
void idfs1(NODE *p) {
// Set up the stack.
STACK_FRAME stack[100];
int i, sp = 0;
// Recursive calls will jump back here.
start:
p->visited_p = 1;
// Simplify by using a while rather than for loop.
i = 0;
while (i < ARITY) {
if (p->child[i] && !p->child[i]->visited_p) {
stack[sp].i = i; // Save params and locals to stack.
stack[sp++].p = p;
p = p->child[i]; // Update the param to its new value.
goto start; // Emulate the recursive call.
rtn: ; // Emulate the recursive return.
}
++i;
}
// Emulate restoring the previous stack frame if there is one.
if (sp) {
i = stack[--sp].i;
p = stack[sp].p;
goto rtn; // Return from previous call.
}
}
继续转变,我们就这样结束了:
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;
}
}
这里是一个指向java程序的链接,该程序显示DFS同时遵循reccursive和non reccursive方法,并计算发现时间和完成时间但不计算边拉勒灵
public void DFSIterative() {
Reset();
Stack<Vertex> s = new Stack<>();
for (Vertex v : vertices.values()) {
if (!v.visited) {
v.d = ++time;
v.visited = true;
s.push(v);
while (!s.isEmpty()) {
Vertex u = s.peek();
s.pop();
boolean bFinished = true;
for (Vertex w : u.adj) {
if (!w.visited) {
w.visited = true;
w.d = ++time;
w.p = u;
s.push(w);
bFinished = false;
break;
}
}
if (bFinished) {
u.f = ++time;
if (u.p != null)
s.push(u.p);
}
}
}
}
}
public-void(){
重置();
堆栈s=新堆栈();
对于(顶点v:vertexs.values()){
如果(!v.已访问){
v、 d=+时间;
v、 访问=真实;
s、 推(v);
而(!s.isEmpty()){
顶点u=s.peek();
s、 pop();
布尔bFinished=true;
用于(顶点w:u.adj){
如果(!w.已访问){
w、 访问=真实;
w、 d=+时间;
w、 p=u;
s、 推力(w);
b完成=错误;
打破
}
}
如果(b完成){
u、 f=+时间;
如果(u.p!=null)
s、 推(u)
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;
}
}
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;
}
}
}
}
void search(Node p) {
Set<Node> visited = new HashSet<>();
Deque<Iterator<Node>> stack = new ArrayDeque<>();
visited.add(p); // Visit the root.
stack.push(p.children.iterator());
while (!stack.isEmpty()) {
Iterator<Node> i = stack.pop(); // Backtrack to a child list with work to do.
while (i.hasNext()) {
Node child = i.next();
if (!visited.contains(child)) {
stack.push(i); // Save progress on this child list.
visited.add(child); // Descend to visit the child.
i = child.children.iterator(); // Process its children next.
}
}
}
}
void search(Node p) {
Set<Node> visited = new HashSet<>();
Deque<Iterator<Node>> stack = new ArrayDeque<>();
visited.add(p); // Visit the root.
if (!p.children.isEmpty()) stack.push(p.children.iterator());
while (!stack.isEmpty()) {
Iterator<Node> i = stack.pop(); // Backtrack to a child list with work to do.
while (i.hasNext()) {
Node child = i.next();
if (!visited.contains(child)) {
if (i.hasNext()) stack.push(i); // Save progress on this child list.
visited.add(child); // Descend to visit the child.
i = child.children.iterator(); // Process its children next.
}
}
}
}
public void DFSIterative() {
Reset();
Stack<Vertex> s = new Stack<>();
for (Vertex v : vertices.values()) {
if (!v.visited) {
v.d = ++time;
v.visited = true;
s.push(v);
while (!s.isEmpty()) {
Vertex u = s.peek();
s.pop();
boolean bFinished = true;
for (Vertex w : u.adj) {
if (!w.visited) {
w.visited = true;
w.d = ++time;
w.p = u;
s.push(w);
bFinished = false;
break;
}
}
if (bFinished) {
u.f = ++time;
if (u.p != null)
s.push(u.p);
}
}
}
}
}