c++:(map和set的底層簡單版本,紅黑樹和AVL樹的基礎) 二叉搜索樹(BST)底層和模擬實現

文章目錄

  • 二叉搜索樹的概念
  • 二叉搜索樹的操作
    • 二叉搜索樹的查找find
  • 二叉搜索樹的模擬實現
    • 構造節點
    • insert
    • find
    • erase(細節巨多,面試可能會考)
      • a.葉子節點
      • b.有一個孩子
        • 左孩子
        • 右孩子
      • c.有兩個孩子
        • 注意:
      • erase代碼
    • 中序遍歷
  • 二叉搜索樹的應用
    • k模型
      • k模型模擬實現的總代碼
    • k-value模型
      • k-value模型模擬實現的總代碼
  • 二叉搜索樹的不足
  • AVL樹和紅黑樹的出現
  • 總結


二叉搜索樹的概念

二叉搜索樹,它的左子樹的值比根的值小,右子樹的值比根的值大
在這里插入圖片描述
比如這一樹,根節點的值8比左子樹所有節點都大,比右子樹的所有節點都小.

二叉搜索樹的操作

二叉搜索樹的查找find

因為二叉樹有以上特性,所有使得它在搜索方面有極大的優勢.
比如我們要找值為7的節點在不在
1.我們從根節點開始找,因為7<根節點的值8,所有根節點在左子樹
在這里插入圖片描述
2.現在根節點的值為3<7,所有在3的右子樹中
在這里插入圖片描述
3.現在根節點的值為6<7,所有在6的右子樹中,剛好右子樹的節點為7.
在這里插入圖片描述
二叉搜索樹最多尋找高度次,如果走到空還沒有找到,說明這個值不存在

二叉搜索樹的模擬實現

構造節點

	template<class K>struct BSTreeNode{typedef BSTreeNode<K> Node;Node* _left;Node* _right;K _val;BSTreeNode(const K& val):_left(nullptr), _right(nullptr), _val(val){}};

_val里面存節點的值

insert

bool insert(const K& val)
{//a.樹為空,直接構造新節點賦值給根節點if (_root == nullptr){_root = new Node(val);return true;}Node* parent = nullptr;Node* cur = _root;//找到空的節點進行插入while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}// 二叉搜索樹默認不允許重復else{return false;}}cur = new Node(val);if (parent->_val < val){parent->_right = cur;}else{parent->_left = cur;}return true;
}

插入有兩種情況
a.樹為空,直接構造新節點賦值給根節點
b.樹不為空,按照二叉樹的性質找到應該插入的空位置插入.

注意:
在b情況下,要找到新節點的位置,也要找到該節點的父親節點,這樣才能進行鏈接

假設要插入0節點,不光要找到0節點應該放的位置,還要找到0節點的父親1,將他們鏈接起來
在這里插入圖片描述

find

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

按照二叉搜索樹的概念,比根大的往右走,比根小的往左走.
找到返回true,找不到返回false

erase(細節巨多,面試可能會考)

erase里面的細節很多,要細品.

刪除的節點有多種可能

a.葉子節點

在這里插入圖片描述

比如這棵樹我們要刪除4節點,就只需要找到4節點和它的父親節點6,讓父親節點6指向空,再刪除4節點.

b.有一個孩子

特殊情況
要刪除的是根節點,此時要更新新的根節點10.
在這里插入圖片描述

if (_root == cur)
{_root = cur->_right;delete cur;
}
左孩子

右為空,父親指向我的左
有一個左孩子,說明右子樹為空.
此時要讓父親指向3的左邊,此時不清楚是父親的左邊還是父親的右邊指向1節點

父親的左指向我的左

父親的右指向我的左
在這里插入圖片描述
代碼實現

if (cur->_right == nullptr)
{//刪除頭節點if (_root == cur){_root = cur->_left;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_left;elseparent->_left = cur->_left;delete cur;}
}
右孩子

左為空, 父親指向我的右

//左為空, 父親指向我的右
else if (cur->_left == nullptr)
{//刪除頭節點if (_root == cur){_root = cur->_right;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;delete cur;}
}

右孩子的判斷和左孩子類似,方向反過來而已.

c.有兩個孩子

在這里插入圖片描述
找到左邊的最大值或者右邊的最小值,與目標值進行替換.
這里以右邊的最小值為例.

我們尋找右邊的最小值時,同時要找它的父親節點,因為要對它的父親節點進行修改.
找到右邊的最小值為4,將4覆蓋到cur上面,再刪除right_min這個節點.
在這里插入圖片描述

注意:

因為是尋找右子樹的最小值,所以這個最小值理論上應該沒有左子樹.
如果有左子樹,說明有更小的值.但是可能會有右子樹.
所有要讓right_min_parent左節點指向right_min的右節點.
這只是理論上,實際里面還有一個大坑
在這里插入圖片描述
如果我們要刪除的節點:cur和right_min_parent 指向同一個地方時,此時應該讓right_min_parent 的右節點指向right_min的右節點.

//有兩個孩子:找到左邊的最大值或者右邊的最小值,與目標值進行替換//讓這個右最小節點的父親的左邊指向右最小的右邊,因為它此時最多只有右孩子
else
{Node* right_min_parent = cur;Node* right_min = cur->_right;while (right_min->_left){right_min_parent = right_min;right_min = right_min->_left;}cur->_val = right_min->_val;//右最小節點,有坑,是連續存放的有序值if (cur->_right == right_min)right_min_parent->_right = right_min->_right;elseright_min_parent->_left = right_min->_right;delete right_min;
}

erase代碼

bool erase(const K& val){Node* parent = _root;Node* cur = _root;//找到要刪除的目標值while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}else{//只有一個孩子/葉子節點:讓父親節點指向子節點的右(nullptr)//右為空,父親指向我的左if (cur->_right == nullptr){//刪除頭節點if (_root == cur){_root = cur->_left;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_left;elseparent->_left = cur->_left;delete cur;}}//左為空, 父親指向我的右else if (cur->_left == nullptr){//刪除頭節點if (_root == cur){_root = cur->_right;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;delete cur;}}//有兩個孩子:找到左邊的最大值或者右邊的最小值,與目標值進行替換//讓這個右最小節點的父親的左邊指向右最小的右邊,因為它此時最多只有右孩子else{Node* right_min_parent = cur;Node* right_min = cur->_right;while (right_min->_left){right_min_parent = right_min;right_min = right_min->_left;}cur->_val = right_min->_val;//右最小節點,有坑,是連續存放的有序值if (cur->_right == right_min)right_min_parent->_right = right_min->_right;elseright_min_parent->_left = right_min->_right;delete right_min;}return true;}}return false;}

中序遍歷

		void MidOrder(){_MidOrder(_root);cout << endl;}private:void _MidOrder(const Node* root){if (root == nullptr){return;}_MidOrder(root->_left);std::cout << root->_val << " ";_MidOrder(root->_right);}

首先,中序的搜索方式是左子樹 根 右子樹.按照這個順序就能有序的取出搜索二叉樹里面的值了

為什么會有兩個函數?
因為函數的形參沒有this指針,沒法調用_root根節點,我們需要另外一個函數來傳_root根節點

二叉搜索樹的應用

k模型

k模型跟我們上面實現的一樣,只存儲一個值
比如:我們可以用這個功能查找到我們英文作文里面的拼寫錯誤的單詞.
我們可以把詞庫里面所有的英語單詞丟進這個二叉搜索樹,再遍歷整個作文,檢查每個單詞是否存在,不存在就報錯.

k模型模擬實現的總代碼

namespace shh1
{template<class K>struct BSTreeNode{typedef BSTreeNode<K> Node;Node* _left;Node* _right;K _val;BSTreeNode(const K& val):_left(nullptr), _right(nullptr), _val(val){}};//k模型template<class K>class BSTree{typedef BSTreeNode<K> Node;public:bool insert(const K& val){if (_root == nullptr){_root = new Node(val);return true;}Node* parent = nullptr;Node* cur = _root;//找到空的節點進行插入while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}// 二叉搜索樹默認不允許重復else{return false;}}cur = new Node(val);if (parent->_val < val){parent->_right = cur;}else{parent->_left = cur;}return true;}bool erase(const K& val){Node* parent = _root;Node* cur = _root;//找到要刪除的目標值while (cur){if (cur->_val < val){parent = cur;cur = cur->_right;}else if (cur->_val > val){parent = cur;cur = cur->_left;}else{//只有一個孩子/葉子節點:讓父親節點指向子節點的右(nullptr)//右為空,父親指向我的左if (cur->_right == nullptr){//刪除頭節點if (_root == cur){_root = cur->_left;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_left;elseparent->_left = cur->_left;delete cur;}}//左為空, 父親指向我的右else if (cur->_left == nullptr){//刪除頭節點if (_root == cur){_root = cur->_right;delete cur;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;delete cur;}}//有兩個孩子:找到左邊的最大值或者右邊的最小值,與目標值進行替換//讓這個右最小節點的父親的左邊指向右最小的右邊,因為它此時最多只有右孩子else{Node* right_min_parent = cur;Node* right_min = cur->_right;while (right_min->_left){right_min_parent = right_min;right_min = right_min->_left;}cur->_val = right_min->_val;//右最小節點,有坑,是連續存放的有序值if (cur->_right == right_min)right_min_parent->_right = right_min->_right;elseright_min_parent->_left = right_min->_right;delete right_min;}return true;}}return false;}bool find(const K& val){Node* cur = _root;while (cur){if (cur->_val < val){cur = cur->_right;}else if (cur->_val > val){cur = cur->_left;}else{return true;}}return false;}void MidOrder(){_MidOrder(_root);cout << endl;}private:void _MidOrder(const Node* root){if (root == nullptr){return;}_MidOrder(root->_left);std::cout << root->_val << " ";_MidOrder(root->_right);}private:Node* _root = nullptr;};void BST_Test1(){int a[] = { 6,5,1,4,7,2,3,8,9,11,55,68,-1 };BSTree<int> t;for (auto e : a){t.insert(e);}t.MidOrder();}void BST_Test2(){int a[] = { 8 };BSTree<int> t;for (auto e : a){t.insert(e);}t.MidOrder();for (auto e : a){t.erase(e);t.MidOrder();}}
}

k-value模型

每一個關鍵碼key,都有與之對應的值Value,即<Key, Value>的鍵值對。
這個在我們日常生活很常見,比如詞典的翻譯,我們在key里面存英語單詞,value里面存相對應的中文翻譯.
我們就可以通過輸入英文單詞得到其對應的中文翻譯.

下面稍作演示:

void TestBSTree(){BSTree<string, string> dict;dict.Insert("insert", "插入");dict.Insert("erase", "刪除");dict.Insert("left", "左邊");dict.Insert("string", "字符串");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_val << endl;}else{cout << "單詞拼寫錯誤" << endl;}}}

在這里插入圖片描述

k-value模型模擬實現的總代碼

k-value模型的代碼和上面的key模型類似,我們只需要要添加新節點的時候再加一個值就行.

namespace shh2
{template<class K, class V>struct BSTreeNode{typedef BSTreeNode<K, V> Node;Node* _left;Node* _right;K _key;V _val;BSTreeNode(const K& key, const V& val):_left(nullptr), _right(nullptr), _key(key), _val(val){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;Node* _root = nullptr;public:bool Insert(const K& key, const V& val){//頭節點if (_root == nullptr){_root = new Node(key, val);return true;}Node* parent = _root;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, val);if (parent->_key < key)parent->_right = cur;elseparent->_left = cur;return true;}Node* 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 cur;}}return nullptr;}bool Erase(const K& key){Node* parent = _root;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;delete cur;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete cur;}}//右為空,父親的左/右指向我的左else if (cur->_right == nullptr){// 如果為根節點if (cur == _root){_root = cur->_left;delete cur;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;}}//兩個孩子 找到cur左子樹的最大值替換else{Node* left_max_parent = cur;Node* left_max = cur->_left;while (left_max->_right){left_max_parent = left_max;left_max = left_max->_right;}swap(cur->_key, left_max->_key);if (left_max_parent->_left = left_max)left_max_parent->_left = left_max->_left;elseleft_max_parent->_right = left_max->_left;delete left_max;}return true;}}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_val << endl;_InOrder(root->_right);}void InOrder(){_InOrder(_root);}};void TestBSTree(){BSTree<string, string> dict;dict.Insert("insert", "插入");dict.Insert("erase", "刪除");dict.Insert("left", "左邊");dict.Insert("string", "字符串");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_val << endl;}else{cout << "單詞拼寫錯誤" << endl;}}}
};

二叉搜索樹的不足

當二叉搜索樹有序存入了一段值
在這里插入圖片描述
這棵樹會退化成單叉樹,因為插入,查找和刪除的時間復雜度都是高度次,
所以在這種情況下插入,查找和刪除的時間復雜度會接近于N.搜索二叉樹也就失去了它的優勢.

AVL樹和紅黑樹的出現

怎么解決這個問題呢,就要用到AVL和紅黑樹了.
在插入的時候,AVL樹會查看樹的高度是否平衡,
左子樹和右子樹的高度差不超過1.超過1會讓樹的幾個節點之間發生旋轉,最終這棵樹會變成這樣.

在這里插入圖片描述
我們平時調用的容器map和set底層就是用AVL樹和紅黑樹生成的.

總結

二叉搜索樹的插入和查找不難,但是它的刪除細節很多,分類很細,一不留神容易掉坑里面,面試也經常會考.大家如果不懂的話,要多看幾遍.

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

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

相關文章

7-Zip命令行調用命令收集(20個)

列出壓縮文件的內容: 7z l archive.7z 解壓壓縮文件到當前目錄: 7z x archive.7z 解壓壓縮文件到指定目錄: 7z x archive.7z -o"C:\path\to\extract" 創建新的壓縮文件 (添加到archive.7z): 7z a archive.7z file_to_compress 創建包含多個文件的壓縮文件: 7z a arc…

【JVM】了解JVM規范中的虛擬機結構

目錄 JVM規范的主要內容 1&#xff09;字節碼指令集(相當于中央處理器CPU) JVM指令分類 2&#xff09;Class文件的格式 3&#xff09;數據類型和值 4&#xff09;運行時數據區 5&#xff09;棧幀 6&#xff09;特殊方法 7&#xff09;類庫 JVM規范的主要內容 1&#…

Vue3+ElementPlus+TS開發業務功能的問題匯總(持續更新)

1.開發表單彈框功能時遇到兩個問題&#xff1a;加入了校驗規則后&#xff0c;無論下拉框是否選擇數據下面的紅色提示都會觸發顯示不會自動隱藏 &#xff1b; 另外&#xff0c;新增的功能在提交后數據無法重置&#xff0c;這種在修改時可能會出現&#xff0c;但新增正常情況是不…

走進C++:C到C++的過渡

目錄 什么是C呢&#xff1f; C的發展史 多了一些吃前來很香的“語法糖”。 語法糖一&#xff1a;命名空間 命名空間有個強大的功能 如何使用 語法糖二&#xff1a;缺省參數 語法糖三&#xff1a;函數重載 語法糖四&#xff1a;引用 引用傳參 引用返回 引用和…

【ZZULIOJ】1100: 求組合數(函數專題)(Java)

目錄 題目描述 輸入 輸出 樣例輸入 Copy 樣例輸出 Copy 提示 code 題目描述 馬上要舉辦新生程序設計競賽了&#xff0c;與以往不同的是&#xff0c;本次比賽以班為單位&#xff0c;為了全面衡量一個班級的整體水平&#xff0c;要求從一個班的m位同學中任選k位同學代表本…

Android GPU渲染SurfaceFlinger合成RenderThread的dequeueBuffer/queueBuffer與fence機制(2)

Android GPU渲染SurfaceFlinger合成RenderThread的dequeueBuffer/queueBuffer與fence機制&#xff08;2&#xff09; 計算fps幀率 用 adb shell dumpsys SurfaceFlinger --list 查詢當前的SurfaceView&#xff0c;然后有好多行&#xff0c;再把要查詢的行內容完整的傳給 ad…

算法訓練Day35 | ● 343. 整數拆分 ● 96.不同的二叉搜索樹

343. 整數拆分 class Solution { public:int integerBreak(int n) {vector<int> dp(n1, 0);dp[2] 1;for(int i3; i<n1; i){for(int j 1; j<i/2; j){dp[i] max(dp[i], max(j*(i-j), j*dp[i-j]));}}return dp[n];} };參考文章&#xff1a;代碼隨想錄-343. 整數拆分…

找不到msvcp140.dll無法執行代碼的原因分析及修復方法

當用戶在嘗試運行某些應用程序或游戲時&#xff0c;可能會遇到系統彈出錯誤提示&#xff0c;顯示“找不到msvcp140.dll無法執行代碼”這一錯誤信息&#xff0c;它會導致程序無法正常啟動。為了解決這個問題&#xff0c;我經過多次嘗試和總結&#xff0c;找到了以下五種解決方法…

hadoop啟動后沒有namenode,datanode等解決方法

之前用的是虛擬機&#xff0c;在虛擬機上安裝的hadoop&#xff0c;但是后來&#xff0c;電腦恢復出廠設置了&#xff0c;什么都重新開始。就在本地安裝 Linux 子系統。 但是&#xff0c;有時候start-dfs.sh后&#xff0c;jps出現錯誤。 像這種拒絕連接 解決辦法就是如下&…

我的創作紀念日1460天(4年)

機緣 作為一名技術愛好者&#xff0c;我最初成為創作者的初心源于對知識的渴望和對分享的熱情。在參與多個實戰項目的過程中&#xff0c;我積累了豐富的經驗&#xff0c;這些經驗不僅僅是代碼和解決方案&#xff0c;更多的是對問題本質的理解和解決問題的思維方式。我意識到&a…

題目----力扣--移除鏈表元素

題目 給你一個鏈表的頭節點 head 和一個整數 val &#xff0c;請你刪除鏈表中所有滿足 Node.val val 的節點&#xff0c;并返回 新的頭節點 。 示例 1&#xff1a; 輸入&#xff1a;head [1,2,6,3,4,5,6], val 6 輸出&#xff1a;[1,2,3,4,5]示例 2&#xff1a; 輸入&…

如何編譯不同目錄下的兩個文件

1.直接編譯 2.打包成動靜態庫進行鏈接

【智能優化算法】蜜獾優化算法(Honey Badger Algorithm,HBA)

蜜獾優化算法(Honey Badger Algorithm,HBA)是期刊“MATHEMATICS AND COMPUTERS IN SIMULATION”&#xff08;IF 3.6&#xff09;的2022年智能優化算法 01.引言 蜜獾優化算法(Honey Badger Algorithm,HBA)受蜜獾智能覓食行為的啟發&#xff0c;從數學上發展出一種求解優化問題的…

【AMBA Bus ACE 總線 9 -- Non-cache IO device】

請閱讀【AMBA Bus ACE 總線與Cache 專欄 】 歡迎學習:【嵌入式開發學習必備專欄】 文章目錄 ACE Non-cache IO device非緩存I/O的工作原理在ARM中配置非緩存I/O示例場景Non-cache IO device Cache 訪問ACE Non-cache IO device 在ARM架構中,ACE(AXI Coherency Extension,…

Flask 統一攔截器

import osfrom flask import Flask, request, sessionapp Flask(__name__) app.config[SECRET_KEY] os.urandom(24) # 生成24位的隨機數種子&#xff0c;用于產生SESSION IDapp.route(/article/<int:article_id>) def test(article_id):"""路由地址參數…

變量的細節

如何打印不同類型的整數常量 相似于我們需要去聲明類型 public class Var {public static void main(String[] args) {// 1就是int類型常量System.out.println(1);// 120后面加一個L(l)表示他是一個long型的整數System.out.println(120l);} }如何打印不同類型的浮點數常量 與…

解決電腦睡眠后,主機ping不通VMware虛擬機

文章目錄 問題解決方法方法一方法二注意 問題 原因&#xff1a;電腦休眠一段時間&#xff0c;再次打開電腦就ping不通VMware虛擬機。 解決方法 方法一 重啟電腦即可&#xff0c;凡是遇到電腦有毛病&#xff0c;重啟能解決90%問題。但是重啟電腦比較慢&#xff0c;而且重啟…

C++用類模板封裝容器

要實現輸出不同容器的值&#xff0c;且各容器包含的數據類型也不同&#xff0c;可以使用類模板和函數模板來實現。 示例代碼如下&#xff1a; #include <iostream> #include <vector> #include <list>template <typename T> class Container { privat…

算法訓練Day36 | ● 01背包問題 ● 416. 分割等和子集

01背包問題 #include<iostream> #include<vector> using namespace std;int main(){int M;int N;cin>>M>>N;vector<int> weight(M, 0);vector<int> value(M, 0);for(int i0; i<M; i){cin>>weight[i];}for(int i0; i<M; i){ci…

Web3工具集合 - 00

使用 React 和 Material-UI 構建的 Web3 工具集合 大家好&#xff01; 我很高興向大家介紹我最近剛啟動了一個項目&#xff1a;Web3 工具集合。 這個項目的目的是一個集成各種 Web3 工具的網站&#xff0c;旨在為開發人員和加密貨幣愛好者提供便捷的工具和資源。 特點&#…