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倍,然后再考慮如果降低飛船成本,如何提高載客量,這是馬斯克的邏輯。
這三個詞的本質是一樣的,告訴我們分析問題的策略:先宏觀再微觀,先抽象再具體。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM