C++鐫刻數據密碼的樹之銘文:二叉搜索樹

文章目錄

  • 1.二叉搜索樹的概念
  • 2.二叉搜索樹的實現
    • 2.1 二叉搜索樹的結構
    • 2.2 二叉搜索樹的節點尋找
      • 2.2.1 非遞歸
      • 2.2.2 遞歸
    • 2.3 二叉搜索樹的插入
      • 2.3.1 非遞歸
      • 2.3.2 遞歸
    • 2.4 二叉搜索樹的刪除
      • 2.4.1 非遞歸
      • 2.4.2 遞歸
    • 2.5 二叉搜索樹的拷貝
  • 3.二叉樹的應用
  • 希望讀者們多多三連支持
  • 小編會繼續更新
  • 你們的鼓勵就是我前進的動力!

繼數據結構的二叉樹學習,本篇進行更進一步的搜索二叉樹,是一種更為常見的結構

1.二叉搜索樹的概念

二叉搜索樹簡單來說就是一個排序樹

它是具有以下性質的二叉樹:

  • 若它的左子樹不為空,則左子樹上所有節點的值都小于根節點的值
  • 若它的右子樹不為空,則右子樹上所有節點的值都大于根節點的值
  • 它的左右子樹也分別為二叉搜索樹

🔥值得注意的是: 每棵子樹都滿足該性質

2.二叉搜索樹的實現

2.1 二叉搜索樹的結構

template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){ }
};
  • _left: 指向左子節點的指針。
  • _right: 指向右子節點的指針。
  • _key: 存儲節點的鍵值

2.2 二叉搜索樹的節點尋找

2.2.1 非遞歸

bool Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return false;
}

借助 cur 指針從根節點開始遍歷二叉搜索樹:

  • cur->_key 小于 key,則轉向右子樹繼續查找
  • cur->_key 大于 key,則轉向左子樹繼續查找
  • cur->_key 等于 key,說明找到了目標鍵值,返回 true
  • 若遍歷結束 curnullptr,表示未找到目標鍵值,返回 false

2.2.2 遞歸

bool _FindR(Node* root, const K& key)
{if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}
}

檢查基本情況: 查看當前節點 root 是否為空。若為空,返回 false,遞歸結束
比較鍵值: 若當前節點不為空,將當前節點的鍵值 root->_key 與目標鍵值 key 進行比較重復,每次遞歸調用都會將問題規模縮小,直至滿足基本情況或者找到目標節點

🔥值得注意的是: 注意這些非遞歸要放在 private,因為 root 也是 private,由于要控制子樹,必須要傳入 root,如果是 public 的話,就只能傳入自己的 root,而不是二叉搜索樹的 root,無法保證 root 的正確性

2.3 二叉搜索樹的插入

2.3.1 非遞歸

bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;
}

cur 為空時,說明已經找到了插入位置。創建一個新節點,并根據 parent 的鍵和要插入的鍵的大小關系,將新節點插入到 parent 的左子樹或右子樹中

🔥值得注意的是: 首先檢查樹是否為空,如果為空,則直接創建一個新節點作為根節點,并返回 true

2.3.2 遞歸

bool _InsertR(Node*& root, const K& key)
{if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}
}

這里遞歸的流程和查找的遞歸代碼幾乎一樣,唯一不同的是要傳入的 root 需要加引用,這是因為這里的代碼只執行了節點尋找創建的操作,那么當我們找到空節點并創建的時候,由于 root 是上一個 _InsertR 函數 root->_leftroot->_right 的別名,創建的時候相當于 root->_left = new Node(key)root->_right = new Node(key),這樣才能完成鏈接

2.4 二叉搜索樹的刪除

2.4.1 非遞歸

bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){// 左為空if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}// 右為空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}}// 左右都不為空 else{Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}swap(leftMax->_key, cur->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;
}

首先先找到需要刪除的節點,接著就需要分了討論:

  1. 要刪除的結點無孩子結點
  2. 要刪除的結點只有左孩子結點
  3. 要刪除的結點只有右孩子結點
  4. 要刪除的結點有左、右孩子結點

🔥值得注意的是: 第一點可以直接看成只有一個節點的情況,即鏈接的是空節點

刪除該結點且使被刪除節點的雙親結點指向被刪除節點的左孩子結點–直接刪除

如果待刪除節點 cur 的左子樹為空,分兩種情況處理:
如果 cur 就是根節點,那么將根節點更新為 cur 的右子樹;如果 cur 不是根節點,則根據 cur 是其父節點 parent 的左子節點還是右子節點,相應地將 parent 的左指針或右指針指向 cur 的右子樹

刪除該結點且使被刪除節點的雙親結點指向被刪除結點的右孩子結點–直接刪除

