按下F5
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- tags: 資料結構, LeetCode disqus: HackMD --- # 樹(Tree) :::spoiler 文章目錄 [toc] ::: ## 介紹 在計算機科學中,樹是一種用來組織和儲存資料的抽象資料結構。樹是由節點和連接它們的邊構成的,其中一個節點被稱為根節點,它沒有父節點。其它節點可以有一個或多個父節點和零個或多個子節點。 樹是一種分層資料結構,因為它的節點在不同的層級上。從根節點開始,可以透過沿著邊向下移動來造訪樹中的每個節點。每個節點可以有任意數量的子節點,但每個子節點只能有一個父節點。 ![](https://i.imgur.com/KTkFjEL.png) 在資料結構中,樹通常是**二元搜尋樹**,每個節點最多有兩個子節點,分別稱為**左子節點**和**右子節點**。二元搜尋樹是最常用的樹之一,因為它們易於實作且非常高效。二元搜尋樹可以用於實現搜尋樹、`Heap`、霍夫曼編碼(Huffman Coding)等資料結構。 ![](https://i.imgur.com/zSLeikA.png) (此為二元搜尋樹`Binary Search tree`) ### 定義 * 樹是一個沒有`loop`的`Graph`,所以下圖不是一個樹 ![](https://i.imgur.com/YfXpLzH.png) * 樹必須有一個,也只能有一個根`root` ## 真實應用 * 文件系統中的文件夾 * 電子郵件信箱中的文件夾 * `HTML`文檔中的`DOM(Document Object Model)`樹 * 自然語言處理 下圖為`DOM`樹範例 ![](https://i.imgur.com/Avtp8Mr.jpg) (圖片來自於[[資料結構] 樹 Tree](https://w3c.hexschool.com/blog/bc00a810)) ## 建立樹的基本要點 1. **確定樹的根節點**:樹的根節點是整棵樹的開始,通常是從根節點開始進行操作。如果樹是空的,則根節點可以是`null`。 1. **定義節點的結構**:確定樹的節點結構,包括每個節點的值和指向其子節點的指標。可以使用 struct 或 class 來定義節點的結構。 1. **建立節點**:使用節點結構定義,建立每個節點。可以透過分配記憶體(如 `malloc()`、`new`)或從 object pool 中獲取節點來建立節點。 1. **建立連接關係**:通過將父節點的指標指向其子節點,來建立樹中節點之間的連接關係。可以使用遞迴或迭代演算法來建立連接關係,具體取決於應用程式的需求。 1. **走訪樹**:走訪樹是指按照一定的順序造訪樹的所有節點。常見的走訪方式包括前序走訪、中序走訪和後序走訪,也可以使用層序走訪來造訪樹的所有節點。 1. **處理樹的操作**:對樹進行查找、插入、刪除等操作。可以使用遞迴或迭代演算法來實作這些操作,具體取決於應用程式的需求。 ## 實作基本的樹 撰寫一個JS程式碼範例,該範例會產生樹,並且該樹的結構類似於下面提供的圖片。 ![](https://i.imgur.com/nat49kd.png) [圖片使用syntree生成](https://mshang.ca/syntree/) 在這個範例中,我們建立了一個 `TreeNode` 類別,每個節點有一個 `value` 屬性代表該節點的值,以及一個 `children` 屬性代表該節點的子節點。 ```javascript= class TreeNode { constructor(value) { this.value = value; this.children = []; } addChild(node) { this.children.push(node); } } const root = new TreeNode(1); const node2 = new TreeNode(2); const node3 = new TreeNode(3); const node4 = new TreeNode(4); const node5 = new TreeNode(5); root.addChild(node2); root.addChild(node3); node2.addChild(node4); node2.addChild(node5); console.log(root); ``` ## 實作二元搜尋樹 以下程式碼是一個二分搜尋樹(Binary Search Tree, BST)的實作,包含三個部分: 1. `TreeNode` 類別:代表樹中的一個節點,包含一個值、一個左子節點和一個右子節點。 1. `BinarySearchTree` 類別:代表一個二分搜尋樹,包含一個根節點和一些操作方法,如插入、尋找和刪除節點。 1. 操作方法: * `insert(value)`:插入一個值為 `value` 的節點到樹中,如果樹為空則新節點設為根節點。如果值已經存在於樹中,則返回 `undefined`。 下圖為範例所產生二元搜尋樹 ![](https://i.imgur.com/cYHjJ8W.jpg) * `find(value)`:在樹中尋找值為 `value` 的節點,如果找到則返回該節點,否則返回 `false`。 ![](https://i.imgur.com/4zsoE2u.gif) * `remove(value)`:從樹中刪除值為 `value` 的節點。如果值不存在於樹中,則返回 `false`。 ![](https://i.imgur.com/Nr75URA.gif) ```javascript= class BinarySearchTree { constructor() { this.root = null; } insert(value) { const newNode = new TreeNode(value); if (this.root === null) { this.root = newNode; return this; } else { let current = this.root; while (true) { if (value === current.value) return undefined; if (value < current.value) { if (current.left === null) { current.left = newNode; return this; } else { current = current.left; } } else if (value > current.value) { if (current.right === null) { current.right = newNode; return this; } else { current = current.right; } } } } } find(value) { if (this.root === null) return false; let current = this.root; let found = false; while (current && !found) { if (value < current.value) { current = current.left; } else if (value > current.value) { current = current.right; } else { found = true; } } if (!found) return false; return current; } remove(value) { if (this.root === null) return false; let current = this.root; let parent = null; let found = false; while (current && !found) { if (value < current.value) { parent = current; current = current.left; } else if (value > current.value) { parent = current; current = current.right; } else { found = true; } } if (!found) return false; if (current.left === null && current.right === null) { if (current.value < parent.value) { parent.left = null; } else if (current.value > parent.value) { parent.right = null; } } else if (current.left === null) { if (current.value < parent.value) { parent.left = current.right; } else if (current.value > parent.value) { parent.right = current.right; } } else if (current.right === null) { if (current.value < parent.value) { parent.left = current.left; } else if (current.value > parent.value) { parent.right = current.left; } } else { let temp = current.right; while (temp.left !== null) { temp = temp.left; } current.value = temp.value; if (temp.right === null) { temp = null; } else { temp.value = temp.right.value; temp.right = null; } } } } const bst = new BinarySearchTree(); bst.insert(10); bst.insert(5); bst.insert(13); bst.insert(11); bst.insert(2); bst.insert(16); console.log(bst.find(11)); console.log(bst.remove(13)); console.log(bst); ``` ## 樹的走訪Tree Traversal 樹的走訪大致分成下面兩項 * 深度優先搜尋`(deppth-first search)`簡稱`DFS` * 廣度優先搜尋`(breadth-first search)`簡稱`BFS` ![](https://i.imgur.com/YBj0Tbq.gif) (圖片來自於[DFS vs BFS (in detail)](https://iq.opengenus.org/dfs-vs-bfs/)) ### 深度優先搜尋 深度優先搜尋詳細解說請參考[👉深度優先搜尋](https://hackmd.io/@SupportCoding/DFS) 樹的走訪在深度優先搜尋中為以下三種方式,分別代表根節點在走訪時的位置: 1. 前序走訪 (Pre-Order Traversal) 2. 中序走訪 (In-Order Traversal) 3. 後序走訪 (Post-Order Traversal) 接下來再個別探討的過程中,皆以**二元搜尋樹**做為範例。 #### 前序走訪 (Pre-Order Traversal) > 根節點排在前面 前序走訪`(Pre-Order Traversal)`是依序以**根節點、左節點、右節點**為順序走訪的方式。 ![](https://i.imgur.com/nGcN6NU.gif) #### LeetCode實戰 [144. Binary Tree Preorder Traversal](https://leetcode.com/problems/binary-tree-preorder-traversal) 題目: 給定一個二元搜尋樹,傳回其前序走訪的值。 ![](https://i.imgur.com/k7lF0Ka.jpg) (圖片來自於[Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page)) #### Code [👉詳細說明](https://leetcode.com/problems/binary-tree-preorder-traversal/solutions/3033034/javascript-solutions-detail/?envType=study-plan&id=data-structure-i&orderBy=most_votes) 前序走訪是**根->左->右**,我們先用一個陣列儲存要輸出的值,檢查節點是否為空,否的話則將值儲存至陣列,並利用遞迴的方式持續檢查左右子節點,最後再傳回該陣列即可。 ```javascript= /** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @return {number[]} */ var preorderTraversal = function(root) { var target = [] function dfs(node){ if (!node){ return; } target.push(node.val) dfs(node.left) dfs(node.right) } dfs(root) return target }; ``` #### 中序走訪 (In-Order Traversal) > 根節點排在中間 中序走訪`(In-Order Traversal)`是依序以**左節點、根節點、右節點**為順序走訪的方式。 ![](https://i.imgur.com/HsYvjNw.gif) (圖片來自於[Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page)) #### LeetCode實戰 [94. Binary Tree Inorder Traversal](https://leetcode.com/problems/binary-tree-inorder-traversal) 題目: 給定一個二元搜尋樹,傳回其中序走訪的值。 ![](https://i.imgur.com/HB20A30.jpg) #### Code [👉詳細說明](https://leetcode.com/problems/binary-tree-inorder-traversal/solutions/3038824/javascript-solutions-detail/?envType=study-plan&id=data-structure-i&orderBy=most_votes) 我們使用一個堆疊`(stack)`來存儲遍歷過的節點,並使用一個變量 `curr` 來記錄當前訪問的節點。 當 `curr` 為空時,從堆疊中彈出一個節點 `curr`,將其值存儲到 `target` 陣列中,然後將 `curr` 設置為其右子節點。因為在中序走訪中,左子節點必須先於根節點被處理,所以我們先處理完左子節點,再處理根節點,最後再處理右子節點。這樣保證了按照中序走訪的順序進行訪問。 ```javascript= /** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @return {number[]} */ var inorderTraversal = function(root) { let target = [] let stack = [] let curr = root while (curr != null || !stack.length == 0) { while (curr != null) { stack.push(curr); curr = curr.left; } curr = stack.pop(); target.push(curr.val); curr = curr.right; } return target; }; ``` #### 後序走訪 (Post-Order Traversal) > 根節點排在最後 後序走訪`(Post-Order Traversal)`是依序以**左節點、右節點、根節點**為順序走訪的方式。 ![](https://i.imgur.com/IabMSbC.gif) 題目: 給定一個二元搜尋樹,傳回其後序走訪的值。 #### LeetCode實戰 [145. Binary Tree Postorder Traversal](https://leetcode.com/problems/binary-tree-postorder-traversal) 題目: 給定一個二元搜尋樹,傳回其後序走訪的值。 ![](https://i.imgur.com/60V8unf.jpg) #### Code [👉詳細說明](https://leetcode.com/problems/binary-tree-postorder-traversal/solutions/3044353/javascript-simple-solution/?envType=study-plan&id=data-structure-i&orderBy=most_votes) 這邊解法有點像是把它反推回去,將節點往前加入陣列。 1. 在 `while` 迴圈中,將 `current` 的值加入結果陣列 `result` 的開頭,此為後序遍歷的特點。 1. 將 `current` 加入堆疊 `stack` 中。 1. 將 `current` 更新為其右子節點,一路向右子樹走,直到無法再往右。 1. 當 `current` 為空時,表示已經走到了右子樹的底部,此時從堆疊 `stack` 中彈出一個節點,將 `current` 更新為該節點的左子節點,以便繼續遍歷左子樹。因為左子樹已經遍歷完畢,所以這裡不需要將該節點加入結果陣列 `result` 中。 1. 重複執行步驟 `1-4`,直到 `current` 為空且堆疊 `stack` 也為空,表示已經遍歷完整棵樹。 最後,回傳遍歷結果陣列 `result`。 ```javascript= /** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @return {number[]} */ var postorderTraversal = function(root) { let result = []; let stack = []; let current = root; while(current || stack.length) { while(current) { result.unshift(current.val); stack.push(current); current = current.right; } current = stack.pop(); current = current.left; } return result; }; ``` ### 廣度優先搜尋 廣度優先搜尋詳細解說請參考[👉廣度優先搜尋](https://hackmd.io/@SupportCoding/BFS) 樹的走訪在廣度優先搜尋中的方式: * 層序走訪 #### 層序走訪 層序走訪會先造訪離根節點最近的節點,並由左至右存取。 ![](https://i.imgur.com/b766f4R.gif) #### LeetCode實戰 [102. Binary Tree Level Order Traversal](https://leetcode.com/problems/binary-tree-level-order-traversal/) 題目: 給定一個二元搜尋樹,傳回其層序走訪的值。 ![](https://i.imgur.com/OqtgiKY.jpg) #### Code [👉詳細說明](https://leetcode.com/problems/binary-tree-level-order-traversal/solutions/3062350/javascript-solutions-detail/?orderBy=most_votes) 1. 判斷樹是否為空,如果是,返回一個空陣列。 1. 宣告一個 `queue` 陣列,並將根節點存入 `queue`。 1. 宣告一個 `result` 陣列。 1. 開始進行 `while` 迴圈,當 `queue` 不為空時,進入迴圈。 1. 在迴圈中,宣告一個 `level` 陣列,代表當前層級的節點。 1. 計算當前層級節點的數量,使用 `size` 變數存儲。 1. 使用 `for` 迴圈,從 `queue` 中取出 `size` 個節點進行處理,直到當前層級所有節點處理完成。 1. 將當前節點的值存入 `level` 陣列中。 1. 如果當前節點有左子節點,將其加入 `queue` 中。 1. 如果當前節點有右子節點,將其加入 `queue` 中。 1. 將 `level` 陣列存入 `result` 二維陣列中。 1. 迴圈結束後,返回 `result` 陣列。 ```javascript= /** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @return {number[][]} */ var levelOrder = function(root) { if (root === null) return []; let queue = [root]; let result = []; while (queue.length > 0) { let level = []; let size = queue.length; for (let i = 0; i < size; i++) { let node = queue.shift(); level.push(node.val); if (node.left !== null) queue.push(node.left); if (node.right !== null) queue.push(node.right); } result.push(level); } return result; }; ``` ## 特別感謝 感謝[Howard Gou](https://hackmd.io/@toto6038)大大訂正

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully