https://blog.csdn.net/weixin_41948075/article/details/100180136
常⻅的數組、鏈表、棧和隊列都是線性結構,在存儲⼤量數據時訪問速度⽐較慢,⽽樹(tree)則是⼀種⾮線性結構,使得訪問時間復雜度降低到O(logn)。
下圖是使⽤樹結構存儲的集合 {A,B,C,D,E,F,G,H,I,J,K,L,M,P,Q} 的示意圖。對於數據 A 來說,和數據 B、C、D 有關系;對於數據 B 來說,和 E、F 有關系。這就是“⼀對多”的關系。將具有“⼀對多”關系的集合中的數據元素按照圖下圖的形式進⾏存儲,整個存儲形狀在邏輯結構上看,類似於實際⽣活中倒着的樹,所以稱這種存儲結構為“樹型”存儲結構。
⼀些概念
結點:樹中⼀個最基本的數據稱為⼀個節點(node) A-Q
根節點:以某個節點為⼀個根節點,從這個節點往下形成⼀棵⼦樹(sub-tree)。 A
葉⼦結點: 整棵樹最年輕的⼀代,在樹中稱為樹的葉節點(leaf node)。通俗就是沒有孩⼦的結點 B C H I K L M N P Q
每個⽗輩有⼏個孩⼦,每個孩⼦有其⽗輩,還有其祖輩。⽗輩稱為⽗節點(parent),孩⼦稱為⼦節點(child)。每個⽗節點可以對應⼀個或者多個⼦節點,
⼀個⼦節點只能有⼀個⽗節點。具有相同⽗節點的節點稱為兄弟節點(siblings)。
結點的層次:根節點為第⼀層,根節點的孩⼦結點為第2層,依次類推
深度:樹的層次的最⼤值
結點的度:結點擁有⼦樹的數⽬
⼆叉樹: 每個結點最多有2顆⼦樹 ,簡單地理解,滿⾜以下兩個條件的樹就是⼆叉樹:
1. 本身是有序樹;
2. 樹中包含的各個節點的度不能超過 2,即只能是 0、1 或者 2;
tree
滿⼆叉樹:除了葉⼦結點,每個節點都有2個孩⼦結點,且葉⼦節點在同⼀層上
完全⼆叉樹:從樹的根節點,從上⾄下,從左到右,依次填滿結點形成的⼆叉樹,滿⼆叉樹是⼀顆完全⼆叉樹
⼆叉樹的搜索速度取決於樹的層級
B TREE(B-TREE)也就是平衡多路搜索樹,也就是多叉的意思
當 數據庫⾥單表存⻋處的數值可能是百萬級或者千萬級,如果有⼆叉樹存儲就會需要百萬個千萬個樹節點,有沒有辦法讓⼀個節點存儲多個元素(數值),
那么,是不是可以減少樹節點,從⽽減少樹整體層級⾼度呢,要知道,減少樹層級⾼度的過程,就是優化查詢效率的過程。
所以,B樹有出現了,B樹屬於多叉樹,⼜名平衡多路查找樹,數據庫索引技術⾥⼤量使⽤是B樹和B+樹的數據結構,顧名思義,就是每個節點上可以存儲多個數值元素,B樹所有的葉⼦節點都處於同⼀層級。不存在層級⾼度差的問題。
每個節點保存的關鍵字的個數和路數關系為:關鍵字個數 = 路數 – 1。
B-樹的搜索,從根結點開始,對結點內的關鍵字(有序)序列進⾏⼆分查找,如果命中則結束,否則進⼊查詢關鍵字所屬范圍的⼉⼦結點;重復,直到
所對應的⼉⼦指針為空,或已經是葉⼦結點;
實際磁盤舉例:
來模擬下查找29的過程:
(1) 根據根結點指針找到⽂件⽬錄的根磁盤塊1,將其中的信息導⼊內存。 【磁盤IO操作1次】
(2) 此時內存中有兩個⽂件名17,35和三個存儲其他磁盤⻚⾯地址的數據。根據算法我們發現17<29<35,因此我們找到指針p2。
(3) 根據p2指針,我們定位到磁盤塊3,並將其中的信息導⼊內存。 【磁盤IO操作2次】
(4) 此時內存中有兩個⽂件名26,30和三個存儲其他磁盤⻚⾯地址的數據。根據算法我們發現26<29<30,因此我們找到指針p2。
(5) 根據p2指針,我們定位到磁盤塊8,並將其中的信息導⼊內存。 【磁盤IO操作3次】 (6) 此時內存中有兩個⽂件名28,29。根據算法我們查找到29,並定位了該⽂件內存的磁盤地址。
分析上⾯過程,發現需要3次磁盤I/O操作,和3次內存查找操作。由於內存中的關鍵字是⼀個有序表結構,可以利⽤⼆分法查找提⾼效率。⽽3次磁盤I/O操作是影響整個B-Tree查找效率的決定因素。B-Tree相對於平衡⼆叉樹縮減了節點個數,使每次磁盤I/O取到內存的數據都發揮了作⽤,從⽽提⾼了查詢效率。
B Tree 能夠很好的利⽤操作系統和磁盤的交互特性, MySQL為了很好的利⽤磁盤的預讀能⼒,將⻚⼤⼩設置為16K,即將⼀個節點(磁盤塊)的⼤⼩設置為16K,⼀次IO將⼀個節點(16K)內容加載進內存。這⾥,假設關鍵字類型為 int,即4字節,若每個關鍵字對應的數據區也為4字節,不考慮⼦節點引⽤的情況下,則上圖中的每個節點⼤約能夠存儲(16 * 1000)/ 8 = 2000個關鍵字,共2001個路數。對於⼆叉樹,三層⾼度,最多可以保存7個關鍵字,
⽽對於這種有2001路的B樹,三層⾼度能夠搜索的關鍵字個數遠遠的⼤於⼆叉樹。這⾥順便說⼀下:在B Tree保證樹的平衡的過程中,每次關鍵字的變化,都會導致結構發⽣很⼤的變化,這個過程是特別浪費時間的,所以創建索引⼀定要創建合適的索引,⽽不是把所有的字段都創建索引,創建冗余索引只會在對數據進⾏新增,刪除,修改時增加性能消耗。
B-Tree每個節點中不僅包含數據的key值,還有data值。⽽每⼀個⻚的存儲空間是有限的,如果data數據較⼤時將會導致每個節點(即⼀個⻚)能存儲的
key的數量很⼩,當存儲的數據量很⼤時同樣會導致B-Tree的深度較⼤,增⼤查詢時的磁盤I/O次數,進⽽影響查詢效率
B+Tree
B+樹是B-樹的變體,也是⼀種多路搜索樹:
1.其定義基本與B-樹同,除了:
2.⾮葉⼦結點的⼦樹指針與關鍵字個數相同;
3.⾮葉⼦結點的⼦樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的⼦樹(B-樹是開區間);
5.為所有葉⼦結點增加⼀個鏈指針;
6.所有關鍵字都在葉⼦結點出現;
在B+Tree中,所有數據記錄節點都是按照鍵值⼤⼩順序存放在同⼀層的葉⼦節點上,⽽⾮葉⼦節點上只存儲key值信息,這樣可以⼤⼤加⼤每個節點存儲
的key值數量,降低B+Tree的⾼度。
B+Tree相對於B-Tree有⼏點不同:
1. ⾮葉⼦節點只存儲鍵值信息;
2. 所有葉⼦節點之間都有⼀個鏈指針;
3. 數據記錄都存放在葉⼦節點中
通常在B+Tree上有兩個頭指針,⼀個指向根節點,另⼀個指向關鍵字最⼩的葉⼦節點,⽽且所有葉⼦節點(即數據節點)之間是⼀種鏈式環結構。因此可
以對B+Tree進⾏兩種查找運算:⼀種是對於主鍵的范圍查找和分⻚查找,另⼀種是從根節點開始,進⾏隨機查找。
B TREE和B+TREE區別是什么?
1. B+Tree 關鍵字的搜索采⽤的是左閉合區間,之所以采⽤左閉合區間是因為他要最好的去⽀持⾃增id,這也是mysql的設計初衷。即,如果id = 1命中,會繼續往下查找,直到找到葉⼦節點中的1。
2. B+Tree 根節點和⽀節點沒有數據區,⾮葉⼦節點中只有關鍵字和指向下⼀個節點的索引,記錄只放在葉⼦節點中。即只有葉⼦節點中的關鍵字數據區才會保存真正的數據內容或者是內容的地址。⽽在B樹種,如果根節點命中,則會直接返回數據。B-樹的關鍵字和記錄是放在⼀起的,葉⼦節點可以看作外部節點,不包含任何信息
3. B+Tree葉⼦節點是順序排列的,並且相鄰的節點具有順序引⽤的關系,如上圖中葉⼦節點之間有指針相連接。
InnoDB存儲引擎中⻚的⼤⼩為16KB,⼀般表的主鍵類型為INT(占⽤4個字節)或BIGINT(占⽤8個字節),指針類型也⼀般為4或8個字節,也就是說⼀個⻚(B+Tree中的⼀個節點)中⼤概存儲16KB/(8B+8B)=1K個鍵值(因為是估值,為⽅便計算,這⾥的K取值為103)。也就是說⼀個深度為3的B+Tree索引可以維護103 * 10^3 * 10^3 = 10億 條記錄。
實際情況中每個節點可能不能填充滿,因此在數據庫中,B+Tree的⾼度⼀般都在2-4層。MySQL的InnoDB存儲引擎在設計時是將根節點常駐內存的,也就是說查找某⼀鍵值的⾏記錄時最多只需要1~3次磁盤I/O操作。
B+Tree性質
1.所有關鍵字都出現在葉⼦結點的鏈表中(稠密索引),且鏈表中的關鍵字恰好是有序的;
2.不可能在⾮葉⼦結點命中;
3.⾮葉⼦結點相當於是葉⼦結點的索引(稀疏索引),葉⼦結點相當於是存儲(關鍵字)數據的數據層;
4.更適合⽂件索引系統;
B*樹
是B+樹的變體,在B+樹的⾮根和⾮葉⼦結點再增加指向兄弟的指針;
MySQL為什么最終要去選擇B+Tree?
1. B+Tree是B TREE的變種,B TREE能解決的問題,B+TREE也能夠解決(降低樹的⾼度,增⼤節點存儲數據量)
2. B+Tree掃庫和掃表能⼒更強。如果我們要根據索引去進⾏數據表的掃描,對B TREE進⾏掃描,需要把整棵樹遍歷⼀遍,⽽B+TREE只需要遍歷他的所有葉⼦節點即可(葉⼦節點之間有引⽤)。
3. B+TREE磁盤讀寫能⼒更強。他的根節點和⽀節點不保存數據區,所以根節點和⽀節點同樣⼤⼩的情況下,保存的關鍵字要⽐B TREE要多。⽽葉⼦節點不保存⼦節點引⽤,能⽤於保存更多的關鍵字和數據。所以,B+TREE讀寫⼀次磁盤加載的關鍵字⽐B TREE更多。
4. B+Tree排序能⼒更強。上⾯的圖中可以看出,B+Tree天然具有排序功能。
5. B+Tree查詢性能穩定。B+Tree數據只保存在葉⼦節點,每次查詢數據,查詢IO次數⼀定是穩定的。當然這個每個⼈的理解都不同,因為在B TREE如果根節點命中直接返回,確實效率更⾼。
⼆叉樹的遍歷:分為⼴度優先和深度優先遍歷
⼴度優先:按照樹的層次,從第⼀次開始,⾃左⾄右遍歷 BFS
深度優先,樹的根結點為D,左⼦樹為L,右⼦樹為R,且要求L⼀定在R之前,分為前序、中序、后序遍歷 DFS
前序遍歷:根節點--左⼦樹--右⼦樹
中序遍歷:左⼦樹--根節點---右⼦樹
后序遍歷:左⼦樹---右⼦樹--根節點