如果待刪除節點 cur 的右子樹為空,同樣分兩種情況:

cur 是根節點,將根節點更新為 cur 的左子樹;若 cur 不是根節點,根據 curparent 的左子節點還是右子節點,將 parent 的左指針或右指針指向 cur 的左子樹

在刪除節點的左子樹中尋找最大節點或者在它的右子樹中尋找最小節點,用它的值填補到被刪除節點中,再來處理該節點的刪除問題–替換法刪除

在這里插入圖片描述

當待刪除節點 cur 的左右子樹都不為空時,為了保持二叉搜索樹的性質,找到 cur 左子樹中的最大節點 leftMax(即左子樹中最右側的節點)。通過一個 while 循環找到 leftMax,并記錄其父親節點 parent。然后交換 leftMaxcur 的鍵值,這樣就將刪除 cur 節點的問題轉化為刪除 leftMax 節點的問題,leftMax 由于是最大的節點,所以要么沒有節點,要么只有左節點

🔥值得注意的是:

在這里插入圖片描述

Node* parent = cur 而不是 Node* parent = nullptr,因為如果第一個左子節點就是 leftMax,那么 parent 就不會改變,使用 parent 的時候就會出問題

2.4.2 遞歸

bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;// 1、左為空// 2、右為空// 3、左右都不為空if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}
}

將即 rootleftMax 的鍵值進行交換,此時原本 leftMax 節點處的鍵值變為要刪除的 key,由于交換后要刪除的節點在左子樹中,所以遞歸調用 _EraseR(root->_left, key) 繼續在左子樹中查找并刪除這個鍵值為 key 的節點。因為在左子樹中刪除節點時,可能又會遇到不同的情況(如左子樹為空、右子樹為空或左右子樹都不為空),所以遞歸調用可以繼續處理這些情況,直到成功刪除節點或者確定節點不存在

🔥值得注意的是:

在這里插入圖片描述

這里 return _EraseR(root->_left, key) 不能寫成 return _EraseR(leftMax, key)

因為 leftMax 只是個局部變量,對其進行操作沒法改變 81 的鏈接

2.5 二叉搜索樹的拷貝

Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* copyroot = new Node(root->_key);copyroot->_left = Copy(root->_left);copyroot->_right = Copy(root->_right);return copyroot;
}
  1. 為當前節點創建一個新的節點 copyroot,新節點的鍵值和原節點 root 的鍵值相同
  2. 遞歸調用 Copy 函數來拷貝原節點 root 的左子樹,將拷貝結果賦值給新節點 copyroot 的左子節點指針 _left
  3. 同樣地,遞歸調用 Copy 函數來拷貝原節點 root 的右子樹,把拷貝結果賦值給新節點 copyroot 的右子節點指針 _right
  4. 最后返回新創建的節點 copyroot,該節點及其子樹構成了原節點及其子樹的深拷貝

3.二叉樹的應用

🚩K模型: 即只有 key 作為關鍵碼,結構中只需要存儲 key 即可,關鍵碼即為需要搜索到的值,主要判斷在不在的場景

比如: 給一個單詞 word,判斷該單詞是否拼寫正確,具體方式如下:

  • 以詞庫中所有單詞集合中的每個單詞作為 key,構建一棵二叉搜索樹
  • 在二叉搜索樹中檢索該單詞是否存在,存在則拼寫正確,不存在則拼寫錯誤。

🚩KV模型: 每一個關鍵碼 key,都有與之對應的值 value,即 <key, value> 的鍵值對,通過一個值找另外一個值

  • 比如英漢詞典就是英文與中文的對應關系,通過英文可以快速找到與其對應的中文,英文單詞與其對應的中文 <word, chinese> 就構成一種鍵值對;
  • 再比如統計單詞次數,統計成功后,給定單詞就可快速找到其出現的次數,單詞與其出現次數就是 <word, count> 就構成一種鍵值對
namespace key_value
{template<class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:BSTree():_root(nullptr){}void InOrder(){_InOrder(_root);cout << endl;}Node* FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key, const V& value){return _InsertR(_root, key, value);}bool EraseR(const K& key){return _EraseR(_root, key);}private:bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;// 1、左為空// 2、右為空// 3、左右都不為空if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftMax = root->_left;while (leftMax->_right){leftMax = leftMax->_right;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}}bool _InsertR(Node*& root, const K& key, const V& value){if (root == nullptr){root = new Node(key, value);return true;}if (root->_key < key){return _InsertR(root->_right, key, value);}else if (root->_key > key){return _InsertR(root->_left, key, value);}else{return false;}}Node* _FindR(Node* root, const K& key){if (root == nullptr)return nullptr;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return root;}}void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root;};

