【重學數據結構】二叉搜索樹 Binary Search Tree

目錄

二叉搜索樹的數據結構

手寫實現二叉搜索樹

樹節點定義?

插入節點?

源碼?

流程圖?

?二叉樹插入步驟圖解

第一步: 插入 20?

第二步: 插入 10

第三步: 插入 30

第四步: 插入 5

查找節點?

源碼?

場景一: 查找成功 (search for 25)

第一步: 從根節點開始

第二步: 移動到右子樹

第三步: 找到目標

場景二: 查找失敗 (search for 12)?

第一步: 從根節點開始

第二步: 移動到左子樹

第三步: 繼續向右查找

刪除節點?

源碼?

核心刪除邏輯: delete(Node delNode)

BST 刪除節點實例圖解?

場景一: 刪除葉子節點 (Delete 5)?

場景二: 刪除只有一個孩子的節點 (Delete 15)

場景三: 刪除有兩個孩子的節點 (Delete 20)

第 1 步: 識別目標 (delNode) 與后繼 (miniNode)

第 2 步: 處理后繼節點的子樹 (第一次 transplant)?

第 3 步: 連接后繼節點與待刪節點的右子樹

第 4 步: 將后繼節點換到待刪位置 (第二次 transplant)

第 5 步: 連接后繼節點與待刪節點的左子樹 (最終狀態)

常見問題?

二叉搜索樹結構簡述&變T的可能也讓手寫

二叉搜索樹的插入、刪除、索引的時間復雜度?

二叉搜索樹刪除含有雙子節點的元素過程敘述?

二叉搜索樹的節點都包括了哪些信息

為什么Java HashMap 中說過紅黑樹而不使用二叉搜索樹 ?


二叉搜索樹的數據結構

二叉搜索樹(Binary Search Tree),也稱二叉查找樹。如果你看見有序二叉樹(Ordered Binary tree)、排序二叉樹(Sorted Binary Tree)那么說的都是一個東西。

  • 若任意節點的左子樹不空,則左子樹上所有節點的值均小于它的根節點的值;
  • 若任意節點的右子樹不空,則右子樹上所有節點的值均大于它的根節點的值;
  • 任意節點的左、右子樹也分別為二叉查找樹;?

手寫實現二叉搜索樹
?

樹節點定義?

public class Node {public Class<?> clazz;public Integer value;public Node parent;public Node left;public Node right;public Node(Class<?> clazz, Integer value, Node parent, Node left, Node right) {this.clazz = clazz;this.value = value;this.parent = parent;this.left = left;this.right = right;}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + ((value == null) ? 0 : value.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (null == obj) return false;if (getClass() != obj.getClass()) return false;Node node = (Node) obj;if (null == value) {return node.value == null;} else {return node.value.equals(value);}}
}

插入節點?

源碼?

public Node insert(int e) {if (null == root) {root = new Node(e, null, null, null);size++;return root;}// 索引出待插入元素位置,也就是插入到哪個父元素下Node parent = root;Node search = root;while (search != null && search.value != null) {parent = search;if (e < search.value) {search = search.left;} else {search = search.right;}}// 插入元素Node newNode = new Node(e, parent, null, null);if (parent.value > newNode.value) {parent.left = newNode;} else {parent.right = newNode;}size++;return newNode;
}

流程圖?

?二叉樹插入步驟圖解

以一個簡單的例子,依次插入數組 [20, 10, 30, 5] 中的四個數字,并畫出每一步操作后樹的結構變化。

第一步: 插入 20?

執行操作: 調用 insert(20)。
代碼路徑: 此時樹是空的,root 為 null。代碼執行第一個 if 分支,創建一個新的根節點,其值為20。

image.png

第二步: 插入 10

執行操作: 調用 insert(10)。
代碼路徑: root (20) 不為 null。進入 while 循環:

  • 將 10 與根節點 20 比較,因為 10 < 20,所以 search 指針移動到左子節點 (當前為 null)。
  • search 變為 null,循環結束。此時的 parent 仍然是節點 20。
  • 創建一個值為 10 的新節點,并將其作為 parent (節點20) 的左孩子。?

第三步: 插入 30

執行操作: 調用 insert(30)。
代碼路徑: root (20) 不為 null。進入 while 循環:

  • 將 30 與根節點 20 比較,因為 30 > 20,所以 search 指針移動到右子節點 (當前為 null)。
  • search 變為 null,循環結束。此時的 parent 仍然是節點 20。
  • 創建一個值為 30 的新節點,并將其作為 parent (節點20) 的右孩子

