我在图上搜索非递归深度优先搜索算法 在Pascal(德尔福)。
我需要DFS来计算大图的强连接或双连接组件。 目前我正在使用算法的递归变体:http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
问题是对于这样的算法我必须定义大量的内存 用于堆栈并在Windows 7中产生后续问题, 由于生成了几个线程,打开和保存对话框不起作用....
再次:我没有看到如何重写Tarjan DFS算法 没有递归的工作。你有什么建议 - 或者指向用于深度优先搜索图形的非递归算法?
感谢。
答案 0 :(得分:4)
维基百科上描述的算法看起来很容易使用显式堆栈进行非递归。从那开始(包括在这里作为参考,如果维基百科改变):
Input: Graph G = (V, E)
index = 0 // DFS node number counter
S = empty // An empty stack of node
for all v in V do
if (v.index is undefined) // Start a DFS at each node
tarjan(v) // we haven't visited yet
procedure tarjan(v)
v.index = index // Set the depth index for v
v.lowlink = index // SHOULD BE v.lowlink = MAX_INT?
index = index + 1
S.push(v) // Push v on the stack
for all (v, v') in E do // Consider successors of v
if (v'.index is undefined) // Was successor v' visited?
tarjan(v') // Recurse
v.lowlink = min(v.lowlink, v'.lowlink)
else if (v' is in S) // Was successor v' in stack S?
v.lowlink = min(v.lowlink, v'.index ) // v' is in stack but it isn't in the dfs tree
if (v.lowlink == v.index) // Is v the root of an SCC?
print "SCC:"
repeat
v' = S.pop
print v'
until (v' == v)
步骤1:删除包含递归,添加标签和gotos的循环。这对于使循环变量显式,可维护和可恢复(在使用堆栈的递归模拟期间需要)是必要的。需要在tarjan()
返回后添加标签,因为我们马上会跳转到它。
procedure tarjan(v)
v.index = index // Set the depth index for v
v.lowlink = index // SHOULD BE v.lowlink = MAX_INT?
index = index + 1
S.push(v) // Push v on the stack
succ = all (v, v') in E // Consider successors of v
succIndex = 0 // presume succ is 0-based
loop_top:
if succIndex >= Length(succ) goto skip_loop
v' = succ[succIndex]
if (v'.index is undefined) // Was successor v' visited?
tarjan(v') // Recurse
recursion_returned:
v.lowlink = min(v.lowlink, v'.lowlink)
else if (v' is in S) // Was successor v' in stack S?
v.lowlink = min(v.lowlink, v'.index ) // v' is in stack but it isn't in the dfs tree
succIndex = succIndex + 1
goto loop_top
skip_loop:
if (v.lowlink == v.index) // Is v the root of an SCC?
print "SCC:"
repeat
v' = S.pop
print v'
until (v' == v)
步骤2:引入一个堆栈,其中包含所有相关状态,用于在我们可能从递归返回的任何点或循环顶部开始在循环中存储我们的位置和计算。
筹码:
T = empty // T will be our stack, storing (v, v', succ, succIndex, state)
state
是一个枚举(TopState
,ReturnedState
),用于编码过程中的位置。以下是使用此堆栈和状态而不是递归重写的过程:
procedure tarjan(v)
while (T is not empty) do
(v, v', succ, succIndex, state) = T.pop
case state of
TopState: goto top
ReturnedState: goto recursion_returned
end case
top:
v.index = index // Set the depth index for v
v.lowlink = index // SHOULD BE v.lowlink = MAX_INT?
index = index + 1
S.push(v) // Push v on the stack
succ = all (v, v') in E // Consider successors of v
succIndex = 0 // presume succ is 0-based
loop_top:
if succIndex >= Length(succ) goto skip_loop
v' = succ[succIndex]
if (v'.index is undefined) // Was successor v' visited?
// instead of recursing, set up state for return and top and iterate
T.push(v, v', succ, succIndex, ReturnedState) // this is where we return to
T.push(v', empty, empty, empty, TopState) // but this is where we go first
continue // continue the while loop at top
recursion_returned:
v.lowlink = min(v.lowlink, v'.lowlink)
else if (v' is in S) // Was successor v' in stack S?
v.lowlink = min(v.lowlink, v'.index ) // v' is in stack but it isn't in the dfs tree
succIndex = succIndex + 1
goto loop_top
skip_loop:
if (v.lowlink == v.index) // Is v the root of an SCC?
print "SCC:"
repeat
v' = S.pop
print v'
until (v' == v)
步骤3:最后,对于调用tarjan的顶级代码,我们需要确保输入条件正确。这可以通过初步推动轻松完成:
procedure tarjan(v)
T.push(v, empty, empty, empty, TopState)
while (T is not empty) do
(v, v', succ, succIndex, state) = T.pop
case state of
TopState: goto top
ReturnedState: goto recursion_returned
end case
top:
v.index = index // Set the depth index for v
v.lowlink = index // SHOULD BE v.lowlink = MAX_INT?
index = index + 1
S.push(v) // Push v on the stack
succ = all (v, v') in E // Consider successors of v
succIndex = 0 // presume succ is 0-based
loop_top:
if succIndex >= Length(succ) goto skip_loop
v' = succ[succIndex]
if (v'.index is undefined) // Was successor v' visited?
// instead of recursing, set up state for return and top and iterate
T.push(v, v', succ, succIndex, ReturnedState) // this is where we return to
T.push(v', empty, empty, empty, TopState) // but this is where we go first
continue // continue the while loop at top
recursion_returned:
v.lowlink = min(v.lowlink, v'.lowlink)
else if (v' is in S) // Was successor v' in stack S?
v.lowlink = min(v.lowlink, v'.index ) // v' is in stack but it isn't in the dfs tree
succIndex = succIndex + 1
goto loop_top
skip_loop:
if (v.lowlink == v.index) // Is v the root of an SCC?
print "SCC:"
repeat
v' = S.pop
print v'
until (v' == v)
也可以通过跳跃来实现,立即跳到top
。代码可以进一步清理,可能转换为使用while或repeat循环来消除一些gotos等,但上面应该至少在功能上等效,消除显式递归。
答案 1 :(得分:1)
消除Tarjan算法中的递归很困难。当然,它需要复杂的代码。 Kosaraju's algorithm是另一种解决方案。我相信在Kosaraju's algorithm中消除递归要容易得多。
您可以尝试使用维基百科中描述的Kosaraju算法,或按照以下说明操作。
让G1
成为有向图,List
为空堆栈。
对于v
以外的每个节点List
,从v
开始执行DFS。每次DFS在节点u
处完成时,将u
推送到List
。
对于没有递归的DFS,创建一个名为st
的堆栈。 st
中的每个元素代表一个命令。
x
表示“如果未访问x则访问节点x”。
-x
表示“完成访问x”。
请考虑以下代码:
const int N = 100005;
bool Dfs[N]; // check if a node u is visited
vector<int> List;
void dfs(int U) {
stack<int> st; st.push(U);
while (st.size()) {
int u=st.top(); st.pop();
if (u<0) {
List.push_back(-u);
} else if (!Dfs[u]) {
Dfs[u] = true;
st.push(-u);
// for each node v adjacent with u in G1
for (int i=0; int v=a1[u][i]; i++)
if (!Dfs[v]) st.push(v);
}
}
}
for (int i=1; i<=n; i++)
if (!Dfs[i]) dfs(i);
现在,List
已创建。
让G2
成为G1
的转置图(反转G1
中所有弧的方向以获得转置图G2
)。
虽然List
不为空,但从v
弹出顶部节点List
。从G2
开始在v
执行BFS,访问的节点合并为新的SCC。从G2
和List
中删除此类节点。
请考虑以下代码:
bool Bfs[N];
int Group[N]; // the result
void bfs(int U) {
queue<int> qu;
qu.push(U); Bfs[U]=true;
while (qu.size()) {
int u=qu.front(); qu.pop();
Group[u]=U;
// for each node v adjacent with u in G2
for (int i=0; int v=a2[u][i]; i++)
if (!Bfs[v]) { qu.push(v); Bfs[v]=true; }
}
}
for (int i=List.size()-1; i>=0; i--)
if (!Bfs[List[i]]) bfs(List[i]);
结果位于数组Group
。
答案 2 :(得分:0)
虽然我无法访问您的数据集,但是在所有正确的情况下都没有找到基本情况的意外错误递归是相当常见的,否则图表中可能会有一个循环。我会在继续之前检查这两件事。例如,是否存在比树中的节点更多的函数下降?
除此之外,您的数据集可能太大而无法溢出进程堆栈。如果是这种情况,我建议编写一个使用您提供给它的堆栈的算法的迭代版本。堆栈应该存在于堆空间中,而不是堆栈空间。您需要自己保留搜索的上下文,而不是让算法执行此操作。
该算法是一种递归算法。期。您不需要编写一个自我调用的函数,但最终您仍需要跟踪您去过的位置以及访问过的节点的顺序。