iT邦幫忙

2026 iThome 鐵人賽

DAY 7
2

前幾篇我們一直在問一個很類似的問題:

資料要怎麼拿?

  • Queue 說:最早進來的先拿
  • Stack 說:最後進來的先拿
  • Priority Queue 說:最重要的先拿
  • Hash Map 更直接:我知道 key,直接找到對應的 value

但今天,我們要換一個方向。
因為有些資料的重心放在:

它和其他資料之間,是什麼關係?

最容易看到這種關係的地方,就是日常都會使用的資料夾。


從一個普通的資料夾開始

假設電腦裡有這樣的結構:

Documents
├─ Work
│  ├─ report.pdf
│  └─ meeting.md
│
└─ Personal
   ├─ photos
   └─ notes.md

如果只把這些資料全部攤平,我們可能得到:

Documents
Work
report.pdf
meeting.md
Personal
photos
notes.md

資料都還在,但是一些很重要的資訊不見了:

  • report.pdf 屬於 Work
  • meeting.md 屬於 Work
  • photos 屬於 Personal
  • notes.md 屬於 Personal
  • WorkPersonal 又都位於 Documents 底下

也就是說,真正重要的不只是「有哪些資料」。

還包括:

這些資料彼此之間的上下關係。

而這種結構,就符合資料結構中的Tree


為什麼叫 Tree?

如果把剛才的資料夾畫成節點:

Documents
├─ Work
│  ├─ report.pdf
│  └─ meeting.md
└─ Personal
   ├─ photos
   └─ notes.md

我們可以把每一個資料夾或檔案想成一個 node(節點)
節點之間,再用關係連接起來。
例如:

Documents
   │
   ├──── Work
   │      │
   │      ├──── report.pdf
   │      └──── meeting.md
   │
   └──── Personal
          │
          ├──── photos
          └──── notes.md

這種從一個起點往下不斷分支的形狀,看起來就像一棵倒過來的樹。
因此我們稱它為Tree。


Tree 的起點:Root

一棵樹通常會有樹根(Root)。
從樹根開始的話大概是這樣
https://ithelp.ithome.com.tw/upload/images/20260902/201290202Wz9gEuGeT.jpg

假設 Documents 這個節點視為 Root,所有其他節點,都可以從 Root 一路往下找到。
例如:

Documents
→ Work
→ report.pdf

或:

Documents
→ Personal
→ notes.md

這和 Array 不一樣,Array 通常關心的是:

index 0
index 1
index 2
index 3

但 Tree 關心的是:

誰在誰下面?
誰屬於誰?

Parent 與 Child

來看這段:

Documents
└─ Work
   ├─ report.pdf
   └─ meeting.md

Work 而言 Documents 是它的 parent
而:

report.pdf
meeting.md

則是 Workchildren
所以:

Documents
   │
   └─ Work
      │
      ├─ report.pdf
      └─ meeting.md

可以理解成:

Documents
   ↓ parent of

Work
   ↓ parent of

report.pdf
meeting.md

反過來看 WorkDocuments 的 child。
report.pdf 又是 Work 的 child。
樹 Tree 就是透過這樣一層一層的 parent-child relationship,把階層關係保存下來。


沒有 Child 的節點:Leaf

再來看看:

report.pdf
meeting.md
photos
notes.md

這些節點下面已經沒有其他節點,這種節點通常稱為 leaf node
也就是:

沒有 child 的節點。

例如:

Documents
├─ Work
│  ├─ report.pdf   ← leaf
│  └─ meeting.md   ← leaf
│
└─ Personal
   ├─ photos       ← leaf
   └─ notes.md     ← leaf

如果把 Tree 想像成一棵真的樹:

root
↓
branches
↓
leaves

leaf 這個名稱就很直覺了。


Tree 描述的是 Hierarchy

現在我們再回頭看整個結構:

Documents
├─ Work
│  ├─ report.pdf
│  └─ meeting.md
│
└─ Personal
   ├─ photos
   └─ notes.md

這裡真正被保存的,其實是一種 hierarchy (階層關係)
例如:

Documents
↓
Work
↓
report.pdf

代表 report.pdf 屬於 WorkWork 又屬於 Documents
這也是為什麼 Tree 非常適合描述具有上下層級的問題。


Tree 不只出現在資料夾

資料夾只是最好理解的例子,Tree 其實到處都存在。
例如公司組織:

CEO
├─ Engineering
│  ├─ Frontend
│  └─ Backend
│
└─ Sales
   ├─ Domestic
   └─ International

或者網站選單:

Products
├─ Computers
│  ├─ Laptop
│  └─ Desktop
│
└─ Phones
   ├─ Android
   └─ iPhone

商品分類也一樣:

Electronics
├─ Computer
│  ├─ Laptop
│  └─ Monitor
│
└─ Audio
   ├─ Headphones
   └─ Speakers

甚至前端工程師很熟悉的 DOM,也可以用類似方式理解:

<body>
  <main>
    <h1>Hello</h1>
    <p>Welcome</p>
  </main>
</body>

可以抽象成:

body
└─ main
   ├─ h1
   └─ p

這些問題共同具有一個特徵:

資料之間存在明確的上下階層。


Subtree:一棵 Tree 裡面,還可以有另一棵 Tree

再來看這個結構:

Documents
├─ Work
│  ├─ report.pdf
│  └─ meeting.md
│
└─ Personal
   ├─ photos
   └─ notes.md

如果我們暫時只看:

Work
├─ report.pdf
└─ meeting.md

你會發現:
它自己其實也完全符合 Tree 的形狀。

這種從某個節點開始、連同它底下所有 descendants 所形成的結構,就可以視為一個 subtree (分支)
例如整棵樹:

Documents
├─ Work
│  ├─ report.pdf
│  └─ meeting.md
└─ Personal
   ├─ photos
   └─ notes.md

其中:

Work
├─ report.pdf
└─ meeting.md

是一個 subtree,而:

Personal
├─ photos
└─ notes.md

也是另一個 subtree。
很多 Tree 的問題,其實都可以理解成:

把大問題拆成一個又一個比較小的 subtree。

這也是為什麼未來談到 recursion 時,Tree 會是一個非常自然的應用場景。
不過今天我們先不深入 recursion。
現在只需要記住:

一棵樹的某個分支,本身也可以看成另一棵樹。


Tree 不只是存資料,我們還得「走過它」

假設現在我要找 notes.md,我們可以從 Documents 開始。
先看到:

Work
Personal

接著進入 Personal,再找到 notes.md
也就是:

Documents
→ Personal
→ notes.md

問題開始變成:

我要按照什麼順序拜訪這些節點?

這件事情稱為 traversal
也就是:

按照某種規則走過 Tree 裡的節點。


最簡單的 Traversal

例如我們可以從 root 開始,一路把每個節點印出來。
假設用 JavaScript 表示:

const fileTree = {
  name: "Documents",
  children: [
    {
      name: "Work",
      children: [
        { name: "report.pdf", children: [] },
        { name: "meeting.md", children: [] }
      ]
    },
    {
      name: "Personal",
      children: [
        { name: "photos", children: [] },
        { name: "notes.md", children: [] }
      ]
    }
  ]
};

我們可以寫一個很簡單的函式:

function traverse(node) {
  console.log(node.name);

  for (const child of node.children) {
    traverse(child);
  }
}

然後:

traverse(fileTree);

可能得到:

Documents
Work
report.pdf
meeting.md
Personal
photos
notes.md

這裡暫時不用急著記住這段程式,重要的是:

先處理目前節點
↓
再處理它的 child
↓
child 又繼續處理自己的 child

也就是沿著 Tree 的結構,一層一層走下去。
至於:

  • 應該先一路往下?
  • 還是應該先把同一層看完?

這會牽涉到不同的 traversal strategy。
我們之後進入搜尋演算法時,還會再次遇到這個問題。


Tree 和前面的資料結構有什麼不同?

到這裡,可以回頭看看前面幾天學過的東西。

  • Queue 關心的是誰先進來? 所以規則是:FIFO
  • Stack 關心的是誰最後進來? 所以規則是:LIFO
  • Priority Queue 關心的是誰比較重要?
  • Hash Map 關心的是我知道 key,怎麼快速找到 value?

這些資料結構當然也存在資料之間的組織方式。
但是對使用者而言,我們前幾篇重心都放在:

資料要怎麼放,以及下一筆資料要怎麼拿。

Tree 開始出現一個很明顯的轉變。
我們真正想保存的是:

  • A 是 B 的 parent
  • B 是 C 的 parent
  • C 屬於 B
  • B 又屬於 A

也就是:

資料之間的關係,本身就是問題的一部分。


如果只用 Array 呢?

假設我們只存:

const files = [
  "Documents",
  "Work",
  "report.pdf",
  "meeting.md",
  "Personal",
  "photos",
  "notes.md"
];

資料確實都存在。
但現在問:

  • report.pdf 位於哪個資料夾?
  • Work 底下有哪些東西?
  • Personal 的 parent 是誰?

單純從這個 Array 裡,很難回答。
因為我們只保存了「有哪些資料」,卻沒有保存「資料彼此之間的關係」。
我們當然可以另外增加資訊:

{
  name: "report.pdf",
  parent: "Work"
}

但是當這些關係越來越重要時,我們其實已經開始在建立某種 Tree structure。
所以資料結構真正重要的從來不只是:

能不能把資料塞進去。

而是:

這個表述方式,有沒有保留問題真正重要的資訊。


Tree 本質上是在描述「一對多」的階層

例如:

Documents
├─ Work
└─ Personal

一個 Documents 可以有很多 children。
Work 又可以有:

Work
├─ report.pdf
└─ meeting.md

因此每個節點都可以繼續往下分支。
但 Tree 通常還有一個很重要的限制:

一個節點只會沿著一條階層路徑往上回到 root。

例如:

report.pdf
↑
Work
↑
Documents

它不會同時又屬於 Personal,否則原本清楚的 parent-child hierarchy 就開始改變了。


但真實世界不一定只有上下關係

現在假設我們不再描述資料夾。
而是在描述四個人:

A
B
C
D

他們彼此認識:

https://ithelp.ithome.com.tw/upload/images/20260903/20129020f1WIoykmYg.png

這時候就很難說:

  • 誰是 root?
  • 誰是誰的 parent?
  • 誰是誰的 child?

A 可以跟 B 有關係。
A 也可以跟 C 有關係。
A 還可以直接跟 D 有關係。
而且:

A
→ B
→ D
→ C
→ A

最後甚至可能又回到原本的位置。
這已經不是單純的上下階層,而是一張關係網


Tree 很適合描述階層,但世界往往比 Tree 更複雜

Tree 給了我們一種非常重要的 problem representation:

root
↓
parent
↓
child
↓
leaf

它非常適合處理:

資料夾
公司組織
DOM
選單
商品分類

因為這些問題都具有相對清楚的 hierarchy。
但我們很快就會發現,有很多現實問題不是這樣。
例如:

朋友關係
道路
捷運
航班
網站連結
電腦網路
dependency

一個節點可以同時和很多節點發生關係。
甚至可能一路走出去,最後又回到原本的位置
像這樣:

https://ithelp.ithome.com.tw/upload/images/20260903/20129020BS2SAhd7cG.png

這時候,我們就不能只靠 Tree 來描述了。
因為 Tree 只是一種比較特殊、限制比較明確的關係結構。
下一篇,我們就要把這些限制拿掉,從上下階層走向任意關係
也就是第一章最後一個非常重要的資料結構 Graph
而當 Graph 出現之後,我們下一個需要考量的問題也會跟著出現:

在這麼多可能的路徑裡,我們到底要怎麼找到自己想要的東西?


上一篇
Day 5|通訊錄為什麼不用從第一個人開始找?
下一篇
Day 7|捷運路網為什麼不是一棵 Tree?
系列文
生活中的資料結構與演算法:30 天學會把現實問題變成可推理的模型13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言