key_value 模型主要是通過一個節點里包含兩個值:keyvalue 實現的,只要找到了key 就能順便找到 value,其余的函數邏輯等都與 K 模型幾乎一致

🔥值得注意的是: 二叉搜索樹的性能是不錯的,插入和刪除操作都必須先查找,查找效率代表了二叉搜索樹中各個操作的性能,

  • 最優情況下,二叉搜索樹為完全二叉樹(或者接近完全二叉樹),其平均比較次數為: l o g 2 N log_2 N log2?N
  • 最差情況下,二叉搜索樹退化為單支樹(或者類似單支),其平均比較次數為: N 2 \frac{N}{2} 2N?

如果退化成單支樹,二叉搜索樹的性能就失去了。那能否進行改進,不論按照什么次序插入關鍵碼,二叉搜索樹的性能都能達到最優?那么我們涉及到后續章節學習的 AVL樹紅黑樹


希望讀者們多多三連支持

小編會繼續更新

你們的鼓勵就是我前進的動力!

請添加圖片描述

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

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

相關文章

系統架構設計師:流水線技術相關知識點、記憶卡片、多同類型練習題、答案與解析

流水線記憶要點? ?公式 總時間 (n k - 1)Δt 吞吐率 TP n / 總時間 → 1/Δt&#xff08;max&#xff09; 加速比 S nk / (n k - 1) | 效率 E n / (n k - 1) 關鍵概念 周期&#xff1a;最長段Δt 沖突?&#xff1a; ?數據沖突&#xff08;RAW&#xff09; → 旁路/…

強制重裝及驗證onnxruntime-gpu是否正確工作

#工作記錄 我們經常會遇到明明安裝了onnxruntime-gpu或onnxruntime后&#xff0c;無法正常使用的情況。 一、強制重新安裝 onnxruntime-gpu 及其依賴 # 強制重新安裝 onnxruntime-gpu 及其依賴 pip install --force-reinstall --no-cache-dir onnxruntime-gpu1.18.0 --extra…

桌面我的電腦圖標不見了怎么恢復 恢復方法指南

在Windows操作系統中&#xff0c;“我的電腦”或在較新版本中稱為“此電腦”的圖標&#xff0c;是訪問硬盤驅動器、外部存儲設備和系統文件的重要入口。然而&#xff0c;有些用戶可能會發現桌面上缺少了這個圖標&#xff0c;這可能是由于誤操作、系統設置更改或是不小心刪除造成…

2025.04.20【Lollipop】| Lollipop圖繪制命令簡介

Customize markers See the different options allowing to customize the marker on top of the stem. Customize stems See the different options allowing to customize the stems. 文章目錄 Customize markersCustomize stems Lollipop圖簡介R語言中的Lollipop圖使用ggp…

docker-compose搭建kafka

1、單節點docker-compose.yml version: 3 services:zookeeper:image: zookeeper:3.8container_name: zookeeperports:- "2181:2181"volumes:- ./data/zookeeper:/dataenvironment:ZOO_MY_ID: 1ZOO_MAX_CLIENT_CNXNS: 100kafka:image: bitnami/kafka:3.7container_na…

【問題】一招解決vscode輸出和終端不一致的困擾

背景&#xff08;閑話Trae&#xff09; Trae是挺好&#xff0c;用了幾天&#xff0c;發現它時不時檢查文件&#xff0c;一檢測就轉悠半天&#xff0c;為此我把當前環境清空&#xff0c;就留一個正在調的程序&#xff0c;結果還照樣檢測&#xff0c;雖然沒影響什么&#xff0c;…

Git,本地上傳項目到github

一、Git的安裝和下載 https://git-scm.com/ 進入官網&#xff0c;選擇合適的版本下載 二、Github倉庫創建 點擊右上角New新建一個即可 三、本地項目上傳 1、進入 要上傳的項目目錄&#xff0c;右鍵&#xff0c;選擇Git Bash Here&#xff0c;進入終端Git 2、初始化臨時倉庫…

從零開始配置spark-local模式

1. 環境準備 操作系統&#xff1a;推薦使用 Linux 或 macOS&#xff0c;Windows 也可以&#xff0c;但可能會有一些額外的配置問題。 Java 環境&#xff1a;Spark 需要 Java 環境。確保安裝了 JDK 1.8 或更高版本。 檢查 Java 版本&#xff1a; bash 復制 java -version 如果…

前端~地圖(openlayers)繪制車輛運動軌跡(仿高德)

