越是結構化的有規律的數據操作起來越簡單,只是我們沒有找到規律和工具。
首先貼代碼
首先定義了一個樹結構,需求是通過任意節點遍歷出其所有的子節點。
根據需求的不同,就會有深度遍歷和廣度遍歷兩種,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倍,然后再考慮如果降低飛船成本,如何提高載客量,這是馬斯克的邏輯。
這三個詞的本質是一樣的,告訴我們分析問題的策略:先宏觀再微觀,先抽象再具體。