# 第 5 题：介绍下深度优先遍历和广度优先遍历，如何实现？ #9

Open
opened this issue Feb 12, 2019 · 28 comments
Open

# 第 5 题：介绍下深度优先遍历和广度优先遍历，如何实现？#9

opened this issue Feb 12, 2019 · 28 comments
Labels

### 深度优先遍历

``````/*深度优先遍历三种方式*/
let deepTraversal1 = (node, nodeList = []) => {
if (node !== null) {
nodeList.push(node)
let children = node.children
for (let i = 0; i < children.length; i++) {
deepTraversal1(children[i], nodeList)
}
}
return nodeList
}
let deepTraversal2 = (node) => {
let nodes = []
if (node !== null) {
nodes.push(node)
let children = node.children
for (let i = 0; i < children.length; i++) {
nodes = nodes.concat(deepTraversal2(children[i]))
}
}
return nodes
}
// 非递归
let deepTraversal3 = (node) => {
let stack = []
let nodes = []
if (node) {
// 推入当前处理的node
stack.push(node)
while (stack.length) {
let item = stack.pop()
let children = item.children
nodes.push(item)
// node = [] stack = [parent]
// node = [parent] stack = [child3,child2,child1]
// node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
// node = [parent, child1-1] stack = [child3,child2,child1-2]
for (let i = children.length - 1; i >= 0; i--) {
stack.push(children[i])
}
}
}
return nodes
}
``````

### 广度优先遍历

``````let widthTraversal2 = (node) => {
let nodes = []
let stack = []
if (node) {
stack.push(node)
while (stack.length) {
let item = stack.shift()
let children = item.children
nodes.push(item)
// 队列，先进先出
// nodes = [] stack = [parent]
// nodes = [parent] stack = [child1,child2,child3]
// nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
// nodes = [parent,child1,child2]
for (let i = 0; i < children.length; i++) {
stack.push(children[i])
}
}
}
return nodes
}
``````

### uhr commented Feb 13, 2019 • edited

 概念上我觉得没啥问题，具体在业务上有啥应用的地方吗（尤其是前端）？ 2.14补充 对象的深拷贝

### 图

`G = (V, E)`

• 有向图
• 无向图

### 图的表示

• 邻接矩阵：使用二维数组来表示点与点之间是否有边，如 `arr[i][j] = 1`表示节点 i 与节点 j 之间有边，`arr[i][j] = 0`表示节点 i 与节点 j 之间没有边
• 邻接表：邻接表是图的一种链式储存结构，这种结构类似树的子链表，对于图中的每一个顶点Vi，把所有邻接于Vi的顶点Vj链成一个单链表，这个单链表就是顶点Vi的邻接表，单链表一般由数组或字典结构表示。

### 创建图

```function Graph() {
this.vertices = [] // 顶点集合
this.edges = new Map() // 边集合
}
Graph.prototype.addVertex = function(v) { // 添加顶点方法
this.vertices.push(v)
this.edges.set(v, [])
}
Graph.prototype.addEdge = function(v, w) { // 添加边方法
let vEdge = this.edges.get(v)
vEdge.push(w)
let wEdge = this.edges.get(w)
wEdge.push(v)
this.edges.set(v, vEdge)
this.edges.set(w, wEdge)
}
Graph.prototype.toString = function() {
var s = ''
for (var i=0; i<this.vertices.length; i++) {
s += this.vertices[i] + ' -> '
var neighors = this.edges.get(this.vertices[i])
for (var j=0; j<neighors.length; j++) {
s += neighors[j] + ' '
}
s += '\n'
}
return s
}```

```var graph = new Graph()
var vertices = [1, 2, 3, 4, 5]
for (var i=0; i<vertices.length; i++) {
}

console.log(graph.toString())
// 1 -> 4 3
// 2 -> 3 5
// 3 -> 1 2
// 4 -> 1
// 5 -> 2```

### 图的遍历

• 深度优先遍历
• 广度优先遍历

#### 深度优先遍历（DFS）

DFS 可以产生相应图的拓扑排序表，利用拓扑排序表可以解决很多问题，例如最大路径问题。一般用堆数据结构来辅助实现DFS算法。

