搜索二叉數(c++)

前言

在學習數據結構的時候我們學習過二叉樹,那啥是搜索二叉樹呢?我們知道單純的二叉樹沒有增刪查改的實際意義,因為沒有任何限制條件的二叉樹其實用處很局限。但是堆就不一樣了,他就是一個二叉樹加上了大小堆的限制條件,在排序方面很有作用,比如堆排序,時間復雜度是n*logN,效率挺高的。那搜索二叉數是加了上限制條件呢?搜索二叉數:本質是一顆二叉樹+左子樹的所有節點都小于根節點的值(左子樹不為空的話)+右子樹的所有結點的值都大于根節點的值(右子樹不為空)+左右子樹也分別為搜索二叉樹。

總結下:搜索二叉樹

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

我們管搜索二叉樹也叫二叉搜索樹,或者二叉排序樹。這里提問下,他為啥可以叫二叉搜索樹呢?

我們看一下這兩個,SBTree(搜索二叉樹),BSTree(二叉搜索樹),你覺得SB好聽呢?還是BSTree,讓你更舒服點呢?這里開玩笑,其實叫啥都行,畢竟都有b樹這種名字(聽著就像在罵人)。可見取名字的人水平不是很高呀。好了,回到正文。

正文

在實現之前我們先看一顆搜索二叉樹,如下圖:

這棵樹就是一顆二叉搜索樹,你用中序遍歷一遍會發現他是有序的,這也是他的實際用處。搜索二叉數主要運用在一些Kye搜索模型,快速判斷在不在的場景,比如小區車輛出入系統。還有就是Key/Value模型,比如大名鼎鼎的高鐵實名認證系統。

搜索二叉數的實現

基本代碼

//結點
template<class K>
struct BSTreeNode
{BSTreeNode<K>* left;BSTreeNode<K>* right;K _key;BSTreeNode(const K& key):left(nullptr), right(nullptr), _key(key){}
};//二叉搜索樹
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public://構造BSTree():_root(nullptr){}//析構~BSTree(){Destroy(_root);}//拷貝構造BSTree(const BSTree<K>& t){_root = Copy(t._root);}//賦值重載 現代寫法BSTree<K>& operator=(BSTree<K>& t){std::swap(_root, t._root);return *this;}//中序遍歷void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->left);cout << root->_key << " ";_InOrder(root->right);}private:
//拷貝 
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;
}
//后序刪除 引用置空 方便很多
void Destroy(Node*& root)
{if (root == nullptr){return;}Destroy(root->left);Destroy(root->right);delete root;root == nullptr;
}Node* _root;
};

這里實現析構,拷貝構造,都是采用套娃的思路,定義一個私有方法,使用私有方法完成代碼的邏輯,在public的方法就是調用這些方法,這樣邏輯很清晰,庫里很多代碼都是這樣實現。如果你學過一點java,會發現java里全是這樣的設計。這里的賦值重載是用的現代寫法,直接交換根節點,返回this指針,這也是推薦的寫法,傳統的一個一個賦值的寫法,太麻煩了,這里不推薦。其實這里拷貝構造這樣設計還有一個原因就是,你無法直接傳一個_root,只能傳一個樹,但是我們有需要_root,因此只能采取這種方式,上傳_root參數,實現拷貝效果。下面講的是搜索二叉數里的核心代碼,有兩個版本,都要掌握。

非遞歸版

這里介紹了,查找,插入,刪除,返回值都是bool值,表示成功與否。

