數據結構 | B樹、B+樹、B*樹

文章目錄

  • 搜索結構
  • B樹
    • B樹的插入
    • B樹的遍歷
    • B樹的性能
  • B+樹
    • B+樹的插入
    • B+樹的遍歷
  • B*樹
    • B*樹的插入
  • 總結


搜索結構

  • 如果我們有大量的數據需要永久存儲,就需要存儲到硬盤之中。
  • 但是硬盤的訪問速度遠遠小于內存,并且由于數據量過大,無法一次性加載到內存中。

此時,就可以考慮將數據存儲在硬盤中,而數據的地址則加載到內存中,通過某種搜索結構進行存儲,使用時只需要通過該結構查找到地址,在通過地址去找到對應的數據即可。

常用的幾種搜索結構:二叉搜索樹、AVL樹、紅黑樹、哈希、位圖、布隆過濾器。

考慮到查找性能以及內存消耗,其中適合這種場景的只有平衡二叉搜索樹(AVL、紅黑樹)。

在這里插入圖片描述

但即使平衡二叉搜索樹的搜索性能能達到 O(log2N),由于數據量過于龐大,例如存儲了 10億個數,則可能最多需要查找 30次。這個數字看起來不是很多,因為之前我們比較的是內存的速度,即使是 10億個數 也能一瞬間查找完。但是對于硬盤來說,由于硬盤的速率低,每一次 IO 都意味這大量的損耗,所以這種方法也不太合適。

如果想要提高查找的效率,那么唯一的方法就是壓縮樹的高度,而壓縮的方法,就是將二叉樹變為 M叉樹,也就是使用到 M路平衡搜索樹——B樹


B樹

B樹 即一棵平衡的 M路平衡搜索樹(M > 2),可以是 空樹 或者滿足以下性質:

  • 根節點至少有 2 個孩子、最多有 M 個孩子;
  • 除根結點外的非葉節點有 i 個孩子, M/2(上取整) <= i <= M
  • 每個非葉節點有 j 個關鍵字, M/2-1(上取整) <= j <= M-1 ,并且以升序排列
  • key[i]key[i+1] 之間的孩子節點的值介于 key[i]、key[i+1] 之間;
  • 所有的葉子節點都在同一層。

對照一棵 M=3B樹 來理解:
在這里插入圖片描述

節點實現

template<class K, int M = 3>
struct BTreeNode
{K _keys[M]; // 存放元素BTreeNode<K, M>* _pSub[M+1]; // 存放孩子節點,注意:孩子比數據多一個BTreeNode<K, M>* _pParent; // 在分裂節點后可能需要繼續向上插入,為實現簡單增加parent域size_t _size; // 節點中有效元素的個數BTreeNode(): _pParent(NULL), _size(0){for(size_t i = 0; i <= M; ++i)_pSub[i] = NULL;}
};

B樹的插入

下面拿一個 M=3三叉B樹 來舉例子。(PS:三叉樹即每個節點至多 3 個孩子,2keykey 的數量永遠比孩子少一個)

假設使用以下數據構建B樹 {63, 131, 85, 39, 148, 31, 111}(B樹從葉子節點的位置進行插入):

在這里插入圖片描述
首先依次插入前三個節點,當插入到第三個時,由于三叉樹的 key 只能有 2 個,所以此時會采取分裂的方法來維持樹的平衡。

B樹分裂的規則是:創建一個兄弟節點,拷貝右半區間的數據到兄弟節點,左半區間保留,中位數放到父親節點(如果沒有則創建新的根節點)。

B樹AVL 的旋轉、紅黑樹的旋轉+變色不一樣,它使用了分裂的方法來維持樹的平衡,這樣的好處是既能做到平衡,也保證了非根節點至少有一半的空間利用率

所以此時按照上面的規則進行分裂:
在這里插入圖片描述

接著插入 39,148

在這里插入圖片描述

然后插入 31,開始分裂:
在這里插入圖片描述
接著插入111,此時再次發生分裂:
在這里插入圖片描述
此時可以看到,葉子節點已經分裂成了 4 個,并且根節點的 key 也達到了 3 個,不符合 B樹 的性質:每個根節點最多有 M 個孩子、M - 1 個key,因此繼續分裂:
在這里插入圖片描述
此時,樹重新平衡。從上面可以看出來,本質上B樹的設計還是參考了二叉搜索樹。


B樹的遍歷

B樹的有序遍歷還是通過中序遍歷來完成,不過需要通過隊列或者遞歸遍歷完這個節點的所有的key

void _InOrder(PNode pRoot)
{if(NULL == pRoot)return;for(size_t i = 0; i < pRoot->_size; ++i){_InOrder(pRoot->_pSub[i]);cout << pRoot->_keys[i] << " ";}_InOrder(pRoot->_pSub[pRoot->_size]);
}

B樹的性能

作為一個M路平衡搜索樹,B樹的搜索性能達到了 log?M+1N\log_{M+1}NlogM+1?N ~ log?M/2N\log_{M/2}NlogM/2?N 之間,查找到指定關鍵字的方法是:

  • 在根結點所包含的關鍵字 K1、…、Kn 查找給定的關鍵字(可用順序查找或二分查找法),若找到等于給定值的關鍵字,則查找成功;
  • 否則,一定可以確定要查找的關鍵字在 KiKi+1之間,Pi 為指向子樹根節點的指針,此時取指針 Pi 所指的結點繼續查找,直至找到,或指針 Pi 為空 時查找失敗。

比起二叉平衡搜索樹,速度快了一大截,并且大大的減少了硬盤 IO 的次數,所以在文件系統以及數據庫索引等方面使用的都是這種數據結構。


B+樹

B+樹是B樹的變形,主要性質如下:

  • 其定義基本與B樹相同
  • 非葉子節點的 孩子key 個數相同(簡化規則)
  • 非葉子節點由葉子節點的最小值構成(充當索引,所以不可能在非葉子節點命中
  • 所有數據都出現在葉子節點,并且所有葉子節點都鏈接起來,同時是有序的。(方便遍歷)

在這里插入圖片描述


B+樹的插入

這里為了方便,使用 三階B+樹 舉例子,這里還是使用同樣的數據。{63, 131, 85, 39, 148, 31, 111}

首先插入 63,并把 63 作為父節點的索引:
在這里插入圖片描述

接著插入 131,85 :
在這里插入圖片描述
當插入 39 時,發生分裂,分裂規則與之前略有區別。

B+樹的分裂規則:因為此時父節點存儲的是索引,所以此時只會將左半部分數據保留,右半部分數據放入新建的兄弟節點,并且會向上更新父節點的索引。

在這里插入圖片描述
當插入 111 時,發生分裂:

在這里插入圖片描述


B+樹的遍歷

從上面可以看出來,B+樹的主要特點其實就是更方便進行遍歷,因為其將所有數據存儲在葉子節點,所有非葉子節點就相當于一個索引。其所有葉子節點連接起來,像遍歷鏈表一樣遍歷B+樹。這樣的結構使得B+樹的查找相當于對關鍵字全集做一次二分查找。

所以通常文件的索引系統都會采用B+樹的結構。


B*樹

B*樹則又是對B+樹的變形,其性質如下:

  • 其定義基本與B+樹相同
  • 將非葉子節點也連接起來
  • 分裂方式再次修改,保證每個節點中 key 的數量 [2/3 * M,M](提高空間利用率,從1/2提升到了2/3)

在這里插入圖片描述


B*樹的插入

B*樹再次修改了插入規則,規則修改為:

  • 如果當前節點數據已滿而兄弟節點未滿,則將數據放入兄弟節點,而當兩個節點都滿了之后再進行分裂;
  • 分裂時,在原節點與兄弟節點之間創建新節點,從兩個節點分別取出 1/3 的數據放入新創建的結點。

還是原來那些數據 {63, 131, 85, 39, 148, 31, 111} :
在這里插入圖片描述

接下來插入 39
在這里插入圖片描述

插入 148、31

在這里插入圖片描述

此時插入111,發生分裂,從兩邊各取走 1/3 的數據:
在這里插入圖片描述

從上面可以看出,B*樹的最大改進就是將B+樹的空間利用率從1/2提升到了2/3,并且對非葉子節點也進行了連接,查找更加便利。


總結

在這里插入圖片描述
在這里插入圖片描述

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

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

相關文章

MySQL 索引 :哈希索引、B+樹索引、全文索引

文章目錄索引引言常見的索引哈希索引自適應哈希索引B樹索引聚集索引非聚集索引使用方法聯合索引最左前綴匹配規則覆蓋索引全文索引使用方法索引 引言 為什么需要索引&#xff1f; 倘若不使用索引&#xff0c;查找數據時&#xff0c;MySQL必須遍歷整個表。而表越大&#xff0c;…

服裝店怎么引流和吸引顧客 服裝店鋪收銀系統來配合

實體店的同城引流和經營是實體經濟的一個重要的一環&#xff0c;今天我們來分享服裝行業的實體店鋪怎么引流和吸引、留住顧客&#xff0c;并實現復購。大家點個收藏&#xff0c;不然劃走就再也找不到了&#xff0c;另外可以點個關注&#xff0c;下次有新的更好的招&#xff0c;…

約瑟夫環(丟手絹問題)

文章目錄問題描述思路代碼實現問題描述 有 1~N 個數字&#xff0c;從 1~m 依次報數&#xff0c;數到 m 的數字要被刪掉&#xff0c;求最后剩下的數字是&#xff1f; 思路 第一次報數第二次報數1n-m12n-m2……m-2n-2m-1n-1m被刪掉了m11m22……n-1n-1-mnn-m 通過上面的表格&…

MySQL 鎖的相關知識 | lock與latch、鎖的類型、簡談MVCC、鎖算法、死鎖、鎖升級

文章目錄lock與latch鎖的類型MVCC一致性非鎖定讀&#xff08;快照讀&#xff09;一致性鎖定讀&#xff08;當前讀&#xff09;鎖算法死鎖鎖升級lock與latch 在了解數據庫鎖之前&#xff0c;首先就要區分開 lock 和 latch。在數據庫中&#xff0c;lock 和 latch 雖然都是鎖&…

Hibernate使用原生SQL適應復雜數據查詢

HQL盡管容易使用&#xff0c;但是在一些復雜的數據操作上功能有限。特別是在實現復雜的報表統計與計算&#xff0c;以及多表連接查詢上往往無能為力&#xff0c;這時可以使用SQL&#xff08;Native SQL&#xff09;實現HQL無法完成的任務。 1、使用SQL查詢 使用SQL查詢可以通過…

MySQL 存儲引擎 | MyISAM 與 InnoDB

文章目錄概念innodb引擎的4大特性索引結構InnoDBMyISAM區別表級鎖和行級鎖概念 MyISAM 是 MySQL 的默認數據庫引擎&#xff08;5.5版之前&#xff09;&#xff0c;但因為不支持事務處理而被 InnoDB 替代。 然而事物都是有兩面性的&#xff0c;InnoDB 支持事務處理也會帶來一些…

MySQL 事務 | ACID、四種隔離級別、并發帶來的隔離問題、事務的使用與實現

文章目錄事務ACID并發帶來的隔離問題幻讀&#xff08;虛讀&#xff09;不可重復讀臟讀丟失更新隔離級別Read Uncommitted (讀未提交)Read Committed (讀已提交)Repeatable Read (可重復讀)Serializable (可串行化)事務的使用事務的實現Redoundo事務 事務指邏輯上的一組操作。 …

MySQL 備份與主從復制

文章目錄備份主從復制主從復制的作用備份 根據備份方法的不同&#xff0c;備份可劃分為以下幾種類型&#xff1a; 熱備(Hot Backup) &#xff1a; 熱備指的是在數據庫運行的時候直接備份&#xff0c;并且對正在運行的數據庫毫無影響&#xff0c;這種方法在 MySQL 官方手冊中又…

C++ 流的操作 | 初識IO類、文件流、string流的使用

文章目錄前言IO頭文件iostreamfstreamsstream流的使用不能拷貝或對 IO對象 賦值條件狀態與 iostate 類型輸出緩沖區文件流fstream類型文件模式文件光標函數tellg() / tellp()seekg() / seekp()向文件存儲內容/讀取文件內容string流istringstreamostringstream前言 我們在使用 …

Hibernate 更新部分更改的字段 hibernate update

Hibernate 中如果直接使用 Session.update(Object o);或則是Session.updateOrUpdate(Object o); 會把這個表中的所有字段更新一遍。 如&#xff1a; ExperClass4k e new ExperClass4k(); e.setTime(time); e.setQ_num(q_num); e.setK(k); if (str "finch_fix")…

C++ 類的行為 | 行為像值的類、行為像指針的類、swap函數處理自賦值

文章目錄概念行為像值的類行為像指針的類概念引用計數動態內存實現計數器類的swap概念swap實現自賦值概念 行為像值的類和行為像指針的類這兩種說法其實蠻拗口的&#xff0c;這也算是 《CPrimer》 翻譯的缺點之一吧。。。 其實兩者的意思分別是&#xff1a; 行為像值的類&am…

C++ 右值引用 | 左值、右值、move、移動語義、引用限定符

文章目錄C11為什么引入右值&#xff1f;區分左值引用、右值引用move移動語義移動構造函數移動賦值運算符合成的移動操作小結引用限定符規定this是左值or右值引用限定符與重載C11為什么引入右值&#xff1f; C11引入了一個擴展內存的方法——移動而非拷貝&#xff0c;移動較之拷…

且談關于最近軟件測試的面試

前段時間有新的產品需要招人&#xff0c;安排和參加了好幾次面試&#xff0c;下面就談談具體的面試問題&#xff0c;在面試他人的同時也面試自己。 面試問題是參與面試同事各自設計的&#xff0c;我也不清楚其他同事的題目&#xff0c;就談談自己設計的其中2道題。 過去面試總是…

C++ 多態 | 虛函數、抽象類、虛函數表

文章目錄多態虛函數重寫重定義&#xff08;參數不同&#xff09;協變&#xff08;返回值不同&#xff09;析構函數重寫&#xff08;函數名不同&#xff09;final和override重載、重寫、重定義抽象類多態的原理虛函數常見問題解析虛函數表多態 一種事物&#xff0c;多種形態。換…

C++ 運算符重載(一) | 輸入/輸出,相等/不等,復合賦值,下標,自增/自減,成員訪問運算符

文章目錄輸出運算符<<輸入運算符>>相等/不等運算符復合賦值運算符下標運算符自增/自減運算符成員訪問運算符輸出運算符<< 通常情況下&#xff0c;輸出運算符的第一個形參是一個 非常量ostream對象的引用 。之所以 ostream 是非常量是因為向流寫入內容會改變…

C++ 重載函數調用運算符 | 再探lambda,函數對象,可調用對象

文章目錄重載函數調用運算符lambdalambda等價于函數對象lambda等價于類標準庫函數對象可調用對象與function可調用對象function函數重載與function重載函數調用運算符 函數調用運算符必須是成員函數。 一個類可以定義多個不同版本的調用運算符&#xff0c;互相之間應該在參數數…

C++ 運算符重載(二) | 類型轉換運算符,二義性問題

文章目錄類型轉換運算符概念避免過度使用類型轉換函數解決上述問題的方法轉換為 bool顯式的類型轉換運算符類型轉換二義性重載函數與類型轉換結合導致的二義性重載運算符與類型轉換結合導致的二義性類型轉換運算符 概念 類型轉換運算符&#xff08;conversion operator&#…

Tomcat中JVM內存溢出及合理配置

Tomcat本身不能直接在計算機上運行&#xff0c;需要依賴于硬件基礎之上的操作系統和一個Java虛擬機。Tomcat的內存溢出本質就是JVM內存溢出&#xff0c;所以在本文開始時&#xff0c;應該先對Java JVM有關內存方面的知識進行詳細介紹。 一、Java JVM內存介紹 JVM管理兩種類型的…

俄羅斯農民乘法 | 快速乘

文章目錄概念概念 俄羅斯農民乘法經常被用于兩數相乘取模的場景&#xff0c;如果兩數相乘已經超過數據范圍&#xff0c;但取模后不會超過&#xff0c;我們就可以利用這個方法來拆位取模計算貢獻&#xff0c;保證每次運算都在數據范圍內。 A 和 B 兩數相乘的時候我們如何利用加…

Linux網絡編程 | socket選項設定 及 網絡信息API

文章目錄讀取和設置 socket 選項SO_REUSEADDRSO_RCVBUF 和 SO_SNDBUFSO_RCVLOWAT 和 SO_SNDLOWATSO_LINGER 選項網絡信息APIgethostbyname 和 gethostbyaddrgetservbyname 和 getservbyportgetaddrinfogetnameinfo讀取和設置 socket 選項 正如 fcntl 系統調用是控制文件描述符…