• 访问顶点v
• 依次从v的未被访问的邻接点出发，对图进行深度优先遍历；直至图中和v有路径相通的顶点都被访问
• 若此时途中尚有顶点未被访问，则从一个未被访问的顶点出发，重新进行深度优先遍历，直到所有顶点均被访问过为止

```Graph.prototype.dfs = function() {
var marked = []
for (var i=0; i<this.vertices.length; i++) {
if (!marked[this.vertices[i]]) {
dfsVisit(this.vertices[i])
}
}

function dfsVisit(u) {
let edges = this.edges
marked[u] = true
console.log(u)
var neighbors = edges.get(u)
for (var i=0; i<neighbors.length; i++) {
var w = neighbors[i]
if (!marked[w]) {
dfsVisit(w)
}
}
}
}```

```graph.dfs()
// 1
// 4
// 3
// 2
// 5```

#### 广度优先遍历（BFS）

BFS从一个节点开始，尝试访问尽可能靠近它的目标节点。本质上这种遍历在图上是逐层移动的，首先检查最靠近第一个节点的层，再逐渐向下移动到离起始节点最远的层

• 创建一个队列，并将开始节点放入队列中
• 若队列非空，则从队列中取出第一个节点，并检测它是否为目标节点
• 若是目标节点，则结束搜寻，并返回结果
• 若不是，则将它所有没有被检测过的字节点都加入队列中
• 若队列为空，表示图中并没有目标节点，则结束遍历

```Graph.prototype.bfs = function(v) {
var queue = [], marked = []
marked[v] = true
queue.push(v) // 添加到队尾
while(queue.length > 0) {
var s = queue.shift() // 从队首移除
if (this.edges.has(s)) {
console.log('visited vertex: ', s)
}
let neighbors = this.edges.get(s)
for(let i=0;i<neighbors.length;i++) {
var w = neighbors[i]
if (!marked[w]) {
marked[w] = true
queue.push(w)
}
}
}
}```

```graph.bfs(1)
// visited vertex:  1
// visited vertex:  4
// visited vertex:  3
// visited vertex:  2
// visited vertex:  5```

### kscript commented Feb 19, 2019

 概念上我觉得没啥问题，具体在业务上有啥应用的地方吗（尤其是前端）？ 2.14补充 对象的深拷贝 前端确实很少涉及算法。 这个我也只在做小游戏的时候有用到过

### vivatoviva commented Feb 19, 2019

 概念上我觉得没啥问题，具体在业务上有啥应用的地方吗（尤其是前端）？ 2.14补充 对象的深拷贝 Dom树的遍历

### caihg commented Feb 19, 2019

 @atheist1 非递归版的 deepTraversal3 实际上是广度优先的
mentioned this issue Feb 20, 2019

### guokangf commented Mar 4, 2019

 @caihg 我也是这样想的

### BaconZhang commented Mar 11, 2019

 概念上我觉得没啥问题，具体在业务上有啥应用的地方吗（尤其是前端）？ 2.14补充 对象的深拷贝 这其实是和公司的具体业务相关的，我们公司把在业务层背后抽象了一套树形结构的领域对象模型，整个项目里少不了折腾树的递归，这时候发现熟练掌握深度遍历和广度遍历就十分有用了。

### rccoder commented Mar 31, 2019

 @atheist1 宽搜还是写 queue 吧，stack 歧义太严重了
changed the title 关于第五题我的一些见解 第五题：深度优先遍历和广度优先遍历 Apr 6, 2019

### iamwelk commented Apr 8, 2019

 这个字打错了吧

### MaxPeak commented Apr 22, 2019

 写得非常好了，我之前也了解过这两个东西，@atheist1 ，代码通俗易懂，答案简单直观，非常棒
changed the title 第五题：深度优先遍历和广度优先遍历 第 5 题：介绍下深度优先遍历和广度优先遍历，如何实现？ Apr 26, 2019

### CBGhaha commented Apr 29, 2019 • edited

 generator +Interator接口实现深度遍历 ``````function *DFS(tree){ yield tree; let children=tree.children; if(children){ for(let i in children){ yield *DFS(children[i]) } } } console.log([...DFS(tree)]) ``````
