遍历树/图时,广度优先和深度优先有什么区别?任何编码或伪代码示例都会很棒。
4 回答
这两个术语区分了两种不同的走树方式。
仅仅展示差异可能是最简单的。考虑树:
A
/ \
B C
/ / \
D E F
深度优先遍历将按此顺序访问节点
A, B, D, C, E, F
请注意,在继续前进之前,您要一直走下一条腿。
广度优先遍历将按此顺序访问节点
A, B, C, D, E, F
在这里,我们在下降之前一直在每个级别上工作。
(请注意,遍历顺序存在一些歧义,并且我作弊以维持树的每一级的“阅读”顺序。在任何一种情况下,我都可以在 C 之前或之后到达 B,同样我可以到达E 在 F 之前或之后。这可能重要也可能不重要,取决于您的应用程序...)
两种遍历都可以用伪代码实现:
Store the root node in Container
While (there are nodes in Container)
N = Get the "next" node from Container
Store all the children of N in Container
Do some work on N
两种遍历顺序的区别在于 的选择Container
。
- 对于深度,首先使用堆栈。(递归实现使用调用堆栈...)
- 对于广度优先使用队列。
递归实现看起来像
ProcessNode(Node)
Work on the payload Node
Foreach child of Node
ProcessNode(child)
/* Alternate time to work on the payload Node (see below) */
当您到达没有子节点的节点时,递归结束,因此保证对于有限的非循环图结束。
在这一点上,我还是有点作弊。稍微聪明一点,您还可以按以下顺序处理节点:
D, B, E, F, C, A
这是深度优先的一种变体,我不会在每个节点上进行工作,直到我走回树上。然而,我在向下寻找他们的孩子的路上访问了更高的节点。
这种遍历在递归实现中是相当自然的(使用上面的“Alternate time”行而不是第一个“Work”行),如果使用显式堆栈也不会太难,但我将把它留作练习。
理解条款:
这张图片应该让您了解使用广度和深度这两个词的上下文。
深度优先搜索:
深度优先搜索算法的行为就像它想尽快远离起点一样。
它通常使用 a
Stack
来记住当它到达死胡同时它应该去哪里。遵循的规则:将第一个顶点 A 推到
Stack
- 如果可能,访问相邻的未访问顶点,将其标记为已访问,并将其压入堆栈。
- 如果你不能遵循规则 1,那么,如果可能的话,从堆栈中弹出一个顶点。
- 如果你不能遵循规则 1 或规则 2,那么你就完了。
Java代码:
public void searchDepthFirst() { // Begin at vertex 0 (A) vertexList[0].wasVisited = true; displayVertex(0); stack.push(0); while (!stack.isEmpty()) { int adjacentVertex = getAdjacentUnvisitedVertex(stack.peek()); // If no such vertex if (adjacentVertex == -1) { stack.pop(); } else { vertexList[adjacentVertex].wasVisited = true; // Do something stack.push(adjacentVertex); } } // Stack is empty, so we're done, reset flags for (int j = 0; j < nVerts; j++) vertexList[j].wasVisited = false; }
应用:深度优先搜索通常用于模拟游戏(以及现实世界中的类似游戏的情况)。在典型的游戏中,您可以选择几个可能的动作之一。每个选择都会导致进一步的选择,每个选择都会导致进一步的选择,依此类推,形成一个不断扩大的树形可能性图。
广度优先搜索:
- 广度优先搜索算法喜欢尽可能靠近起点。
- 这种搜索通常使用
Queue
. - 遵循的规则:使起始顶点 A 成为当前顶点
- 访问与当前顶点相邻的下一个未访问的顶点(如果有的话),标记它,并将其插入到队列中。
- 如果由于没有更多未访问的顶点而无法执行规则 1,请从队列中删除一个顶点(如果可能)并将其设为当前顶点。
- 如果您因为队列为空而无法执行规则 2,那么您就完成了。
Java代码:
public void searchBreadthFirst() { vertexList[0].wasVisited = true; displayVertex(0); queue.insert(0); int v2; while (!queue.isEmpty()) { int v1 = queue.remove(); // Until it has no unvisited neighbors, get one while ((v2 = getAdjUnvisitedVertex(v1)) != -1) { vertexList[v2].wasVisited = true; // Do something queue.insert(v2); } } // Queue is empty, so we're done, reset flags for (int j = 0; j < nVerts; j++) vertexList[j].wasVisited = false; }
应用:广度优先搜索首先查找距离起点一条边的所有顶点,然后查找距离起点两条边的所有顶点,以此类推。如果您试图找到从起始顶点到给定顶点的最短路径,这很有用。
希望这足以理解广度优先和深度优先搜索。为了进一步阅读,我会推荐 Robert Lafore 的一本优秀的数据结构书中的 Graphs 章节。
给定这个二叉树:
广度优先遍历:
从左到右遍历每个级别。
“我是 G,我的孩子是 D 和 I,我的孙子是 B、E、H 和 K,他们的孙子是 A、C、F”
- Level 1: G
- Level 2: D, I
- Level 3: B, E, H, K
- Level 4: A, C, F
Order Searched: G, D, I, B, E, H, K, A, C, F
深度优先遍历:
遍历不是一次在整个关卡中完成。相反,遍历首先深入到树的深度(从根到叶)。但是,它比简单的上下移动要复杂一些。
有以下三种方法:
1) PREORDER: ROOT, LEFT, RIGHT.
You need to think of this as a recursive process:
Grab the Root. (G)
Then Check the Left. (It's a tree)
Grab the Root of the Left. (D)
Then Check the Left of D. (It's a tree)
Grab the Root of the Left (B)
Then Check the Left of B. (A)
Check the Right of B. (C, and it's a leaf node. Finish B tree. Continue D tree)
Check the Right of D. (It's a tree)
Grab the Root. (E)
Check the Left of E. (Nothing)
Check the Right of E. (F, Finish D Tree. Move back to G Tree)
Check the Right of G. (It's a tree)
Grab the Root of I Tree. (I)
Check the Left. (H, it's a leaf.)
Check the Right. (K, it's a leaf. Finish G tree)
DONE: G, D, B, A, C, E, F, I, H, K
2) INORDER: LEFT, ROOT, RIGHT
Where the root is "in" or between the left and right child node.
Check the Left of the G Tree. (It's a D Tree)
Check the Left of the D Tree. (It's a B Tree)
Check the Left of the B Tree. (A)
Check the Root of the B Tree (B)
Check the Right of the B Tree (C, finished B Tree!)
Check the Right of the D Tree (It's a E Tree)
Check the Left of the E Tree. (Nothing)
Check the Right of the E Tree. (F, it's a leaf. Finish E Tree. Finish D Tree)...
Onwards until...
DONE: A, B, C, D, E, F, G, H, I, K
3) POSTORDER:
LEFT, RIGHT, ROOT
DONE: A, C, B, F, E, D, H, K, I, G
用法(又名,我们为什么关心):
我真的很喜欢 Quora 对深度优先遍历方法及其常用方法的简单解释:
“按顺序遍历将打印值 [为了 BST(二叉搜索树)] "
"前序遍历用于创建[二叉搜索树]的副本。"
“后序遍历用于删除[二叉搜索树]。”
https://www.quora.com/What-is-the-use-of-pre-order-and-post-order-traversal-of-binary-trees-in-computing
我认为以一种仅通过切换某些代码行才能为您提供一种算法或另一种算法的方式编写它们会很有趣,这样您就会发现您的困境并不像起初看起来那么强大.
我个人喜欢将 BFS 解释为淹没景观:首先淹没低海拔地区,然后才是高海拔地区。如果您将景观高度想象为我们在地理书籍中看到的等值线,那么很容易看出 BFS 同时填充了同一等值线下的所有区域,就像物理学一样。因此,将高度解释为距离或按比例缩放的成本给出了算法的一个非常直观的概念。
考虑到这一点,您可以轻松地调整广度优先搜索背后的思想,以轻松找到最小生成树、最短路径以及许多其他最小化算法。
我还没有看到对 DFS 的任何直观解释(只有关于迷宫的标准解释,但它没有 BFS 和洪水那么强大),所以对我来说,BFS 似乎与上述物理现象的相关性更好,而DFS 与理性系统上的选择困境(即人或计算机决定在国际象棋游戏中采取哪一步或走出迷宫)具有更好的相关性。
所以,对我来说,区别在于哪种自然现象最符合它们在现实生活中的传播模型(横向)。