JavaScript通过reduce+递归实现树的深度遍历
越是结构化的有规律的数据操作起来越简单,只是我们没有找到规律和工具。
首先贴代码
首先定义了一个树结构,需求是通过任意节点遍历出其所有的子节点。
根据需求的不同,就会有深度遍历和广度遍历两种,getAllChildrenDFSByReduce(),getAllChildrenDFSByStack()
是深度遍历的两种实现,getAllChildrenBFSByQueue()
是广度遍历的实现。
class Node {
constructor(id, data, parent) {
this.id = id
this.data = data
this.parent = parent || null
this.children = []
if (this.parent !== null) {
this.parent.children.push(this)
}
}
// DFS使用递归作为驱动力
// 使用递归只能实现前序和后序遍历,取决于item放在什么位置
getAllChildrenDFSByReduce() {
return this.children.reduce((res, item) => {
return [...res, item, ...item.getAllChildrenDFSByReduce()]
}, [])
}
// DFS使用栈遍历作为驱动力
// 对比递归的方式我们发现: 1. 使用递归(调用栈)代替了stack 2. 使用reduce代替了data
getAllChildrenDFSByStack() {
const stack = [this]
const data = []
while (stack.length !== 0) {
// 以前序遍历为例
const node = stack.pop()
if (node !== this) data.push(node)
stack.push(...node.children.reverse())
}
return data
}
// BFS使用队列遍历作为驱动力
getAllChildrenBFSByQueue() {
const queue = [this]
const data = []
while (queue.length !== 0) {
const node = queue.shift()
data.push(...node.children)
queue.push(...node.children)
}
return data
}
}
let id = 1
const root = new Node(id++, {}, null)
new Node(id++, {}, root)
new Node(id++, {}, root)
new Node(id++, {}, root.children[0])
new Node(id++, {}, root.children[0])
new Node(id++, {}, root.children[1])
new Node(id++, {}, root.children[1])
new Node(id++, {}, root.children[0].children[0])
new Node(id++, {}, root.children[0].children[0])
new Node(id++, {}, root.children[0].children[1])
new Node(id++, {}, root.children[0].children[1])
new Node(id++, {}, root.children[1].children[0])
new Node(id++, {}, root.children[1].children[0])
new Node(id++, {}, root.children[1].children[1])
new Node(id++, {}, root.children[1].children[1])
/*
1
2 3
4 5 6 7
8 9 10 11 12 13 14 15
*/
// [2, 4, 8, 9, 5, 10, 11, 3, 6, 12, 13, 7, 14, 15]
console.log(root.getAllChildrenDFSByReduce().map(item => item.id))
// [2, 4, 8, 9, 5, 10, 11, 3, 6, 12, 13, 7, 14, 15]
console.log(root.getAllChildrenDFSByStack().map(item => item.id))
// [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
console.log(root.getAllChildrenBFSByQueue().map(item => item.id))
深度优先遍历和广度优先遍历
深度优先遍历(DFS)和广度优先遍历(BFS)是树和图搜索的两种策略,一种是纵深优先(子节点优先),一种是广度优先(父节点优先)
做最短路径搜索时,DFS耗性能,BFS耗内存
深度遍历依赖栈结构,通过不断的回溯(很像递归)将子节点优先摘取出来,二叉树里面根据根节点和左右子树节点的排列顺序分为前序(根左右),中序(左根右),后序(左右根)
广度遍历又叫层序遍历,依赖队列结构,通过队列将录入的节点依次摘出,不存在回溯过程。
递归的思想
递归的思想除了分治之外,关键在于学会抽象和隔离问题的复杂性。
比如:全世界的人口 = 我 + 我旁边的同事 + 我的家人 + 我的朋友 + ...,这就陷入了复杂问题的细节之中;
如果不考虑细节:全世界的人口 = 一个人 + 剩下来的人,就轻松很多;
如果恰好发现”剩下来的人 = 一个人 + 再次剩下来的人“,那就可以用递归了,因为问题是相似的。
递归,分治与第一性原理
第一性原理是告诉我们分析问题:首先直击本质,再具体解决各个子问题。
火星旅行票价降低100倍 = 飞船成本降低10倍 + 飞船载客量提高10倍,然后再考虑如果降低飞船成本,如何提高载客量,这是马斯克的逻辑。
这三个词的本质是一样的,告诉我们分析问题的策略:先宏观再微观,先抽象再具体。