樹的走訪:寬度優先走訪(BFS)、深度優先走訪(DFS)

樹(Tree)演算法的筆記

· 3 min read

走訪的意思是,從根節點開始,逐個走訪各個節點,不同的拜訪順序,在程式碼的實作有很大的落差。

依照走訪原則,可以分為兩種:

  1. 寬度優先走訪 Breadth-First Tree Traversal
  2. 深度優先走訪 Depth-First Tree Traversal(PreOrder, InOrder, PostOrder)

一、寬度優先走訪 Breadth-First Tree Traversal

寬度優先的概念其實很簡單,從根開始,同一層都先走訪完之後,再進入下一層:如圖:level 1 的 1 開始,走訪完後進入 level 2 走訪 2, 3,再進入 level 走訪 4, 5, 6, 7。

以上圖為例,這個樹狀圖有四層,分別為 第一層: 2 第二層: 7, 5 第三層: 2, 10, 6, 9 第四層: 5, 11, 4

走訪的順序就會是: [ 2, 7, 5, 2, 10, 6, 9, 2, 11, 4 ]

用 JavaScript 實作寬度優先走訪

實作一個 BFTT 函式,input 為 Tree,output 為依照 寬度優先走訪 的順序組成 result 陣列。

有一個 Tree,每個節點都叫做 TreeNodeTreeNode 有兩個屬性 valuechildren,value 為數字,children 為 TreeNode 陣列或是 null

Tree Node 型別
/**
 * @typedef { Object } TreeNode
 * @property { number } value
 * @property { Array[TreeNode] | null }
 * /
Tree
const tree = {
  value: 2,
  children: [
    { 
      value: 7,
      children: [
        { value: 2, children: null },
        { value: 10, children: null },
        {
          value: 6,
          children: [
            { value: 5, children: null },
            { value: 11, children: null },
          ],
        },
      ],
    },
    {
      value: 5,
      children: [
        {
          value: 9,
          children: [
            { value: 4, children: null }
          ],
        },
      ],
    },
  ],
};

思路

一層層遍歷 children,存進 queue,然後依照 queue 的特性:先進先出,從 queue 內排序第一的開始處理,處理完則離開 queue。

完成程式碼 - GitHub

function BFTT(root) {
  const result = []
  const queue = []

  queue.push(root)

  const traversal = (tree) => {
    result.push(tree.value)
    const subTree = tree.children
    if (subTree) {
      for (let i = 0; i < subTree.length; i++)
        queue.push(subTree[i])

    }
  }

  while (queue.length !== 0) {
    traversal(queue[0])
    queue.shift()
  }

  return result
}
const result = BFTT(tree)
console.log(result)

// [2, 7, 5, 2, 10, 6, 9, 2, 11, 4];

二、Depth-First Tree Traversal(深度優先走訪)

深度優先走訪 根據每個 subTree 的走訪順序,又可以區分為三種:Pre-OrderIn-OrderPost-Order

1. Pre-Order

先遇到的節點先處理。

順序:root, left, right

從圖片中可以看到從根節點 F 開始,藍色為走訪順序,若顯示紅色則為無法再走下去的情況,這時就會則回到上一層繼續走訪,直到沒辦法再繼續下去。

完整程式碼 - GitHub

實作方法:先遇到的節點先走訪,並採用遞迴方式,若有 left, right 節點則繼續遞迴。

function preOrder(root) {
  const result = []

  const traversal = (node) => {
    result.push(node.value)
    if (node.left)
      traversal(node.left)
    if (node.right)
      traversal(node.right)
  }

  traversal(root)

  return result
}

// PreOrder: ["F", "B", "A", "D", "C", "E", "G", "I", "H"]

2. In-Order

順序: left, root, right

從圖片中可以看到從根節點 F 開始,藍色為走訪順序,若該節點為目前最左邊的節點,則推入 Inorder 陣列中;若顯示紅色則為無法再走下去的情況,這時就會則繼續尋找最左邊的節點,直到沒辦法再繼續下去。

完整程式碼 - GitHub

實作方法:越左的節點先走訪,並採用遞迴方式,若有 left, right 節點則繼續遞迴。

function InOrder(root) {
  const result = []

  const traversal = (node) => {
    if (node.left)
      traversal(node.left)
    result.push(node.value)
    if (node.right)
      traversal(node.right)
  }

  traversal(root)
  return result
}
// // ["A", "B", "C", "D", "E", "F", "G", "H", "I"];

3. Post-Order

順序:left, root, right

從圖片中可以看到從根節點 F 開始,藍色為走訪順序,若該節點為目前最左邊的節點,則推入 Postorder 陣列中,若找不到最左節點,就會將右邊的節點推入;若顯示紅色則為無法再走下去的情況,這時就會則繼續尋找,直到沒辦法再繼續下去。

完整程式碼 - GitHub

實作方法:越左的節點先走訪,並採用遞迴方式,若有 left, right 節點則繼續遞迴。

function PostOrder(root) {
  const queue = []

  const traversal = (node) => {
    if (node.left)
      traversal(node.left)
    if (node.right)
      traversal(node.right)

    queue.push(node.value)
  }

  traversal(root)

  return queue
}
// ['A', 'C', 'E', 'D', 'B', 'H', 'I', 'G', 'F']