//插入
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->left = cur;}else{parent->right = cur;}}
//查找
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;
}
//刪除(重點) 思路先找后刪除 刪除有兩種可能一個/沒有孩子 或者 多個孩子>=2(替換法)
bool Erase(const K& key)
{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{//左為空if (cur->left == nullptr){if (cur == _root){_root = cur->right;}else{if (parent->right == cur){parent->right = cur->right;}else{parent->left = 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{//cur = 8;Node* leftParent = cur;Node* leftMax = _root->left;while (leftMax->right){leftParent = leftMax;leftMax = leftMax->right;}std::swap(leftMax->_key, cur->_key);if (leftParent->left == leftMax){leftParent->left = leftMax->left;}else{leftParent->right = leftMax->left;}cur = leftMax;}delete cur;return true;}}return false;
}
Find實現

我們前面講過,搜索二叉數的特點,適合排序。因此,我們在查找值的時候,可以根據左右子樹大于/小于根這一特性,實現查找,這樣查找的效率就不會是全部遍歷一遍,如果比根小,那我們就可以直接在左子樹里找,所以這里的時間復雜度就是高度次。我們這里非遞歸實現就是使用while循環cur指針,if else if分支語句,控制查找方向,在循環內找到,就進入else語句。如果循環解釋還沒找到,這時cur指針為空,那說明就沒有,直接返回false 。提問下,這里的時間復雜是,O(n)還是logN,答案是O(n),不是查找高度次嗎,二叉樹的高度不是logN嗎?這里二叉搜索樹不一定就很滿足完全二叉樹的條件,他甚至可以這樣,如下圖

這種情況是肯定存在的,在這種情況幾乎就是O(n),所以這里二叉搜索樹,也沒有很厲害,因此我們后面還要學習紅黑樹,他的底層就是二叉搜索樹。這里我沒有套值進去,大家可以自己套一下,這種情況是包存在的。

Insert實現

插入的代碼的遍歷樹和查找是一個邏輯的,不同就是,插入是要循環外,當cur指針為空的時候再插入結點,所以這里需要new 一個結點,但是這并沒有真正改變鏈表里的指向,我們需要引入一個父節點,來鏈接這里的關系。這里在根指針為空的時候,我們直接特殊處理,new結點賦值給root,然后返回true就行。這里注意二叉搜索樹是不允許重復的值,這也符合他的特性。因此這里插入失敗就是發現了重復的值。這里在用父節點,改變鏈接關系的時候,需要保證插入后還是一個二叉搜索樹,因此,還需要判斷一下他與父節點值的大小,再決定插入在左邊還是右邊。

入上圖,這里插入-1還是2就是不同的結果,或者插入可能就會改變了這棵樹,導致這棵樹不再是二叉搜索樹。

Erase實現

這里把這個刪除放在最后講,是因為刪除是最難的一個實現,而且考的話也應該就是考這個(題目),這里刪除是要分好幾個情況

第一種情況:沒有孩子或者一個孩子-->托孤

對于第一種情況,我們需要定義一個父節點,改變鏈接關系,那這里父節點是賦值給空指針呢還是賦值給cur指針呢,這里只能賦值cur指針,因為有一種情況會導致空指針引用問題,如下圖

第二種情況:兩個及兩個以上的孩子-->替換法

這里也是需要一個父節點,再替換后,改變鏈接關系,但是注意這里是需要做一下判斷的,不一定就是右子樹改變關系,如下圖

遞歸版

	//遞歸實現bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseE(const K& key){return _EraseE(_root, key);}

遞歸是需要上傳一個root節點,但是我們只能上傳一個個樹,所以這里采取內部上傳根節點,調用私有方法實現。

Find實現
	bool _FindR(Node* root, const K& key){if (root == nullptr) return false;if (root->_key < key) {_FindR(root->right, key);}if (root->_key > key){_FindR(root->left, key);}else{return true;

遞歸版就簡單多了,使用二叉搜索樹的特性判斷,和上面循環的版本是一樣的,只不過是吧循環該遞歸了而已

Insert實現
	//這里引用 起到了自動鏈接的作用 bool _InsertR(Node*& root, const K& key){//為空鏈接 if (root == nullptr) {root = new Node(key);return true;}if (root->_key < key) {_InsertR(root->right, key);}if (root->_key > key){_InsertR(root->left, key);}//找不到else{return false;}}

這里遞歸實現插入也很簡單,非遞歸的版本是需要用父節點來鏈接,但是遞歸的版本我們可以不用這樣子做,把參數改成引用,這樣子改變的就不再是局部變量,而是直接改變本體。

Erase實現
	//遞歸實現bool _EraseE(Node*& root, const K& key){if (root == nullptr) return false;//這里是if else if 一個執行了 另一個就不能執行 否則 你出錯if (root->_key < key){_EraseE(root->right, key);}else if(root->_key > key){_EraseE(root->left, key);}//找到了else{Node* del = root;if (root->left == nullptr){root = root -> right;}else if (root->right == nullptr){root = root->left;}else{Node* leftMax = root->left;while (leftMax->left){leftMax = leftMax->right;}std::swap(root->_key, leftMax->_key);//這里不能傳leftMax 指向改變會亂return _EraseE(root->left, key);}delete del;}return true;}

這里遞歸實現刪除相比于非遞歸的版本就簡單多了,刪除里面尋找代碼的邏輯和查找是一樣的,沒啥好講,主要是找到了之后我們該如何處理。上面插入的時候我們使用了引用直接改變了本體,不用再使用父節點改變鏈接關系,這里也是可以用引用,不用在搞一個父節點。刪除的思路和非遞歸是一致的,也是分兩種情況,不過這里有點復雜,因為你遞歸傳的是一個節點的左子樹或右子樹的指針的引用,因此你是可以直接改變這個本體,所以在判斷的時候,直接就用這個引用本身判斷他的左子樹還是右子樹為空,然后在選擇賦值給這個引用,就改變鏈接關系。然后替換也是找到節點交換節點的值,不過這里我們就可以再次遞歸,而不是直接刪除,因為這里你沒有父節點,也無法直接刪除,否則鏈接關系也沒法改變,我們干脆把遞歸思想貫徹到底,上傳root->left去刪除節點就行。注意這里不可以傳leftMax,指向會直接改變,這里引用比較復雜,大家畫圖理解建議,這里剛開始看這里的代碼,我也是沒想通,但花了圖,就想明白了。

這里遞歸不理解,其實很好理解,畫遞歸展開圖,就可以解決大部分問題。

總結

搜索二叉樹其實整體上講還是比較簡單的,主要是為后面的紅黑樹打基礎,在學習C語言的時候我們發現有些數據結構很難寫,但是學到c++之后,會發現這些數據結構直接放在STL容器里,而且他們的實現也好寫多了,像這個二叉搜索樹更適用于c++寫,其實二叉樹的學習,以及到后面的紅黑樹,都是更適用于c++。

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

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

相關文章

vc MFC在opencv的Mat圖像上顯示中文:Mat轉位MFC的CImage,畫圖寫文字,再轉回Mat

vc MFC在opencv的Mat圖像上顯示中文&#xff1a;Mat轉位MFC的CImage&#xff0c;畫圖寫文字&#xff0c;再轉回Mat // Step 1 創建CImage獲取dc int iImgW matImgSized.cols; int iImgH matImgSized.rows; int iChannel matImgSized.channels(); bool bCon matImgSized.is…

Docker環境部署

目錄 一&#xff1a;Docker 概述 1.什么是 Docker 2:Docker 的優勢 3.Docker 的應用場景 4:Docker 核心概念 二:Docker 安裝 1:本安裝方式使用阿里的軟件倉庫 三:Docker 鏡像操作 1:獲取鏡像 2.查看鏡像信息 3.查看鏡像詳細信息 4.修改鏡像標簽(老名字新名字) 5:刪…

Axios 攔截器實現原理深度剖析:構建優雅的請求處理管道

在構建現代前端應用時&#xff0c;網絡請求處理是關鍵環節。作為最流行的HTTP客戶端庫之一&#xff0c;Axios通過其攔截器機制&#xff08;Interceptors&#xff09;提供了強大的請求/響應處理能力。本文將深入Axios源碼&#xff0c;揭示攔截器背后的精妙設計與實現原理。 一、…

寶塔安裝nginx-http-flv-module,音視頻直播,第二篇

1&#xff0c;先安裝環境安裝nginx 先卸載原有nigix nigix 大于等于 1.2.6 cd /www/server # 進入寶塔目錄 yum install git -y git clone https://gitee.com/winshining/nginx-http-flv-module.git 使用源碼安裝nigix 在 自定義模塊 區域點擊「添加」&#xff0c;填寫以下參…

低延遲4G專網:保障關鍵業務的實時通信

在工業互聯網、智慧園區、應急通信等對“實時性”要求極高的場景中&#xff0c;網絡延遲的高低&#xff0c;直接決定了業務運行的可靠性與安全性。IPLOOK依托多年核心網研發經驗&#xff0c;推出的低延遲4G專網解決方案&#xff0c;正是為此類關鍵業務打造的“通信專線”&#…

NLP語言發展路徑分享

自然語言處理初期發展歷程 早期&#xff1a;離散表示 one-hot&#xff08;只表達“有/無”&#xff0c;語義完全丟失&#xff09;→ n-gram&#xff08;局部上下文&#xff0c;但高維稀疏&#xff09;→ TF-IDF&#xff08;考慮詞頻與權重&#xff0c;但不能表達詞關聯&#x…

如何將文件從安卓設備傳輸到電腦?

將文件從 Android 手機傳輸到 PC 是例行公事嗎&#xff1f;想讓文件傳輸更輕松嗎&#xff1f;幸運的是&#xff0c;您可以從本文中獲得 7 種方法&#xff0c;其中包含詳細的步驟&#xff0c;幫助您輕松了解如何將文件從 Android 傳輸到 PC&#xff0c;涵蓋了從無線工具到傳統 U…

【經驗分享】淺談京東商品SKU接口的技術實現原理

京東商品 SKU 接口的技術實現原理涉及數據建模、架構設計、接口協議、安全機制及性能優化等多個技術層面。以下從技術角度詳細拆解其實現邏輯&#xff1a; 一、SKU 數據模型與存儲架構 1. SKU 數據模型設計 核心字段定義&#xff1a; 基礎屬性&#xff1a;SKU ID、商品名稱、…

虛擬機配置node.js(前端環境搭建)

1.在windows下安裝node.js&#xff08;以及npm&#xff09; 修改npm鏡像為阿里云的 npm install --registryhttps://registry.npmmirror.com 2.在Linux下安裝node.js&#xff08;Centos7 只支持16版本之前的&#xff09; wget https://npmmirror.com/mirrors/node/v15.14.0/n…

多模態大語言模型arxiv論文略讀(129)

Task Success Prediction for Open-Vocabulary Manipulation Based on Multi-Level Aligned Representations ?? 論文標題&#xff1a;Task Success Prediction for Open-Vocabulary Manipulation Based on Multi-Level Aligned Representations ?? 論文作者&#xff1a;M…

【Redis】Redis 關于 BigKey 的實踐規約

目錄 一、BigKey 的概念 1.1 普通 key 的設計規則 1.2 BigKey 的定義 1.3 BigKey 存在的問題 二、BigKey 的發現與解決方案 第一種方式&#xff1a;redis-cli --bigkeys 第二種方式&#xff1a;scan掃描 第三種方式&#xff1a;第三方工具 第四種方式&#xff1a;網絡…

Golang 與 C/C++ 交互實踐

在軟件開發的實際場景中&#xff0c;我們常常會遇到需要將不同語言的優勢結合起來的情況。Golang 憑借其高效的并發性能和簡潔的語法&#xff0c;在網絡編程和系統開發領域備受青睞&#xff1b;而 C/C 則以其強大的底層操作能力&#xff0c;在系統資源管理方面具有獨特優勢。那…

五子棋流量主小程序單模式多模式開源版

功能和特點&#xff1a; 核心游戲功能&#xff1a; 1515 標準棋盤 黑白棋交替落子 自動判斷勝負和平局 悔棋功能 計時功能 UI 設計&#xff1a; 木紋風格棋盤 立體感棋子&#xff08;使用陰影和漸變&#xff09; 響應式布局&#xff0c;適配不同屏幕尺寸 勝利彈窗動畫 交互體驗…

Python古代文物成分分析與鑒別研究:灰色關聯度、嶺回歸、K-means聚類、決策樹分析

原文鏈接&#xff1a;tecdat.cn/?p42718分析師&#xff1a;Gan Tian 在文化遺產保護領域&#xff0c;古代玻璃制品的成分分析一直是研究中西方文化交流的關鍵課題。作為數據科學家&#xff0c;我們在處理某博物館委托的古代玻璃文物保護咨詢項目時&#xff0c;發現傳統分析方法…

RabbitMQ消息隊列實戰指南

RabbitMQ 是什么&#xff1f; RabbitMQ是一個遵循AMQP協議的消息中間件&#xff0c;它從生產者接收消息并傳遞給消費者&#xff0c;在這個過程中&#xff0c;根據路由規則進行消息的路由、緩存和持久化。 AMQP&#xff0c;高級消息隊列協議&#xff0c;是應用層協議的一個開放…

用Java將PDF轉換成GIF

為什么要將 PDF 文件轉換為 GIF 圖片&#xff1f; PDF 是一種矢量圖像格式&#xff08;因此可以根據指定的尺寸進行渲染&#xff09;&#xff0c;而 GIF 是一種有損的、固定尺寸的位圖文件&#xff0c;像素值固定。因此&#xff0c;將 PDF 轉換為 GIF 文件時&#xff0c;我們需…

Redis之分布式鎖(2)

上一篇文章我們介紹了什么是分布式鎖和分布式鎖的一些基本概念。這篇文章我們來講解一下基于數據庫如何實現分布式鎖。 基于數據庫實現分布式鎖 基于數據庫實現分布式鎖可以分為兩種方式&#xff0c;分別是基于數據庫表和基于數據庫排他鎖。 基于數據庫表 要實現分布式鎖&…

智能檢測護航電池產業:容量設備如何提升效率與安全?

電池容量是衡量其儲能能力的重要指標&#xff0c;直接影響設備續航與使用壽命。電池容量檢測設備通過模擬真實使用場景&#xff0c;精準測量電池的充放電性能&#xff0c;為電池生產、質檢及回收環節提供關鍵數據支持&#xff0c;成為保障電池品質與安全的核心工具。 核心功能…

介紹一款免費MES、開源MES系統、MES源碼

一、系統概述&#xff1a; 萬界星空科技免費MES、開源MES、商業開源MES、市面上最好的開源MES、MES源代碼、適合二開的開源MES。 1.萬界星空開源MES制造執行系統的Java開源版本。 開源mes系統包括系統管理&#xff0c;車間基礎數據管理&#xff0c;計劃管理&#xff0c;物料控制…

構建高性能日志系統:QGroundControl日志模塊深度解析

引言&#xff1a;日志系統的重要性 在無人機地面站系統中&#xff0c;日志記錄是診斷問題、分析性能的關鍵基礎設施。QGroundControl&#xff08;QGC&#xff09;作為領先的開源無人機地面站軟件&#xff0c;其日志系統設計值得深入探討。本文將揭示QGC日志系統的核心技術&…