繪制軌跡路線軌跡路線描邊增加起點終點圖標繪制仿高德方向箭頭模仿車輛動態運動動畫 車輛運行軌跡 車輛軌跡經緯度坐標 const linePoints [new Point([123.676031, 43.653421]),new Point([123.824347, 43.697124]),new Point([124.197882, 43.946811]),new Point([124.104498…

分布式之CAP原則:理解分布式系統的核心設計哲學

聲明&#xff1a;CAP中的P原則都是需要帶著的 在分布式系統的設計與實踐中&#xff0c;CAP原則&#xff08;又稱CAP定理&#xff09;是開發者必須掌握的核心理論之一。它揭示了分布式系統在一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#x…

IF=40.8|腫瘤免疫:從免疫基因組學到單細胞分析和人工智能

一、寫在前面 今天分享的是發表在《Signal Transduction and Targeted Therapy》上題目為"Technological advances in cancer immunity: from immunogenomics to single-cell analysis and artificial intelligence"的文章。 IF&#xff1a;40.8 DOI:10.1038/s41392…

深入理解 Spring @Bean 注解

在 Spring 框架中,@Bean 注解是用于顯式地聲明一個或多個 Bean 實例,并將其注冊到 Spring 容器中的重要工具。與 @Component 系列注解不同的是,@Bean 是方法級別的注解,通常與 @Configuration 注解結合使用。本文將詳細介紹 @Bean 注解的功能、用法及其應用場景。 1. @Bean…

Pycharm 如何刪除某個 Python Interpreter

在PyCharm中&#xff0c;點擊右下角的“Interpreter Settings”按鈕&#xff0c;或者通過菜單欄選擇“File” > “Settings”&#xff08;macOS用戶選擇“PyCharm” > “Preferences”&#xff09;。在設置窗口中&#xff0c;導航到“Project: [Your Project Name]” >…

如何改電腦網絡ip地址完整教程

更改電腦的網絡IP地址以滿足特定的網絡需求&#xff0c;本文將為您提供一份詳細的步驟指南。其實&#xff0c;改變IP地址并不是一件復雜的事&#xff0c;能解決因為IP限制帶來的麻煩。以下是操作指南&#xff1a; 方法一&#xff1a;Windows 系統&#xff0c;通過圖形界面修改 …

Oracle--SQL性能優化與提升策略

前言&#xff1a;本博客僅作記錄學習使用&#xff0c;部分圖片出自網絡&#xff0c;如有侵犯您的權益&#xff0c;請聯系刪除 一、導致性能問題的內在原因 系統性能問題的底層原因主要有三個方面&#xff1a; CPU占用率過高導致資源爭用和等待內存使用率過高導致內存不足并需…

【go】什么是Go語言中的GC,作用是什么?調優,sync.Pool優化,逃逸分析演示

Go 語言中的 GC 簡介與調優建議 Go語言GC工作原理 對于 Go 而言&#xff0c;Go 的 GC 目前使用的是無分代&#xff08;對象沒有代際之分&#xff09;、不整理&#xff08;回收過程中不對對象進行移動與整理&#xff09;、并發&#xff08;與用戶代碼并發執行&#xff09;的三…

【unity實戰】Animator啟用root motion根運動動畫,實現完美的動畫動作匹配

文章目錄 前言1、動畫分類2、如何使用根位移動畫&#xff1f; 一、根位移動畫的具體使用1、導入人形模型2、導入動畫3、配置動畫參數4、配置角色Animator動畫狀態機5、使用代碼控制人物前進后退 二、問題分析三、Humanoid動畫中的Root Motion機制及相關配置1、Humanoid動畫中的…

中間件--ClickHouse-10--海量數據存儲如何抉擇ClickHouse和ES?

在Mysql數據存儲或性能瓶頸時&#xff0c;采用冷熱數據分離的方式通常是一種選擇。ClickHouse和Elasticsearch&#xff08;ES&#xff09;是兩個常用的組件&#xff0c;但具體使用哪種組件取決于冷數據的存儲目的、查詢模式和業務需求等方面。 1、核心對比 &#xff08;1&…

服務器運維:服務器流量的二八法則是什么意思?

文章目錄 用戶行為角度時間分布角度應用場景角度 服務器流量的二八法則&#xff0c;又稱 80/20 法則&#xff0c;源自意大利經濟學家帕累托提出的帕累托法則&#xff0c;該法則指出在很多情況下&#xff0c;80% 的結果是由 20% 的因素所決定的。在服務器流量領域&#xff0c;二…

springboot對接豆包大模型

文檔地址: 豆包大模型-火山引擎 模型廣場地址: 賬號登錄-火山引擎 首先來到模型廣場&#xff0c;選取你需要的模型,我這邊要做圖片理解的應用&#xff0c;所以選用了Doubao-1.5.vision-pro. 點立即體驗&#xff0c;進入一個新的頁面&#xff0c;可以上傳圖片&#xff0c;然后…