第四步: 插入 5

執行操作: 調用 insert(5)。
代碼路徑: root (20) 不為 null。進入 while 循環:

  • 第1輪循環: 5 < 20,search 移動到左孩子 (節點10)。parent 更新為節點 20。
  • 第2輪循環: 5 < 10,search 移動到左孩子 (當前為 null)。parent 更新為節點 10。
  • search 變為 null,循環結束。此時的 parent 是節點 10。
  • 創建一個值為 5 的新節點,并將其作為 parent (節點10) 的左孩子。?

查找節點?

源碼?

public Node search(int e) {Node node = root;while (node != null && node.value != null && node.value != e) {if (e < node.value) {node = node.left;} else {node = node.right;}}return node;
}

初始樹結構
假設我們的樹通過依次插入 `[20, 10, 30, 5, 15, 25, 35]` 構建而成。

image.png

場景一: 查找成功 (search for 25)

我們來查找一個存在于樹中的值:`25`。?

第一步: 從根節點開始
  • 當前節點 node: 20

  • 比較: `25 < 20` 為假。

  • 操作: 進入 `else` 分支,將 node 更新為其右孩子。node = node.right。

第二步: 移動到右子樹
  • 當前節點 node: 30

  • 比較: `25 < 30` 為真。
  • 操作: 進入 `if` 分支,將 node 更新為其左孩子。node = node.left。

image.png

第三步: 找到目標
  • 當前節點 node: 25

  • 循環條件檢查: `node.value != e` (即 `25 != 25`) 為假。
  • 操作: 循環條件不滿足,退出 while 循環。?

結果: 方法返回值為 25 的 Node 對象。?

場景二: 查找失敗 (search for 12)?

現在,我們查找一個不存在于樹中的值:`12`。

第一步: 從根節點開始
  • 當前節點 node: 20
  • 比較: `12 < 20` 為真,node 移動到左孩子 (10)。
第二步: 移動到左子樹
  • 當前節點 node: 10
  • 比較: `12 < 10` 為假。
  • 操作: 進入 `else` 分支,node 更新為其右孩子。node = node.right。

第三步: 繼續向右查找
  • 當前節點 node: 15
  • 比較: `12 < 15` 為真。
  • 操作: 進入 `if` 分支,node 更新為其左孩子。

?第四步: 到達葉節點末端

  • 當前節點 node: `null` (因為節點 15 沒有左孩子)。
  • 循環條件檢查: `node != null` 為假。
  • 操作: 循環條件不滿足,退出 while 循環。

樹的結構保持不變,但我們的 node 指針現在是 null。

結果: 方法返回 null,表示未找到值為 12 的節點。?

刪除節點?

源碼?

