---
# System prepended metadata

title: Tree

---

t# 1. Tree

**ต้นไม้ (Tree)** คือ กราฟที่เชื่อมต่อกันและไม่มีวงจร มีคุณสมบัติที่ควรทราบดังนี้
- โดยประกอบด้วย $n$ โหนดและ $n − 1$ เส้นเชื่อม 
- หากนำเส้นเชื่อมออกจากต้นไม้ ใด ๆ จะทำให้กราฟแบ่งออกเป็นสองส่วน (components) 
- การเพิ่มเส้นเชื่อมเข้าไปในต้นไม้จะทำให้เกิดวงจรขึ้น
- ระหว่างโหนดใด ๆ สองโหนดในต้นไม้จะมีเส้นทางเฉพาะเจาะจงเพียงเส้นทางเดียวเสมอ

ตัวอย่างต้นไม้ที่ประกอบไปด้วย 8 จุดยอดและ 7 เส้นเชื่อม

![Screenshot 2025-03-18 at 14.31.07](https://hackmd.io/_uploads/BydHWo8nyg.png)

**ใบ (leaves)** คือ โหนดที่มีดีกรีเท่ากับ 1 หรือพูดง่าย ๆ คือมีโหนดที่ติดกันเพียงโหนดเดียว ตัวอย่างเช่น จากต้นไม้ข้างต้น ใบของต้นไม้คือโหนด $3, 5, 7$ และ $8$

**ต้นไม้ที่มีราก (Rooted Tree)** จะมีการกำหนดโหนดหนึ่งเป็นรากของต้นไม้ (Root) และโหนดอื่น ๆ จะอยู่ใต้รากต้นไม้นั้น ตัวอย่างเช่น ในต้นไม้ต่อไปนี้ โหนด 1 ถูกกำหนดให้เป็นรากของต้นไม้

![Screenshot 2025-03-18 at 14.37.53](https://hackmd.io/_uploads/SyCAMjLnJe.png)

**ความสัมพันธ์ระหว่างโหนดในต้นไม้ที่มีราก**
- ลูก (Children) ของโหนด คือ โหนดที่อยู่ระดับต่ำกว่าซึ่งเชื่อมต่อโดยตรงกับโหนดปัจจุบัน
- พ่อแม่ (Parent) ของโหนด คือ โหนดที่อยู่ระดับสูงกว่าซึ่งเชื่อมต่อโดยตรงกับโหนดปัจจุบัน
- โหนดราก (Root Node) ไม่มีโหนดพ่อแม่
- แต่ละโหนดจะมีโหนดพ่อแม่เพียงโหนดเดียวเท่านั้น ยกเว้นรากของต้นไม้ซึ่งจะไม่มีพ่อแม่
- พี่น้อง (Sibling) คือ โหนดที่มีพ่อแม่เดียวกัน
- ความลึก (Depth) คือ ระยะทางจากโหนดรากไปยังโหนดปัจจุบัน
- ความสูง (Height) คือ ระยะทางจากโหนดปัจจุบันไปยังโหนดใบที่อยู่ลึกที่สุด

ตัวอย่างเช่น ในต้นไม้ด้านบน
- โหนด 2 มีลูกเป็นโหนด 5 และ 6
- โหนด 2 มีพ่อแม่เป็นโหนด 1
- โหนด 5 และ 6 เป็นพี่น้องกัน
- โหนด 2 มีความลึกเท่ากับ 1 มีความสูงเท่ากับ 2

**โครงสร้างของต้นไม้ที่มีราก (Recursive Structure)**
โครงสร้างของต้นไม้ที่มีรากสามารถนิยามแบบเวียนเกิด (Recursive) ได้ โดย แต่ละโหนดสามารถเป็นรากของต้นไม้ย่อย (Subtree) ซึ่งมีโหนดนั้นเป็นรากและรวมโหนดลูกทั้งหมดที่อยู่ใต้โหนดนั้น

ตัวอย่างเช่น
![Screenshot 2025-03-18 at 20.07.36](https://hackmd.io/_uploads/BJEXxxP2yx.png)

ต้นไม้ย่อยที่มีโหนด $2$ เป็นราก ประกอบด้วยโหนด $2, 5, 6$ และ $8$

## 2. Tree Traversal
แม้ว่าจะสามารถใช้ อัลกอริทึมท่องกราฟทั่วไป เช่น DFS (Depth-First Search) หรือ BFS (Breadth-First Search) ในการท่องโหนดของต้นไม้ได้ แต่การท่องต้นไม้จะง่ายกว่ากราฟทั่วไป เนื่องจาก:

1. ไม่มีวัฏจักร (Cycles) – ทำให้ไม่ต้องกังวลเรื่องการกลับมาเยี่ยมโหนดเดิมซ้ำ
2. ไม่มีเส้นทางที่เชื่อมกลับ (No Multiple Paths) – ในต้นไม้ แต่ละโหนดมีเพียงเส้นทางเดียวที่สามารถใช้เดินทางจากโหนดรากไปยังโหนดใด ๆ ในต้นไม้ได้

ดังนั้น DFS จึงเป็นวิธีที่นิยมใช้ ในการท่องต้นไม้ เพราะทำงานได้ง่ายและมีประสิทธิภาพสูง

**ตัวอย่างโปรแกรม**
```cpp=
void dfs(int s, int e) {
    // ประมวลผลโหนด s (สามารถเพิ่มโค้ดสำหรับการทำงานที่ต้องการได้)

    // วนลูปผ่านโหนดที่เชื่อมต่อกับ s
    for (auto u : adj[s]) {
        if (u != e) // หลีกเลี่ยงการกลับไปโหนดก่อนหน้า
            dfs(u, s);
    }
}

// เราสามารถเริ่มต้นการท่องต้นไม้จากโหนด x โดยเรียกใช้ฟังก์ชัน
dfs(x, 0);
```

## 3. การใช้ Dynamic Programming บนต้นไม้ (Tree DP)
Dynamic Programming (DP) สามารถนำมาใช้คำนวณข้อมูลระหว่างการท่องต้นไม้ได้ เช่น:

- หาจำนวนโหนดใน subtree ของโหนดใด ๆ
- คำนวณ เส้นทางที่ยาวที่สุดจากโหนดไปยังใบไม้
- หาค่าต่าง ๆ ที่สามารถแตกออกเป็นปัญหาย่อยได้

DP บนต้นไม้สามารถทำให้การคำนวณทำได้ใน O(n) เวลาแทนที่จะต้องใช้วิธี brute force ที่อาจใช้เวลามากกว่านั้น

### 3.1 คำนวณจำนวนโหนดใน subtree
เราจะคำนวณค่า count[s] ซึ่งหมายถึงจำนวนโหนดทั้งหมดที่อยู่ใน subtree ของโหนด s รวมถึงตัวมันเอง โดยพิจารณาคุณสมบัติของ subtree ต่อไปนี้
- โหนด s เป็นรากของ subtree
- subtree ของ s ประกอบด้วยตัวมันเอง และ subtree ของลูก ๆ
- ดังนั้นสามารถใช้ DFS + DP เพื่อคำนวณค่า count[s] ได้

**ตัวอย่างโปรแกรม**
```cpp=
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;
vector<int> adj[MAXN]; // กราฟต้นไม้
int countSubtree[MAXN]; // เก็บจำนวนโหนดใน subtree ของแต่ละโหนด

void dfs(int s, int e) {
    countSubtree[s] = 1; // เริ่มต้นด้วยตัวมันเอง
    for (auto u : adj[s]) {
        if (u == e) continue; // ข้ามโหนดพ่อแม่
        dfs(u, s);
        countSubtree[s] += countSubtree[u]; // รวมขนาดของ subtree ลูก ๆ
    }
}

int main() {
    int n; cin >> n; // จำนวนโหนดในต้นไม้
    for (int i = 1; i < n; i++) { // รับข้อมูลเส้นเชื่อมของต้นไม้
        int u, v; cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    dfs(1, 0); // เริ่ม DFS จากรากของต้นไม้ (สมมติว่าโหนด 1 เป็นราก)

    for (int i = 1; i <= n; i++) {
        cout << "Subtree size of node " << i << " : " << countSubtree[i] << "\n";
    }

    return 0;
}
```

## 4. การหาค่าเส้นผ่านศูนย์กลางของต้นไม้ (Tree Diameter)
เส้นผ่านศูนย์กลางของต้นไม้ หมายถึง ความยาวของเส้นทางที่ยาวที่สุด ระหว่างสองโหนดในต้นไม้

ตัวอย่างเช่น สำหรับต้นไม้ด้านล่าง
![Screenshot 2025-03-18 at 20.27.24](https://hackmd.io/_uploads/ryOpEgP3yx.png)

เส้นผ่านศูนย์กลางของต้นไม้นี้คือ 4 ซึ่งอาจเป็นเส้นทาง $5 → 2 → 1 → 4 → 7$ หรือ $6 → 2 → 1 → 4 → 7$ เนื่องจากมีหลายเส้นทางที่มีความยาวสูงสุดเท่ากัน
![Screenshot 2025-03-18 at 20.28.39](https://hackmd.io/_uploads/r14GBeDnye.png)

### 4.1 การใช้ DP สำหรับการค่าเส้นผ่านศูนย์กลางของต้นไม้
แนวทางการแก้ปัญหาในต้นไม้ที่นิยมใช้ คือ การกำหนดรากของต้นไม้โดยสุ่ม (Rooting the Tree arbitrarily) และคำนวณค่าต่าง ๆ แยกกันในแต่ละ Subtree เมื่อเรากำหนดรากของต้นไม้แล้ว เราสามารถคำนวณคุณสมบัติต่าง ๆ ของโหนดในต้นไม้ได้อย่างมีประสิทธิภาพ

**กระบวนการ**
เราสามารถ คำนวณค่าที่สำคัญสองค่า สำหรับแต่ละโหนด x:
- `toLeaf(x):` ความยาวสูงสุดของเส้นทางจาก x ไปยังโหนดใบ (Leaf Node) ใด ๆ 
- ใช้ DFS คำนวณ `toLeaf[u] = max(toLeaf[v]) + 1` โดย `v` คือโหนดลูกที่อยู่ติดกับ `u`
- `maxLength(x):` ความยาวสูงสุดของเส้นทางที่มี x เป็นโหนดสูงสุด (Highest Point)
- ถ้าโหนด `u` มีลูกหลายตัว ให้นำ `toLeaf` ของลูกที่มีค่ามากที่สุด 2 ตัวมาบวกกัน
- ค่า `maxLength[u]` จะเป็นเส้นผ่านศูนย์กลางที่มี `u` เป็นโหนดสูงสุด

ตัวอย่าง
![Screenshot 2025-03-18 at 20.42.00](https://hackmd.io/_uploads/rkHNuxD31e.png)

**toLeaf(x):**
- toLeaf(5) = 0, toLeaf(6) = 0, toLeaf(7) = 0 (เพราะเป็น Leaf)
- toLeaf(2) = max(toLeaf(5), toLeaf(6)) + 1 = 1
- toLeaf(4) = toLeaf(7) + 1 = 1
- toLeaf(1) = max(toLeaf(2), toLeaf(3), toLeaf(4)) + 1 = 2

**maxLength(x):**
maxLength(1) = toLeaf(2) + toLeaf(4) + 2 = 4 (เป็นเส้นผ่านศูนย์กลางของต้นไม้)

**ข้อสังเกต**
- วิธีนี้ใช้แนวคิด Rooting the Tree แล้วคำนวณค่าต่าง ๆ ของแต่ละโหนดแยกกัน
- ใช้ Tree DP เพื่อคำนวณ toLeaf(x) และ maxLength(x) ผ่าน DFS
- ทำงานใน $O(n)$ เพราะมีการเยี่ยมชมโหนดแต่ละตัวเพียง 1 ครั้ง

**ตัวอย่างโปรแกรม**
```cpp=
#include <bits/stdc++.h>
using namespace std;

const int N = 100005;
vector<int> adj[N];  // Adjacency list
int toLeaf[N], maxLength[N];  // เก็บค่าต่าง ๆ สำหรับแต่ละโหนด
int diameter = 0;

void dfs(int u, int parent) {
    vector<int> childLengths;  // เก็บค่าของ `toLeaf` ของลูกแต่ละตัว
    for (int v : adj[u]) {
        if (v == parent) continue;
        dfs(v, u);
        toLeaf[u] = max(toLeaf[u], toLeaf[v] + 1);
        childLengths.push_back(toLeaf[v] + 1);
    }

    // ถ้ามีลูกมากกว่า 2 ให้เรียงลำดับหา 2 ค่ามากสุด
    if (childLengths.size() >= 2) {
        sort(childLengths.rbegin(), childLengths.rend());  // เรียงจากมากไปน้อย
        maxLength[u] = childLengths[0] + childLengths[1];  // เอาสองค่ามากสุดมาบวกกัน
    } else if (!childLengths.empty()) {
        maxLength[u] = childLengths[0];
    }

    // อัปเดตค่าเส้นผ่านศูนย์กลางของต้นไม้
    diameter = max(diameter, maxLength[u]);
}

int main() {
    int n; cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    dfs(1, -1);  // เริ่ม DFS ที่โหนดราก (สมมติให้เป็นโหนด 1)
    cout << "Diameter of the tree: " << diameter << "\n";
    return 0;
}
```

### 4.2 การหาค่าเส้นผ่านศูนย์กลางของต้นไม้โดยใช้ DFS สองครั้ง
อัลกอริทึม DFS 2 ครั้งสามารถหาเส้นผ่านศูนย์กลางของต้นไม้ได้เพราะอาศัยหลักการที่ว่าโหนดที่อยู่ไกลที่สุดจากโหนดเริ่มต้นต้องเป็นจุดปลายด้านหนึ่งของเส้นผ่านศูนย์กลางของต้นไม้ โดยเริ่มจากการเลือกโหนด a แบบสุ่มและใช้ DFS ครั้งที่หนึ่งเพื่อหาโหนด b ที่อยู่ไกลที่สุดจาก a ซึ่งรับประกันได้ว่า b ต้องเป็นส่วนหนึ่งของเส้นผ่านศูนย์กลาง จากนั้นใช้ DFS ครั้งที่สองเริ่มจาก b เพื่อหาโหนด c ที่อยู่ไกลที่สุด ซึ่ง c จะต้องเป็นปลายอีกด้านหนึ่งของเส้นผ่านศูนย์กลางเสมอ

**กระบวนการ**
1. เลือกโหนด a ใด ๆ เป็นจุดเริ่มต้น และใช้ Depth-First Search (DFS) เพื่อหาว่า โหนด b ที่อยู่ไกลที่สุดจาก a คืออะไร
2. ใช้ DFS อีกครั้งจาก b เพื่อหา โหนด c ที่อยู่ไกลที่สุดจาก b
3. ค่าระยะทางจาก b ไป c คือเส้นผ่านศูนย์กลางของต้นไม้

ตัวอย่าง
![Screenshot 2025-03-18 at 21.25.37](https://hackmd.io/_uploads/HkGdG-whyg.png)

**ตัวอย่างโปรแกรม**
```cpp=
#include <bits/stdc++.h>
using namespace std;

const int N = 100005;
vector<int> adj[N];
int farthestNode, maxDistance;

// DFS เพื่อหาโหนดที่อยู่ไกลที่สุด
void dfs(int u, int parent, int depth) {
    if (depth > maxDistance) {
        maxDistance = depth;
        farthestNode = u;
    }
    for (int v : adj[u]) {
        if (v == parent) continue;
        dfs(v, u, depth + 1);
    }
}

int findTreeDiameter(int start) {
    maxDistance = -1;
    dfs(start, -1, 0);  // DFS ครั้งที่ 1 หาค่า farthestNode (โหนด b)

    maxDistance = -1;
    dfs(farthestNode, -1, 0);  // DFS ครั้งที่ 2 จากโหนด b เพื่อหาโหนด c และเส้นผ่านศูนย์กลาง
    return maxDistance;
}

int main() {
    int n;
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    cout << "Diameter of the tree: " << findTreeDiameter(1) << "\n";
    return 0;
}
```

## ตัวอย่างโจทย์
- https://leetcode.com/problems/diameter-of-binary-tree/description/

# 5. ต้นไม้ทวิภาค (Binary Tree)
ต้นไม้ทวิภาค (Binary Tree) เป็นต้นไม้แบบมีราก (Rooted Tree) ที่แต่ละโหนดมีโหนดลูกได้ไม่เกินสองโหนด ซึ่งประกอบด้วย ซ้าย (Left Subtree) และ ขวา (Right Subtree) โดยอาจเป็นไปได้ว่าโหนดลูกใดลูกหนึ่งหรือทั้งสองอาจว่างเปล่า (ไม่มีโหนดลูกเลย) ดังนั้นแต่ละโหนดในต้นไม้ทวิภาคสามารถมีลูกได้ 0, 1 หรือ 2 โหนดเท่านั้น

ตัวอย่าง
![Screenshot 2025-03-18 at 21.54.35](https://hackmd.io/_uploads/H1q4tZvn1x.png)

## 5.1 ประเภทของต้นไม้ทวิภาค

**ต้นไม้ทวิภาคแบบเต็ม (Full Binary Tree)** คือ ต้นไม้ทวิภาคที่ทุกโหนดจะต้องมีลูกสองโหนดหรือไม่มีลูกเลย กล่าวคือ ไม่มีโหนดใดที่มีลูกเพียงโหนดเดียว ต้นไม้ประเภทนี้มักใช้ในโครงสร้างข้อมูลที่ต้องการความเป็นระเบียบและช่วยให้การทำงานของอัลกอริทึมที่เกี่ยวข้องกับต้นไม้มีประสิทธิภาพมากขึ้น

**ต้นไม้ทวิภาคแบบสมบูรณ์ (Complete Binary Tree)** เป็นต้นไม้ทวิภาคที่ทุกระดับของต้นไม้จะต้องเต็ม ยกเว้นระดับสุดท้ายซึ่งอาจไม่เต็ม แต่ต้องเติมจากซ้ายไปขวาให้มากที่สุด ต้นไม้ประเภทนี้นิยมใช้ในการสร้าง Heap ซึ่งเป็นโครงสร้างข้อมูลที่ใช้จัดลำดับความสำคัญ (Priority Queue) เนื่องจากการแทรกและการลบโหนดสามารถทำได้ในเวลา $O(\log n)$

**ต้นไม้ทวิภาคแบบสมบูรณ์แบบ (Perfect Binary Tree)** คือ ต้นไม้ที่ทุกโหนดภายใน (Internal Nodes) มีลูกสองโหนดทั้งหมด และทุกโหนดใบ (Leaf Nodes) อยู่ในระดับเดียวกัน ต้นไม้ประเภทนี้มีโครงสร้างที่สมดุลอย่างสมบูรณ์และมักใช้ในอัลกอริทึมที่ต้องการความสม่ำเสมอของโครงสร้างต้นไม้ เช่น Divide and Conquer Algorithm ซึ่งอาศัยการแบ่งปัญหาออกเป็นส่วน ๆ ที่มีขนาดเท่ากัน

**ต้นไม้ทวิภาคแบบสมดุล (Balanced Binary Tree)** เป็นต้นไม้ทวิภาคที่มีความสูงของซับทรีฝั่งซ้ายและฝั่งขวาแตกต่างกันไม่เกิน 1 เพื่อให้สามารถดำเนินการต่าง ๆ เช่น ค้นหา (Search), แทรก (Insert) และลบ (Delete) ได้ในเวลา $O(\log n)$ ต้นไม้ประเภทนี้ถูกนำไปใช้ใน **AVL Tree** และ **Red-Black Tree** ซึ่งเป็นต้นไม้ที่สามารถปรับสมดุลได้อัตโนมัติเมื่อมีการแทรกหรือลบข้อมูล ทำให้ต้นไม้ยังคงมีโครงสร้างที่เหมาะสมสำหรับการดำเนินการต่าง ๆ

**ต้นไม้ทวิภาคแบบเสื่อม (Degenerate Binary Tree)** หรือที่เรียกอีกชื่อว่า ต้นไม้เอนเอียง (Skewed Tree) เป็นต้นไม้ที่แต่ละโหนดมีลูกเพียงโหนดเดียวเท่านั้น อาจเป็นลูกฝั่งซ้ายทั้งหมดหรือฝั่งขวาทั้งหมด ทำให้ต้นไม้มีโครงสร้างคล้ายกับ Linked List ซึ่งส่งผลให้การดำเนินการต่าง ๆ เช่น การค้นหาใช้เวลา $O(n)$ ในกรณีที่แย่ที่สุด ต้นไม้ประเภทนี้มักเกิดจากการแทรกข้อมูลใน Binary Search Tree (BST) โดยไม่มีการบาลานซ์โครงสร้าง ดังนั้นต้นไม้ที่สมดุลเช่น AVL Tree และ Red-Black Tree จึงถูกนำมาใช้เพื่อแก้ปัญหานี้

## 5.2 การแทนต้นไม้ทวิภาค (Binary Tree Representation) 
ต้นไม้ทวิภาค (Binary Tree) สามารถแทนอยู่ในหน่วยความจำของคอมพิวเตอร์ได้หลายวิธี โดยทั่วไปสามารถแบ่งออกเป็นสองแนวทางหลัก ได้แก่ การใช้โครงสร้างข้อมูลแบบลิงก์ (Linked Representation) และ การใช้อาร์เรย์ (Array Representation) แต่ละวิธีมีข้อดีและข้อเสียที่แตกต่างกันตามลักษณะของปัญหาที่ต้องการแก้ไข

### 5.2.1 การแทนต้นไม้ทวิภาคด้วยโครงสร้างข้อมูลแบบเชื่อมต่อ (Linked Representation) 
วิธีนี้ใช้โครงสร้างแบบ โหนด (Node) ซึ่งมีพอยน์เตอร์ชี้ไปยังลูกซ้ายและลูกขวาของแต่ละโหนด โครงสร้างพื้นฐานของโหนดในต้นไม้ทวิภาคสามารถนิยามได้ดังนี้
```cpp=
struct Node {
    int data;       // ข้อมูลที่เก็บในโหนด
    Node* left;     // พอยน์เตอร์ชี้ไปยังลูกซ้าย
    Node* right;    // พอยน์เตอร์ชี้ไปยังลูกขวา

    Node(int val) { // คอนสตรักเตอร์กำหนดค่าเริ่มต้น
        data = val;
        left = right = nullptr;
    }
};
```
การสร้างต้นไม้สามารถทำได้โดยการกำหนดค่าของรากและเชื่อมโยงลูกซ้ายและลูกขวาแบบไดนามิก เช่น

```cpp=
int main() {
    Node* root = new Node(1);  // สร้างรากของต้นไม้
    root->left = new Node(2);  // สร้างลูกซ้ายของราก
    root->right = new Node(3); // สร้างลูกขวาของราก
    root->left->left = new Node(4);
    root->left->right = new Node(5);
    root->right->right = new Node(6);

    return 0;
}
```

**ข้อดีของการใช้โครงสร้างแบบลิงก์**
- มีความยืดหยุ่นสูง สามารถปรับขนาดของต้นไม้ได้โดยไม่มีข้อจำกัดด้านหน่วยความจำที่ต่อเนื่อง
- รองรับต้นไม้ที่ไม่สมบูรณ์ ต้นไม้ที่มีโหนดบางส่วนว่างเปล่าสามารถแทนได้อย่างมีประสิทธิภาพ
- รองรับการเปลี่ยนแปลงโครงสร้าง การเพิ่มหรือลบโหนดทำได้สะดวกโดยไม่ต้องเลื่อนข้อมูลในอาร์เรย์

**ข้อเสีย**
- ใช้หน่วยความจำมากกว่าวิธีอาร์เรย์ เนื่องจากต้องเก็บพอยน์เตอร์เพิ่มเติม
- เข้าถึงข้อมูลแบบสุ่ม (Random Access) ทำได้ช้ากว่าอาร์เรย์

### 5.2.2 การแทนต้นไม้ทวิภาคด้วยอาร์เรย์ (Array Representation)
วิธีนี้ใช้ อาร์เรย์ (Array) แทนต้นไม้โดยกำหนดให้รากอยู่ที่ดัชนีที่ 1 หรือ 0 แล้วใช้กฎต่อไปนี้เพื่อกำหนดความสัมพันธ์ระหว่างโหนดพ่อแม่กับลูก

โหนดพ่อแม่ที่ตำแหน่ง i
- ลูกซ้ายอยู่ที่ ตำแหน่ง 2*i + 1
- ลูกขวาอยู่ที่ ตำแหน่ง 2*i + 2

โหนดลูกที่ดัชนี j
- โหนดพ่อแม่อยู่ที่ ตำแหน่ง (j-1)/2

ตัวอย่างโครงสร้างของต้นไม้ที่แทนด้วยอาร์เรย์

```cpp=
#include <iostream>
using namespace std;

const int MAX_SIZE = 100; // ขนาดสูงสุดของต้นไม้

int tree[MAX_SIZE];

// ฟังก์ชันแทรกโหนดที่ตำแหน่ง i
void insert(int i, int value) {
    if (i < MAX_SIZE)
        tree[i] = value;
}

// แสดงค่าของต้นไม้ที่ถูกแทนในอาร์เรย์
void printTree(int n) {
    for (int i = 0; i < n; i++)
        cout << tree[i] << " ";
    cout << endl;
}

int main() {
    insert(0, 1); // ราก
    insert(1, 2); // ลูกซ้ายของราก
    insert(2, 3); // ลูกขวาของราก
    insert(3, 4); // ลูกซ้ายของโหนดที่ 2
    insert(4, 5); // ลูกขวาของโหนดที่ 2
    insert(6, 6); // ลูกขวาของโหนดที่ 3

    printTree(7);
    return 0;
}
```

### 5.2.3 การเปรียบเทียบระหว่างการแทนต้นไม้
| คุณสมบัติ         | โครงสร้างลิงก์ (Linked Representation) | อาร์เรย์ (Array Representation) |
|-------------------|--------------------------------|--------------------------------|
| **การใช้หน่วยความจำ** | ใช้พอยน์เตอร์เพิ่มหน่วยความจำมากขึ้น | ใช้หน่วยความจำอย่างมีประสิทธิภาพหากต้นไม้เต็ม |
| **การเข้าถึงข้อมูล** | ต้องใช้การค้นหาแบบ DFS หรือ BFS | สามารถเข้าถึงข้อมูลได้โดยใช้ตำแหน่ง |
| **ความเร็วในการดำเนินการ** | เพิ่ม/ลบโหนดได้เร็ว แต่ค้นหาช้ากว่าอาร์เรย์ | เข้าถึงข้อมูลเร็ว แต่เพิ่ม/ลบโหนดยาก |


## 5.3 การเรียงลำดับโหนดในต้นไม้ทวิภาค (Tree Traversal)
ต้นไม้ทวิภาคสามารถทำการเยี่ยมชมโหนดได้สามแบบหลัก ซึ่งแตกต่างกันตามลำดับการดำเนินการ:

**1. Pre-order Traversal (ก่อนลำดับ)**
ดำเนินการที่โหนดรากก่อน → เยี่ยมชมซับทรีทางซ้าย → เยี่ยมชมซับทรีทางขวา
ลำดับของโหนด: [1, 2, 4, 5, 6, 3, 7]
**2. In-order Traversal (กลางลำดับ)**
เยี่ยมชมซับทรีทางซ้ายก่อน → ดำเนินการที่โหนดราก → เยี่ยมชมซับทรีทางขวา
ลำดับของโหนด: [4, 2, 6, 5, 1, 3, 7]
**3. Post-order Traversal (หลังลำดับ)**
เยี่ยมชมซับทรีทางซ้าย → เยี่ยมชมซับทรีทางขวา → ดำเนินการที่โหนดราก
ลำดับของโหนด: [4, 6, 5, 2, 7, 3, 1]

ตัวอย่าง
![Screenshot 2025-03-18 at 21.54.35](https://hackmd.io/_uploads/H1q4tZvn1x.png)

**ตัวอย่างโปรแกรม**
```cpp=
#include <iostream>
using namespace std;

// โครงสร้างของโหนดใน Binary Tree
struct Node {
    int data;
    Node* left;
    Node* right;
    
    Node(int val) {
        data = val;
        left = right = nullptr;
    }
};

// ฟังก์ชัน Pre-order Traversal
void preOrder(Node* root) {
    if (!root) return;
    cout << root->data << " ";
    preOrder(root->left);
    preOrder(root->right);
}

// ฟังก์ชัน In-order Traversal
void inOrder(Node* root) {
    if (!root) return;
    inOrder(root->left);
    cout << root->data << " ";
    inOrder(root->right);
}

// ฟังก์ชัน Post-order Traversal
void postOrder(Node* root) {
    if (!root) return;
    postOrder(root->left);
    postOrder(root->right);
    cout << root->data << " ";
}

int main() {
    // สร้าง Binary Tree ตามตัวอย่าง
    Node* root = new Node(1);
    root->left = new Node(2);
    root->right = new Node(3);
    root->left->left = new Node(4);
    root->left->right = new Node(5);
    root->right->right = new Node(7);
    root->left->right->left = new Node(6);

    cout << "Pre-order Traversal: ";
    preOrder(root);
    cout << "\n";

    cout << "In-order Traversal: ";
    inOrder(root);
    cout << "\n";

    cout << "Post-order Traversal: ";
    postOrder(root);
    cout << "\n";

    return 0;
}
```

## 5.4 Binary Search Tree (BST)
Binary Search Tree (BST) เป็นโครงสร้างข้อมูลต้นไม้ทวิภาคที่มีคุณสมบัติพิเศษเพิ่มเติม คือ:
- ค่าของโหนดซ้าย (Left Subtree) ต้องน้อยกว่าค่าของโหนดปัจจุบัน
- ค่าของโหนดขวา (Right Subtree) ต้องมากกว่าค่าของโหนดปัจจุบัน
- กฎนี้ต้องเป็นจริงสำหรับทุกโหนดในต้นไม้

ตัวอย่าง 

![Binary_search_tree.svg](https://hackmd.io/_uploads/HJ7domDn1l.png)

BST เป็นโครงสร้างข้อมูลที่มีประโยชน์ในการค้นหาข้อมูลอย่างมีประสิทธิภาพ โดยใช้กฎที่ว่า ค่าทางซ้ายต้องน้อยกว่าโหนดปัจจุบัน และค่าทางขวาต้องมากกว่าโหนดปัจจุบัน ซึ่งช่วยให้การแทรก, ค้นหา และลบข้อมูลเป็นไปได้อย่างรวดเร็วในเวลาเฉลี่ย $O(\log n)$ อย่างไรก็ตาม หาก BST ไม่สมดุล จะมีประสิทธิภาพลดลงเหลือ $O(n)$ ในกรณีที่แย่ที่สุด

## ตัวอย่างโจทย์
- https://leetcode.com/problems/binary-tree-inorder-traversal/
- https://leetcode.com/problems/same-tree/description/
- https://leetcode.com/problems/maximum-depth-of-binary-tree
- https://leetcode.com/problems/balanced-binary-tree/
- https://leetcode.com/problems/merge-two-binary-trees/description/


# อ้างอิง

- https://cses.fi/book/book.pdf
- https://github.com/tchomphoochan/toi14-tutorial/tree/master
- https://visualgo.net/en
- https://programming.in.th/
