有人可以给我提供迭代深度优先树遍历的伪代码吗?并且可以在每个节点进行前序和后序操作吗?
也就是说,在下降到节点的子节点之前执行某个操作,然后在从子节点上升后执行另一个操作?
此外,我的树不是二叉树 - 每个节点都有0..n个子节点。
基本上,我的情况是将递归遍历转换为迭代遍历,在递归进入子节点之前和之后对当前节点执行前序和后序操作。
有人可以给我提供迭代深度优先树遍历的伪代码吗?并且可以在每个节点进行前序和后序操作吗?
也就是说,在下降到节点的子节点之前执行某个操作,然后在从子节点上升后执行另一个操作?
此外,我的树不是二叉树 - 每个节点都有0..n个子节点。
基本上,我的情况是将递归遍历转换为迭代遍历,在递归进入子节点之前和之后对当前节点执行前序和后序操作。
我的计划是使用两个栈。一个用于先序遍历,另一个用于后序遍历。 现在,我运行标准的迭代深度优先遍历(DFS),并且一旦从"pre"栈中弹出,就将其推入"post"栈中。 最后,我的"post"栈将在顶部具有子节点,在底部具有根节点。
treeSearch(Tree root) {
Stack pre;
Stack post;
pre.add(root);
while (pre.isNotEmpty()) {
int x = pre.pop();
// do pre-order visit here on x
post.add(x);
pre.addAll(getChildren(x));
}
while (post.isNotEmpty()) {
int y = post.pop();
// do post-order visit here on y
}
}
post
栈中的 root
始终会最后遍历,因为它将保留在底部。
这是简单的 Java 代码:
public void treeSearch(Tree tree) {
Stack<Integer> preStack = new Stack<Integer>();
Stack<Integer> postStack = new Stack<Integer>();
preStack.add(tree.root);
while (!preStack.isEmpty()) {
int currentNode = preStack.pop();
// do pre-order visit on current node
postStack.add(currentNode);
int[] children = tree.getNeighbours(currentNode);
for (int child : children) {
preStack.add(child);
}
}
while (!postStack.isEmpty()) {
int currentNode = postStack.pop();
// do post-order visit on current node
}
}
我假设这是一棵树,因此:没有环,也不会重新访问同一个节点。但是,如果需要的话,我们可以始终使用一个visited数组并对其进行检查。
我知道这篇文章已经几年了,但是没有一个答案直接回答这个问题,所以我想写一些比较简单的内容。
这假设了一个整数索引的图;但您可以根据需要进行调整。在进行DFS迭代并仍然具有先序/后序操作的关键是不仅仅一次添加每个子节点,而是像递归DFS一样行为,即一次只向栈添加一个子节点,并且仅当它完成后才将其从栈中删除。我在我的示例中通过创建带有邻接表作为栈的包装器节点来实现这一点。如果您希望允许循环(它不会遍历已访问的节点,因此它仍然可以工作),请省略循环检查。
class Stack(object):
def __init__(self, l=None):
if l is None:
self._l = []
else:
self._l = l
return
def pop(self):
return self._l.pop()
def peek(self):
return self._l[-1]
def push(self, value):
self._l.append(value)
return
def __len__(self):
return len(self._l)
class NodeWrapper(object):
def __init__(self, graph, v):
self.v = v
self.children = Stack(graph[v])
return
def iterative_postorder(G, s):
onstack = [False] * len(G)
edgeto = [None] * len(G)
visited = [False] * len(G)
st = Stack()
st.push(NodeWrapper(G, s))
while len(st) > 0:
vnode = st.peek()
v = vnode.v
if not onstack[v]:
print "Starting %d" % (v)
visited[v] = True
onstack[v] = True
if len(vnode.children) > 0:
e = vnode.children.pop()
if onstack[e]:
cycle = [e]
e = v
while e != cycle[0]:
cycle.append(e)
e = edgeto[e]
raise StandardError("cycle detected: %s, graph not acyclic" % (cycle))
if not visited[e]:
edgeto[e] = v
st.push(NodeWrapper(G, e))
else:
vnode = st.pop()
onstack[vnode.v] = False
print 'Completed %d' % (vnode.v)
class Node:
def __init__( self, value ):
self.value = value
self.children = []
def preprocess( node ):
print( node.value )
def postprocess( node ):
print( node.value )
def preorder( root ):
# Always a flat, homogeneous list of Node instances.
queue = [ root ]
while len( queue ) > 0:
a_node = queue.pop( 0 )
preprocess( a_node )
queue = a_node.children + queue
def postorder( root ):
# Always a flat, homogeneous list of Node instances:
queue = [ root ]
visited = set()
while len( queue ) > 0:
a_node = queue.pop( 0 )
if a_node not in visited:
visited.add( a_node )
queue = a_node.children + [ a_node ] + queue
else:
# this is either a leaf or a parent whose children have all been processed
postprocess( a_node )
在尝试了不同的解决方案后,让我添加一个迭代解决方案的伪代码,其中你基本上重新创建了函数调用堆栈空间(在递归版本中可能会溢出),并将其保存在一个堆栈变量中。
你需要存储的所有状态都是顶点编号和已处理邻居的数量。这可以是元组、列表、对象或任何你的语言允许的东西。
这种解决方案的优点是你不会遇到堆栈溢出问题,它也适用于具有循环的图表,而且相当健壮。如果使用邻接列表或矩阵,则很容易获得下一个邻居。
这是伪代码,因为它更容易理解,你不会只从SO复制代码,对吧?
globals: isProcessed, preOrder, postOrder
depthFirstSearch()
set isProcessed to all false
for each vertex
if !isProcessed(vertex)
explore(vertex)
explore(root)
create stack
add (root, 0) to stack
visited = empty list
// a list of visited vertices e.g. for finding connected components
while stack is not empty
(vertex, processedNeighbors) ← pop from stack
if !isProcessed(vertex)
// previsit
add vertex to preOrder list
isProcessed(vertex) ← true
if processedNeighbors < number of vertex's neighbors
nextNeighborNumber ← processedNeighbors + 1
push (vertex, nextNeighborNumber) to stack
nextNeighbor ← 'nextNeighborNumber'th neighbor of vertex
if !isProcessed(nextNeighbor)
push (nextNeighbor, 0) to stack
else
// postvisit
add vertex to postOrder list
public void dfsPostOrderIterative(AdjGraph graph, AdjGraph.Node vertex, Callback callback) {
Stack<Level> toVisit = new Stack<Level>();
toVisit.push(new Level(Collections.singletonList(vertex)));
while (!toVisit.isEmpty()) {
Level level = toVisit.peek();
if (level.index >= level.nodes.size()) {
toVisit.pop();
continue;
}
AdjGraph.Node node = level.nodes.get(level.index);
if (!node.isVisited()) {
if (node.isChildrenExplored()) {
node.markVisited();
callback.nodeVisited(graph, node);
level.index++;
} else {
List<AdjGraph.Node> edges = graph.edges(node);
List<AdjGraph.Node> outgoing = Lists.newArrayList(Collections2.filter(edges, new Predicate<AdjGraph.Node>() {
@Override
public boolean apply(AdjGraph.Node input) {
return !input.isChildrenExplored();
}
}));
if (outgoing.size() > 0)
toVisit.add(new Level(outgoing));
node.markChildrenExplored();
}
} else {
level.index++;
}
}
}
我认为通过在El Mariachi提供的后序函数中插入preProcess,我已经得到了我需要的东西:
def postorder( root ):
# Always a flat, homogeneous list of Node instances:
queue = [ root ]
visited = set()
while len( queue ) > 0:
a_node = queue.pop( 0 )
if a_node not in visited:
preprocess( a_node ) # <<<<<<<< Inserted
visited.add( a_node )
queue = a_node.children + [ a_node ] + queue
else:
# this is either a leaf or a parent whose children have all been processed
postprocess( a_node )
class Print_visitor(object):
def __init__(self):
pass
def pre_visit(self, node):
print "pre: ", node.value
def post_visit(self, node):
print "post:", node.value
class Prettyprint_visitor(object):
def __init__(self):
self.level=0
def pre_visit(self, node):
print "{}<{}>".format(" "*self.level, node.value)
self.level += 1
def post_visit(self, node):
self.level -= 1
print "{}</{}>".format(" "*self.level, node.value)
class Node(object):
def __init__(self, value):
self.value = value
self.children = []
def traverse(self, visitor):
visitor.pre_visit(self)
for child in self.children:
child.traverse(visitor)
visitor.post_visit(self)
if __name__ == '__main__':
#test
tree = Node("root")
tree.children = [Node("c1"), Node("c2"), Node("c3")]
tree.children[0].children = [Node("c11"), Node("c12"), Node("c13")]
tree.children[1].children = [Node("c21"), Node("c22"), Node("c23")]
tree.children[2].children = [Node("c31"), Node("c32"), Node("c33")]
tree.traverse(Print_visitor())
tree.traverse(Prettyprint_visitor())
另一种变化,允许将所有子节点一次性推入堆栈,只需要进行最少的状态跟踪。
请注意,它与原始递归遍历产生不同的结果,因为它以相反的顺序进入相邻边。如果这是一个问题,您可以在将它们推入堆栈之前反转邻居列表。
private void TraverseDepthFirst(Node entryNode, Action<Node>? preVisit = null, Action<Node>? postVisit = null)
{
var worklist = new Stack<(Node node, bool entered)>();
var visited = new HashSet<Node>(); //not necessary for trees/acyclic-graphs.
worklist.Push((entryNode, false));
visited.Add(entryNode);
while (worklist.Count > 0) {
var (node, entered) = worklist.Pop();
if (!entered) {
worklist.Push((node, true));
preVisit?.Invoke(node);
foreach (var neighbor in node.Neighbors) {
if (visited.Add(neighbor)) {
worklist.Push((neighbor, false));
}
}
} else {
postVisit?.Invoke(node);
}
}
}
对于需要在下一个兄弟节点的预访问之前进行子节点的后访问的解决方案,可以参考@Creak提到的方法(C#):
public void DepthFirstTraversal2(Node root)
{
Stack<Node> stack = new Stack<Node>();
HashSet<Node> visited = new HashSet<Node>();
stack.Push(root);
while (stack.TryPeek(out var node))
{
if (!visited.Contains(node))
{
visited.Add(node);
Console.WriteLine($"Pre-visit node {node.Value}");
foreach (Node child in node.Children)
{
stack.Push(child);
}
}
else
{
stack.Pop();
Console.WriteLine($"Post-visit node {node.Value}");
}
}
}