public Node delete(int e) {Node delNode = search(e);if (null == delNode) return null;return delete(delNode);
}private Node delete(Node delNode) {if (delNode == null) return null;Node result = null;if (delNode.left == null) {result = transplant(delNode, delNode.right);} else if (delNode.right == null) {result = transplant(delNode, delNode.left);} else {// 因為刪除的節點,有2個孩子節點,這個時候找到這條分支下,最左側做小的節點。用它來替換刪除的節點Node miniNode = getMiniNode(delNode.right);if (miniNode.parent != delNode) {// 交換位置,用miniNode右節點,替換miniNodetransplant(miniNode, miniNode.right);// 把miniNode 提升父節點,設置右子樹并進行掛鏈。替代待刪節點miniNode.right = delNode.right;miniNode.right.parent = miniNode;}// 交換位置,刪除節點和miniNode 可打印測試觀察;System.out.println(this);transplant(delNode, miniNode);// 把miniNode 提升到父節點,設置左子樹并掛鏈miniNode.left = delNode.left;miniNode.left.parent = miniNode;result = miniNode;}size--;return result;
}
private Node getMinimum(Node node) {while (node.left != null) {node = node.left;}return node;
}private Node transplant(Node delNode, Node addNode) {if (delNode.parent == null) {this.root = addNode;}// 判斷刪除元素是左/右節點else if (delNode.parent.left == delNode) {delNode.parent.left = addNode;} else {delNode.parent.right = addNode;}// 設置父節點if (null != addNode) {addNode.parent = delNode.parent;}return addNode;
}

核心刪除邏輯: delete(Node delNode)

這是處理刪除操作的核心方法。它分為三個主要分支:

  1. 情況1: 要刪除的節點沒有左孩子。

  2. 情況2: 要刪除的節點有左孩子,但沒有右孩子。
  3. 情況3: 要刪除的節點有兩個孩子(最復雜的情況)。?

transplant 方法是實現刪除的核心。它的作用是在樹中用一個節點 (addNode) 替換另一個節點 (delNode),并正確地維護父節點的鏈接關系。它本身并不處理子節點的鏈接,這由 delete 方法完成。
getMinimum 是一個簡單的輔助方法,用于在給定的子樹中找到值最小的節點。它通過不斷地沿著左子節點向下遍歷直到末端來實現。

BST 刪除節點實例圖解?

我們將使用一個具體的二叉搜索樹來逐步演示刪除操作的三個場景。在圖示中:

  1. 紅色節點: 將要被刪除的目標節點 (delNode)。
  2. 黃色節點: 用于替換的后繼節點 (miniNode)。
  3. 紫色邊: 表示正在發生改變的父子鏈接。?

初始樹結構
我們的示例樹通過依次插入 `[20, 10, 30, 5, 15, 25, 35]` 構建而成。

image.png

場景一: 刪除葉子節點 (Delete 5)?

執行步驟

  1. delete(5) 調用 search(5) 找到節點 5。
  2. 進入 delete(Node delNode) 方法,delNode 是節點 5。
  3. 檢查 delNode.left == null 為真。
  4. 調用 transplant(delNode, delNode.right),即 transplant(5, null)。
  5. 在 transplant 中,delNode 的父節點 (10) 的左孩子 (left) 被設置為 null。
  6. 刪除完成。

image.png

場景二: 刪除只有一個孩子的節點 (Delete 15)

注: 為演示此場景,我們創建一棵新樹 `[20, 10, 30, 5, 15, 35]`,其中節點 15 只有一個左孩子 5。我們會用它的子節點替換它。?

image.png

image.png

執行邏輯: transplant(15, 5) 被調用,節點 10 的右孩子指針直接指向了節點 5,同時更新了節點 5 的父指針。?

場景三: 刪除有兩個孩子的節點 (Delete 20)

這是最復雜的情況,我們將刪除根節點 20。這會觸發代碼中最完整的邏輯:尋找后繼節點 (右子樹的最小值),并用它來替換被刪除的節點。

第 1 步: 識別目標 (delNode) 與后繼 (miniNode)

我們想刪除 20。因為它有兩個孩子,我們必須找到它的后繼節點。通過 `getMinimum(delNode.right)`,我們找到其右子樹 (以30為根) 的最小值,即 25。?

第 2 步: 處理后繼節點的子樹 (第一次 transplant)?

代碼檢查 `miniNode.parent != delNode` (即 30 != 20),這個條件為真。因此,我們需要先將 `miniNode` (25) 提拔上來。我們調用 transplant(miniNode, miniNode.right),即 transplant(25, null),將 25 原來的父節點 (30) 的左孩子指向 25 的右孩子 (null)。

第 3 步: 連接后繼節點與待刪節點的右子樹

執行 miniNode.right = delNode.right。我們將 20 的右子樹 (以 30 為根) 變成 25 的右子樹。

image.png

第 4 步: 將后繼節點換到待刪位置 (第二次 transplant)

調用 transplant(delNode, miniNode),即 transplant(20, 25)。因為 20 是根節點,這會將樹的 root 指針更新為 25。

image.png

第 5 步: 連接后繼節點與待刪節點的左子樹 (最終狀態)

最后,執行 miniNode.left = delNode.left,將 20 的左子樹 (以 10 為根) 掛到 25 的左邊,完成整個刪除操作。?

常見問題?

二叉搜索樹結構簡述&變T的可能也讓手寫

某個節點的左子樹所有值都小于該節點,右子樹中所有值都大于該節點?

二叉搜索樹的插入、刪除、索引的時間復雜度?

插入、刪除、查找的時間復雜度取決于樹的高度

理想情況下是 O(log n), 但最壞情況下會退化為 O(n)?

二叉搜索樹刪除含有雙子節點的元素過程敘述?

刪除一個有兩個子節點的節點時,不能直接刪除掉它,而是從它的右子樹中找一個最小的節點(剛好比他大一點點),用這個節點來頂替他的位置。
例如:刪除節點 delNode 他有兩個子節點 delNode.left、delNode.right

  1. 從它的右子樹中找一個合適的節點來替代它 addNode
  2. 因為要用 addNode 節點來替換 delNode,所以需要先把 addNode 原來的位置清理干凈,最多只有一個右孩子(沒有左孩子),用右孩子來替換 addNode的位置
  3. 把delNode的父節點 指向 addNode 、把delNode的左右子樹接到 addNode上面?

二叉搜索樹的節點都包括了哪些信息

Integer value 值;
Node parent 父節點;
Node left 左子節點;
Node right 右子節點;

為什么Java HashMap 中說過紅黑樹而不使用二叉搜索樹 ?

是為了避免 BST 在極端情況下退化為鏈表,從而保證查找、插入和刪除操作始終保持 O(log n)?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93269.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93269.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93269.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

四、計算機組成原理——第1章:計算機系統概述

目錄 1.1計算機發展歷程 1.1.1計算機硬件的發展 1.計算機的四代變化 2.計算機元件的更新換代 1.1.2計算機軟件的發展 1.2計算機系統層次結構 1.2.1計算機系統的組成 1.2.2計算機硬件 1.馮諾依曼機基本思想 2.計算機的功能部件 (1)輸入設備 (2)輸出設備 (3)存儲器 (4)運算器 (5)…

flutter TextField 失去焦點事件

在 Flutter 中&#xff0c;處理 TextField 的失去焦點事件&#xff08;即失去焦點時觸發的操作&#xff09;通常有兩種常用方式&#xff1a;使用 FocusNode 或 onEditingComplete 回調。以下是具體實現&#xff1a; import package:flutter/material.dart;class MyTextField e…

Moonlight for ChromeOS 常見問題解決方案

Moonlight for ChromeOS 常見問題解決方案 項目基礎介紹 Moonlight for ChromeOS 是一個開源的 NVIDIA GameStream 客戶端&#xff0c;允許用戶將他們的游戲從高性能的桌面電腦流式傳輸到運行 ChromeOS 的設備上。該項目還支持 Android 和 iOS/tvOS 平臺。Moonlight for Chrome…

SQL語句:讀操作、寫操作、視圖

文章目錄讀操作分類基礎查詢語句示例高級查詢--分組查詢、子查詢、表連接、聯合查詢分組查詢&#xff1a;子查詢&#xff08;嵌套查詢&#xff09;表連接聯合查詢寫操作視圖SQL&#xff1a;結構化查詢語言讀操作 重點是where查詢&#xff0c;即高級查詢部分 分類 DML &#…

Python 機器學習實戰:基于 Scikit-learn

本文圍繞《Python 機器學習實戰&#xff1a;基于 Scikit-learn 的項目開發》展開&#xff0c;先介紹 Scikit-learn 庫的基礎特性與優勢&#xff0c;再闡述機器學習項目開發的完整流程&#xff0c;包括數據收集與預處理、模型選擇與訓練、評估與優化等。通過具體實戰案例&#x…

java里List鏈式編程

java里對list的操作&#xff0c;我們一遍使用for遍歷&#xff0c;輸出或改變里面的內容。單經常在代碼里面我們發現&#xff0c;也可以使用這樣的代碼結構daPaymentActionVo.setApnolist(paymentActionVo.getApnolist().stream().map(PaymentActionVo.Voucher::getApno).collec…

【esp32s3】7 - VSCode + PlatformIO + Arduino + 構建項目

一、PlatformIO 1.1. 概述 官方文檔&#xff1a;What is PlatformIO? PlatformIO 是一個跨平臺的物聯網開發生態系統&#xff0c;專門為嵌入式系統開發設計&#xff0c;支持多種開發板和框架。 1.1.1. 主要特點 跨平臺&#xff1a;支持 Windows、macOS 和 Linux多框架支持&…

LE AUDIO CIS/BIS音頻傳輸時延的計算

LE AUDIO音頻總時延計算方法 按照BAP的規范,LE AUDIO音頻總延時包括三個部分:Audio Processing Time,Transport Latency,Presentation Delay。如下圖所示是播放音樂的示例圖: 這里還有一個麥克風錄音的總時延示例圖: Audio Processing Time:這個就是音頻DSP獲取音頻數…

git 修改 更新

git 修改 更新先更新&#xff0c;后修改# 暫存當前修改 git add . git stash# 獲取最新的 main 分支 git checkout main git pull# 新建開發分支 git checkout -b lbg_0727# ?? 先把 main 的最新代碼合并/變基到當前分支&#xff08;用于消除沖突&#xff09; # 方法1&#x…

飛鶴困局:增長神話的裂痕

增長天花板已然逼近&#xff0c;飛鶴需要探尋新方向。作者|安德魯編輯|文昌龍“飛鶴&#xff0c;更適合中國寶寶體質”——這句曾讓無數媽媽點頭的廣告語&#xff0c;幫飛鶴坐上了中國奶粉市場的頭把交椅。可多年后&#xff0c;時代紅利退潮&#xff0c;故事不好講了。飛鶴的利…

Java設計模式之<建造者模式>

目錄 1、建造者模式 2、建造者模式結構 3、實現 4、工廠模式對比 5、適用場景差異 前言 建造者模式是一種創建型設計模式。用于封裝復雜對象的構建過程&#xff0c;通過步驟構建產品類。它包括產品類、抽象建造者、具體建造者和指揮者角色。 優點在于靈活性、解耦和易擴展…

fchown/fchownat系統調用及示例

55. fchmod - 通過文件描述符改變文件權限 函數介紹 fchmod是一個Linux系統調用&#xff0c;用于通過文件描述符來改變文件的訪問權限。它是chmod函數的文件描述符版本&#xff0c;避免了路徑名解析。 函數原型 #include <sys/stat.h> #include <unistd.h>int fchm…

20250726-5-Kubernetes 網絡-Service 代理模式詳解(iptables與ipvs)_筆記

一、服務三種常用類型 ?? 1. LoadBalancer類型 工作原理:與NodePort類似,在每個節點上啟用端口暴露服務,同時Kubernetes會請求底層云平臺(如阿里云、騰訊云、AWS等)的負載均衡器,將每個Node([NodeIP]:[NodePort])作為后端添加。 自動化實現:云廠商通過官方實現的控制…

horizon置備出錯

報錯內容如下&#xff1a; [2025/7/28 19:15] 置備 Customization failure: Customization of the guest operating system is not supported due to the given reason: 期間出錯 解決方法&#xff1a;將模板轉換為虛擬機&#xff0c;安裝vmtools&#xff1b;再安裝vmtools之后…

【unitrix】 6.19 Ord特質(ord.rs)

一、源碼 這段代碼定義了一個標記特征&#xff08;marker trait&#xff09;Ord 和三個實現&#xff0c;用于將類型標記與 Rust 標準庫中的 Ordering 枚舉關聯起來。 use crate::sealed::Sealed; use core::cmp::Ordering; use crate::number::{Greater, Equal, Less}; /// 用于…

數據結構之順序表鏈表棧

順序表 什么是 list list 的使用 線性表是什么 順序表是什么 順序表和線性表的關系 順序表和數組的區別 List 和 ArrayList 的關系 如何自己模擬實現 myArrayList ArrayList 的構造 ArrayList 的常見方法 以下兩種寫法有什么區別 ArrayList<Integer> arrayLis…

day062-監控告警方式與Grafana優雅展示

文章目錄0. 老男孩思想-馬太效應1. API監控2. zabbix的API接口2.1 生成zabbix的api token2.2 訪問格式2.3 前端添加web監測3. 監控告警方式3.1 云監控-郵件告警3.1.1 郵箱開啟授權碼3.1.2 zabbix前端配置3.1.3 消息模板3.1.4 配置郵箱收件人信息3.1.5 配置觸發器3.2 企業微信告…

Ettus USRP X410/X440 運行 ADC 自校準

Ettus USRP X410/X440 運行 ADC 自校準 打開一個接收&#xff08;Rx&#xff09;會話到您在設備名稱輸入中指定的設備并返回會話句柄 out&#xff0c;您可以使用該句柄在所有后續 NI-USRP VI 中識別此儀器會話。 支持設備&#xff1a;Ettus USRP X410/X440輸入/輸出 文明.png 會…

Qt元類型系統(QMetaType)詳解

Qt元類型系統詳解一、Qt元類型系統(QMetaType)詳解1. 核心功能2. 注冊機制3. 關鍵技術點4. 信號槽支持5. 流式傳輸支持6. 使用場景7. 注意事項二、完整示例1、基本實例2、基本實例3、元類型在信號槽中的應用4、高級用法三、元對象編譯器moc元對象編譯器&#xff08;Moc&#xf…

《C++繼承詳解:從入門到理解公有、私有與保護繼承》

《C繼承詳解&#xff1a;從入門到理解公有、私有與保護繼承》 文章目錄《C繼承詳解&#xff1a;從入門到理解公有、私有與保護繼承》一、繼承的概念及定義1.1 繼承的概念1.2 繼承定義1.2.1 定義格式1.2.2 繼承基類成員訪問方式的變化1.3 繼承類模版二、基類和派生類間的轉換三、…