我正在寻找一种非递归深度优先搜索算法,用于非二叉树。非常感谢任何帮助。
我正在寻找一种非递归深度优先搜索算法,用于非二叉树。非常感谢任何帮助。
DFS:
list nodes_to_visit = {root};
while( nodes_to_visit isn't empty ) {
currentnode = nodes_to_visit.take_first();
nodes_to_visit.prepend( currentnode.children );
//do something
}
BFS:
list nodes_to_visit = {root};
while( nodes_to_visit isn't empty ) {
currentnode = nodes_to_visit.take_first();
nodes_to_visit.append( currentnode.children );
//do something
}
这两者的对称性非常酷。
更新:如指出的那样,take_first()
会删除并返回列表中的第一个元素。
.first()
函数还会从列表中移除元素。就像许多语言中的 shift()
一样。pop()
也可以使用,并且返回的子节点顺序是从右到左而不是从左到右。 - Ariel灰(第1个)->灰(第2个)->灰(第3个)->标黑(第3个)->标黑(第2个)->标黑(第1个)
。但是你的代码却得出了:灰(第1个)->灰(第2个)->灰(第3个)->标黑(第2个)->标黑(第3个)->标黑(第1个)
。 - batman您可以使用一个堆栈(stack),其中包含尚未访问的节点:
stack.push(root)
while !stack.isEmpty() do
node = stack.pop()
for each node.childNodes do
stack.push(stack)
endfor
// …
endwhile
if (未被标记的节点)
来判断是否适合将其推入堆栈。这样可行吗? - Alstonfor each node.childNodes.reverse() do stack.push(stack) endfor
)。这也可能是您想要的。为什么会这样的好解释在这个视频中:https://www.youtube.com/watch?v=cZPXfl_tUkA。 - Mariusz Pawelski如果您有指向父节点的指针,则可以在不使用额外内存的情况下完成此操作。
def dfs(root):
node = root
while True:
visit(node)
if node.first_child:
node = node.first_child # walk down
else:
while not node.next_sibling:
if node is root:
return
node = node.parent # walk up ...
node = node.next_sibling # ... and right
请注意,如果子节点是作为数组而不是通过兄弟指针存储的,则可以通过以下方式找到下一个兄弟节点:
def next_sibling(node):
try:
i = node.parent.child_nodes.index(node)
return node.parent.child_nodes[i+1]
except (IndexError, AttributeError):
return None
while not node.next_sibling or node is root:
轻松修复。 - Basel ShishaniStack<Node> s;
s.prepend(tree.head);
while(!s.empty) {
Node n = s.poll_front // gets first node
// do something with q?
for each child of n: s.prepend(child)
}
root = {
text: "root",
children: [{
text: "c1",
children: [{
text: "c11"
}, {
text: "c12"
}]
}, {
text: "c2",
children: [{
text: "c21"
}, {
text: "c22"
}]
}, ]
}
console.log("DFS:")
DFS(root, node => node.children, node => console.log(node.text));
console.log("BFS:")
BFS(root, node => node.children, node => console.log(node.text));
function BFS(root, getChildren, visit) {
let nodesToVisit = [root];
while (nodesToVisit.length > 0) {
const currentNode = nodesToVisit.shift();
nodesToVisit = [
...nodesToVisit,
...(getChildren(currentNode) || []),
];
visit(currentNode);
}
}
function DFS(root, getChildren, visit) {
let nodesToVisit = [root];
while (nodesToVisit.length > 0) {
const currentNode = nodesToVisit.shift();
nodesToVisit = [
...(getChildren(currentNode) || []),
...nodesToVisit,
];
visit(currentNode);
}
}
虽然“使用堆栈”可能是刻意设计的面试问题的答案,但实际上,它只是明确地执行递归程序在幕后执行的操作。
递归使用程序内置的堆栈。当您调用函数时,它会将参数推送到堆栈上,并且当函数返回时,它会通过弹出程序堆栈来实现。
PreOrderTraversal is same as DFS in binary tree. You can do the same recursion
taking care of Stack as below.
public void IterativePreOrder(Tree root)
{
if (root == null)
return;
Stack s<Tree> = new Stack<Tree>();
s.Push(root);
while (s.Count != 0)
{
Tree b = s.Pop();
Console.Write(b.Data + " ");
if (b.Right != null)
s.Push(b.Right);
if (b.Left != null)
s.Push(b.Left);
}
}
一般的逻辑是,将一个节点(从根节点开始)推入栈中,然后 Pop() 弹出并打印值。接着,如果它有子节点(左右孩子),则将它们推入栈中——先推入右子节点,这样你就能先访问左子节点(在访问自身节点之后)。当栈为空时,你已经遍历了所有前序遍历的节点。
class Node {
constructor(name, childNodes) {
this.name = name;
this.childNodes = childNodes;
this.visited = false;
}
}
function *dfs(s) {
let stack = [];
stack.push(s);
stackLoop: while (stack.length) {
let u = stack[stack.length - 1]; // peek
if (!u.visited) {
u.visited = true; // grey - visited
yield u;
}
for (let v of u.childNodes) {
if (!v.visited) {
stack.push(v);
continue stackLoop;
}
}
stack.pop(); // black - all reachable descendants were processed
}
}
完整的编程示例代码,不包含堆栈:
import java.util.*;
class Graph {
private List<List<Integer>> adj;
Graph(int numOfVertices) {
this.adj = new ArrayList<>();
for (int i = 0; i < numOfVertices; ++i)
adj.add(i, new ArrayList<>());
}
void addEdge(int v, int w) {
adj.get(v).add(w); // Add w to v's list.
}
void DFS(int v) {
int nodesToVisitIndex = 0;
List<Integer> nodesToVisit = new ArrayList<>();
nodesToVisit.add(v);
while (nodesToVisitIndex < nodesToVisit.size()) {
Integer nextChild= nodesToVisit.get(nodesToVisitIndex++);// get the node and mark it as visited node by inc the index over the element.
for (Integer s : adj.get(nextChild)) {
if (!nodesToVisit.contains(s)) {
nodesToVisit.add(nodesToVisitIndex, s);// add the node to the HEAD of the unvisited nodes list.
}
}
System.out.println(nextChild);
}
}
void BFS(int v) {
int nodesToVisitIndex = 0;
List<Integer> nodesToVisit = new ArrayList<>();
nodesToVisit.add(v);
while (nodesToVisitIndex < nodesToVisit.size()) {
Integer nextChild= nodesToVisit.get(nodesToVisitIndex++);// get the node and mark it as visited node by inc the index over the element.
for (Integer s : adj.get(nextChild)) {
if (!nodesToVisit.contains(s)) {
nodesToVisit.add(s);// add the node to the END of the unvisited node list.
}
}
System.out.println(nextChild);
}
}
public static void main(String args[]) {
Graph g = new Graph(5);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
g.addEdge(3, 1);
g.addEdge(3, 4);
System.out.println("Breadth First Traversal- starting from vertex 2:");
g.BFS(2);
System.out.println("Depth First Traversal- starting from vertex 2:");
g.DFS(2);
}}
输出: 广度优先遍历-从顶点2开始: 2 0 3 1 4 深度优先遍历-从顶点2开始: 2 3 4 1 0
nodesToVisit.contains(s)
在节点数量上线性地消耗时间。另一种选择是使用布尔数组跟踪已经访问过的节点,其访问时间为O(1),并占用额外的O(numOfVertices)空间。 - nspovoid DFSRecursive(Node n, Set<Node> visited) {
visited.add(n);
for (Node x : neighbors_of(n)) { // iterate over all neighbors
if (!visited.contains(x)) {
DFSRecursive(x, visited);
}
}
OnVisit(n); // callback to say node is finally visited, after all its non-visited neighbors
}
好的,现在你想要一个基于栈的实现,因为你的例子不起作用。复杂的图形可能会导致程序的堆栈溢出,因此您需要实现非递归版本。最大的问题是要知道何时发出通知。
下面的伪代码可以工作(使用Java和C ++混合编写以提高可读性):
void DFS(Node root) {
Set<Node> visited;
Set<Node> toNotify; // nodes we want to notify
Stack<Node> stack;
stack.add(root);
toNotify.add(root); // we won't pop nodes from this until DFS is done
while (!stack.empty()) {
Node current = stack.pop();
visited.add(current);
for (Node x : neighbors_of(current)) {
if (!visited.contains(x)) {
stack.add(x);
toNotify.add(x);
}
}
}
// Now issue notifications. toNotifyStack might contain duplicates (will never
// happen in a tree but easily happens in a graph)
Set<Node> notified;
while (!toNotify.empty()) {
Node n = toNotify.pop();
if (!toNotify.contains(n)) {
OnVisit(n); // issue callback
toNotify.add(n);
}
}
看起来很复杂,但发出通知所需的额外逻辑存在是因为您需要按访问的反向顺序进行通知 - DFS从根开始,但最后通知它,不像BFS那样非常容易实现。
为了好玩,尝试以下图形: 节点为s、t、v和w。 有向边为: s->t,s->v,t->w,v->w和v->t。 运行您自己的DFS实现,节点应该被访问的顺序是: w,t,v,s 一个笨拙的DFS实现可能会首先通知t,这表明存在错误。DFS的递归实现始终最后到达w。