mentioned this issue May 10, 2019

### dbl520 commented Jun 11, 2019

 概念上我觉得没啥问题，具体在业务上有啥应用的地方吗（尤其是前端）？ 2.14补充 对象的深拷贝 前端确实很少涉及算法。 这个我也只在做小游戏的时候有用到过 问下大佬，有个旧的json 格式转成新的json格式 var old ={ "颜色":[ { "黄色":{ "尺码":[ { "XL":{ "形状":[ { "多边形":1 }, { "方形":2 } ] } }, { "M":{ "形状":[ { "方形":3 } ] } } ] } }, { "红色":{ "尺码":[ { "XL":{ "形状":[ { "多边形":1 }, { "方形":2 } ] } }, { "M":{ "形状":[ { "方形":3 } ] } } ] } } ] } var new =[ { // priceId: 1, // price: 35.0, // "stock": 8, "attrValueList": [ { "attrKey": "颜色", "attrValue": "黄色", "attrCode": "1001" }, { "attrKey": "尺码", "attrValue": "xm", "attrCode": "2001" }, { "attrKey": "形状", "attrValue": "多边形", "attrCode": "3001" } `````` ] }, { // priceId: 1, // price: 35.0, // "stock": 8, "attrValueList": [ { "attrKey": "颜色", "attrValue": "黄色", "attrCode": "1001" }, { "attrKey": "尺码", "attrValue": "L", "attrCode": "2001" }, { "attrKey": "形状", "attrValue": "多边形", "attrCode": "3001" } ] }, { // priceId: 1, // price: 35.0, // "stock": 8, "attrValueList": [ { "attrKey": "颜色", "attrValue": "黄色", "attrCode": "1001" }, { "attrKey": "尺码", "attrValue": "L", "attrCode": "2001" }, { "attrKey": "形状", "attrValue": "五边形", "attrCode": "3001" } ] } `````` ] 有方便的转换方法吗

### yujihu commented Jul 12, 2019

 概念上我觉得没啥问题，具体在业务上有啥应用的地方吗（尤其是前端）？ 2.14补充 对象的深拷贝 数组扁平化

### yujikan-andrew commented Jul 12, 2019

 深度优先：找到一个节点后，把它的后辈都找出来，最常用递归法。 广度优先：找到一个节点后，把他同级的兄弟节点都找出来放在前边，把孩子放到后边，最常用 while
mentioned this issue Jul 14, 2019

### chenxiaoleizi commented Jul 15, 2019

 @atheist1 非递归版的 deepTraversal3 实际上是广度优先的 为啥是广度的啊？deepTraversal3 不是把当前节点的所有子节点遍历出来之后，在遍历下一个同胞节点吗？

### 深度优先遍历

``````/*深度优先遍历三种方式*/
let deepTraversal1 = (node, nodeList = []) => {
if (node !== null) {
nodeList.push(node)
let children = node.children
for (let i = 0; i < children.length; i++) {
deepTraversal1(children[i], nodeList)
}
}
return nodeList
}
let deepTraversal2 = (node) => {
let nodes = []
if (node !== null) {
nodes.push(node)
let children = node.children
for (let i = 0; i < children.length; i++) {
nodes = nodes.concat(deepTraversal2(children[i]))
}
}
return nodes
}
// 非递归
let deepTraversal3 = (node) => {
let stack = []
let nodes = []
if (node) {
// 推入当前处理的node
stack.push(node)
while (stack.length) {
let item = stack.pop()
let children = item.children
nodes.push(item)
// node = [] stack = [parent]
// node = [parent] stack = [child3,child2,child1]
// node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
// node = [parent, child1-1] stack = [child3,child2,child1-2]
for (let i = children.length - 1; i >= 0; i--) {
stack.push(children[i])
}
}
}
return nodes
}
``````

### 广度优先遍历

``````let widthTraversal2 = (node) => {
let nodes = []
let stack = []
if (node) {
stack.push(node)
while (stack.length) {
let item = stack.shift()
let children = item.children
nodes.push(item)
// 队列，先进先出
// nodes = [] stack = [parent]
// nodes = [parent] stack = [child1,child2,child3]
// nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
// nodes = [parent,child1,child2]
for (let i = 0; i < children.length; i++) {
stack.push(children[i])
}
}
}
return nodes
}
``````

### yingye commented Jul 25, 2019

#### 二叉树

```var myTree = {
val: 6,
left: {
val: 5,
left: {
val: 4
},
right: {
val: 3
}
},
right: {
val: 2,
right: {
val: 1
}
}
}```
##### 广度优先遍历 BFS

```function bfs (tree) {
var queue = [tree]
var res = []
var count = 0
while (queue[count] && queue[count].val) {
res.push(queue[count].val)
var left = queue[count].left
var right = queue[count].right
if (left) {
queue.push(left)
}
if (right) {
queue.push(right)
}
count++
}
return res
}```
##### 深度优先遍历 DFS

```// 非递归版本
function dfs (tree) {
var stack = [tree]
var res = []
while (stack.length) {
var node = stack.pop()
res.push(node.val)
var left = node.left
var right = node.right
if (right) stack.push(right)
if (left) stack.push(left)
}
return res
}```

### montage-f commented Jul 25, 2019

 概念上我觉得没啥问题，具体在业务上有啥应用的地方吗（尤其是前端）？ 2.14补充 对象的深拷贝 深拷贝感觉是用到了深度优先, 广度优先,我还没写过...我得研究下

### pomelovico commented Jul 26, 2019 • edited

 概念上我觉得没啥问题，具体在业务上有啥应用的地方吗（尤其是前端）？ 2.14补充 对象的深拷贝 深拷贝感觉是用到了深度优先, 广度优先,我还没写过...我得研究下 从掘金的广度深拷贝问题过来的，结果全是在聊遍历，自己试着实现了一下广度拷贝，不知道有没有更好的方式。 ```const isArray = (a)=>{Array.isArray(a)}; const isObject = (o)=>Object.prototype.toString.call(o) === '[object Object]'; /** * 广度优先深拷贝，借助队列实现，每个子节点入队列时，记录下它的新父节点以及他的key */ const bfsCopy = (src)=>{ const queue = []; // 入队列操作 const inQueue = (o,parent) => { for(let key in o){ queue.push({ parent, // 新的父节点 key, // 子节点的key node: o[key] // 子节点 }) } } if(isArray(src) || isObject(src)){ const root = isArray(src) ? [] : {}; // 将根节点的子节点入队列 inQueue(src,root); // 开始遍历队列 while(queue.length !== 0){ // 取出队头元素 const { parent, key , node } = queue.shift(); if(isArray(node) || isObject(node)){ const r = isArray(node) ? [] : {}; parent[key] = r; inQueue(node,r) }else{ parent[key] = node; } } return root; } return src; }```

### yaoboGit commented Aug 3, 2019

 @atheist1 非递归版的 deepTraversal3 实际上是广度优先的 这个是深度优先的，你看看它最后的循环，是从末尾开始，也就是说，再下一次循环进来的时候，每次出栈都是从后面最后一个开始[其实已经到了子节点的范畴了】，[(当前节点的子节点是被放在栈的后面）

### xuanbabybaby commented Aug 6, 2019

 感觉广度优先遍历使用的stack 应该使用queue 更合适吧 就是先push进去的 先shift(）取出来 放在nodes里
mentioned this issue Aug 8, 2019

### 深度优先遍历

``````/*深度优先遍历三种方式*/
let deepTraversal1 = (node, nodeList = []) => {
if (node !== null) {
nodeList.push(node)
let children = node.children
for (let i = 0; i < children.length; i++) {
deepTraversal1(children[i], nodeList)
}
}
return nodeList
}
let deepTraversal2 = (node) => {
let nodes = []
if (node !== null) {
nodes.push(node)
let children = node.children
for (let i = 0; i < children.length; i++) {
nodes = nodes.concat(deepTraversal2(children[i]))
}
}
return nodes
}
// 非递归
let deepTraversal3 = (node) => {
let stack = []
let nodes = []
if (node) {
// 推入当前处理的node
stack.push(node)
while (stack.length) {
let item = stack.pop()
let children = item.children
nodes.push(item)
// node = [] stack = [parent]
// node = [parent] stack = [child3,child2,child1]
// node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
// node = [parent, child1-1] stack = [child3,child2,child1-2]
for (let i = children.length - 1; i >= 0; i--) {
stack.push(children[i])
}
}
}
return nodes
}
``````

### 广度优先遍历

``````let widthTraversal2 = (node) => {
let nodes = []
let stack = []
if (node) {
stack.push(node)
while (stack.length) {
let item = stack.shift()
let children = item.children
nodes.push(item)
// 队列，先进先出
// nodes = [] stack = [parent]
// nodes = [parent] stack = [child1,child2,child3]
// nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
// nodes = [parent,child1,child2]
for (let i = 0; i < children.length; i++) {
stack.push(children[i])
}
}
}
return nodes
}
``````

### MissNanLan commented Aug 16, 2019

 深度优先遍历 遍历结果是： 1->2->4->8->5->3->6->7 广度优先遍历 遍历结果是：1->2->3->4->5->6->7->8

### aeolusheath commented Sep 3, 2019 • edited

 宽度优先遍历 和 深度优先遍历 是 `遍历` 或者 `搜索` 图 和 树 的算法。 深度优先遍历： 从根节点开始，沿着树的深度遍历树的节点，尽可能深的搜索树的分支。沿着一条可能的路径一直往下走，深入到不能深入为止。【可以纵向优先搜索】 宽度优先遍历： 从根节点开始，沿着树的宽度遍历树的节点。横向一层(level)的去遍历。 一楼大兄弟的html代码： ```
a
b
c
d
e
f
g
``` 深度优先遍历 - 递归 ``` var dfs_recursive = function(root, res = []){ res.push(root) for (let item of root.children) { dfs_recursive(item, res) } return res } console.log('1------------->>', dfs_recursive(root)) ``` 深度优先遍历 - stack 先进后出 【push(item) -> pop】 或者 [unshift(item) -> shift()] ``` var dfs_stack = function(root) { const res = [] const stack = [] stack.push(root) while (stack.length != 0) { let item = stack.pop() res.push(item) for (let i = item.children.length - 1; i >= 0; i--) { stack.push(item.children[i]) } } return res } console.log('2------------->>', dfs_stack(root)) ``` 广度优先遍历 - queue 先进先出[push(item) -> shift()] 或者[unshift(item) -> pop()] ``` var bfs_queue = function(root) { const res = [] const queue = [] queue.push(root) while (queue.length != 0) { let currentLevelLength = queue.length for (let i = 0 ; i < currentLevelLength; i++) { let item = queue.shift() res.push(item) for (let j = 0; j < item.children.length; j++) { queue.push(item.children[j]) } } } return res } console.log('3------------->>', bfs_queue(root))```

### calmchang commented Sep 4, 2019

 深度遍历 ``````var deep=(node,fn)=>{ fn(node); if(node.children){ for(let i=0;i{ var queue = []; queue.push(node); while (queue.length != 0) { let item = queue.shift(); fn(item); for (let j = 0; j < item.children.length; j++) { queue.push(item.children[j]) } } }; `````` 测试代码 ``````var parent=document.getElementsByClassName('parent')[0]; wide(parent,(node)=>{ console.log(node.className); }); deep(parent,(node)=>{ console.log(node.className); }); ``````
mentioned this issue Oct 17, 2019
Closed
added the label Dec 16, 2019

### hadardb commented Dec 23, 2019 • edited

 我看大家广度优先都用两个数组做，其实用一个就可以了 ，利用for循环每次计算length的特性 广度优先遍历 ` ``````function widthTraversal(node,nodeList = []){ if(!node){ return [] } nodeList.push(node) for(let a = 0; a

### kiccer commented Dec 30, 2019

 我举个通俗的例子 假如你的面前摆了一排杯子，每个杯子里都装了不确定量的水。 深度优先遍历就是你拿起第一个杯子，喝光水，然后再拿起下一个杯子，喝光，一直到最后一个杯子。 广度优先遍历就是你从第一个杯子开始，每个杯子挨个都喝一口，然后又重头开始挨个喝一口，一直到全部喝完为止。 是这样吧？