一文詳解:什麼是B樹?

七月線上實驗室發表於2019-03-19

640?wx_fmt=png

長按識別上方二維碼,關注:七月線上實驗室,回覆關鍵詞:100

即可免費領取【100道機器學習面試題(上)】PDF版本一份



1.前言: 

動態查詢樹主要有:二叉查詢樹(Binary Search Tree),平衡二叉查詢樹(Balanced Binary Search Tree),紅黑樹(Red-Black Tree ),B-tree/B+-tree/ B*-tree (B~Tree)。前三者是典型的二叉查詢樹結構,其查詢的時間複雜度O(log2N)與樹的深度相關,那麼降低樹的深度自然會提高查詢效率。 


但是我們們有面對這樣一個實際問題:就是大規模資料儲存中,實現索引查詢這樣一個實際背景下,樹節點儲存的元素數量是有限的(如果元素數量非常多的話,查詢就退化成節點內部的線性查詢了),這樣導致二叉查詢樹結構由於樹的深度過大而造成磁碟I/O讀寫過於頻繁,進而導致查詢效率低下(為什麼會出現這種情況,待會在外部儲存器-磁碟中有所解釋),那麼如何減少樹的深度(當然是不能減少查詢的資料量),一個基本的想法就是:採用多叉樹結構(由於樹節點元素數量是有限的,自然該節點的子樹數量也就是有限的)。 


也就是說,因為磁碟的操作費時費資源,如果過於頻繁的多次查詢勢必效率低下。那麼如何提高效率,即如何避免磁碟過於頻繁的多次查詢呢?根據磁碟查詢存取的次數往往由樹的高度所決定,所以,只要我們通過某種較好的樹結構減少樹的結構儘量減少樹的高度,那麼是不是便能有效減少磁碟查詢存取的次數呢?那這種有效的樹結構是一種怎樣的樹呢? 


這樣我們就提出了一個新的查詢樹結構——多路查詢樹。根據平衡二叉樹的啟發,自然就想到平衡多路查詢樹結構,也就是這篇文章所要闡述的第一個主題B~tree,即B樹結構(後面,我們將看到,B樹的各種操作能使B樹保持較低的高度,從而達到有效避免磁碟過於頻繁的查詢存取操作,從而有效提高查詢效率)。


 B-tree(B-tree樹即B樹,B即Balanced,平衡的意思)這棵神奇的樹是在Rudolf Bayer, Edward M. McCreight(1970)寫的一篇論文《Organization and Maintenance of Large Ordered Indices》中首次提出的(wikipedia中:http://en.wikipedia.org/wiki/B-tree,闡述了B-tree名字來源以及相關的開源地址)。 


在開始介紹B~tree之前,先了解下相關的硬體知識,才能很好的瞭解為什麼需要B~tree這種外存資料結構。 


2.外儲存器—磁碟 

計算機儲存裝置一般分為兩種:記憶體儲器(main memory)和外儲存器(external memory)。 記憶體存取速度快,但容量小,價格昂貴,而且不能長期儲存資料(在不通電情況下資料會消失)。 


外儲存器—磁碟是一種直接存取的儲存裝置(DASD)。它是以存取時間變化不大為特徵的。可以直接存取任何字元組,且容量大、速度較其它外存裝置更快。 


2.1 磁碟的構造 

磁碟是一個扁平的圓盤(與電唱機的唱片類似)。盤面上有許多稱為磁軌的圓圈,資料就記錄在這些磁軌上。磁碟可以是單片的,也可以是由若干碟片組成的盤組,每一碟片上有兩個面。如下圖11.3中所示的6片盤組為例,除去最頂端和最底端的外側面不儲存資料之外,一共有10個面可以用來儲存資訊。

640?wx_fmt=gif640?wx_fmt=jpeg


當磁碟驅動器執行讀/寫功能時。碟片裝在一個主軸上,並繞主軸高速旋轉,當磁軌在讀/寫頭(又叫磁頭) 下通過時,就可以進行資料的讀 / 寫了。 


一般磁碟分為固定頭盤(磁頭固定)和活動頭盤。固定頭盤的每一個磁軌上都有獨立的磁頭,它是固定不動的,專門負責這一磁軌上資料的讀/寫。 


活動頭盤 (如上圖)的磁頭是可移動的。每一個盤面上只有一個磁頭(磁頭是雙向的,因此正反盤面都能讀寫)。它可以從該面的一個磁軌移動到另一個磁軌。所有磁頭都裝在同一個動臂上,因此不同盤面上的所有磁頭都是同時移動的(行動整齊劃一)。當碟片繞主軸旋轉的時候,磁頭與旋轉的碟片形成一個圓柱體。各個盤面上半徑相同的磁軌組成了一個圓柱面,我們稱為柱面 。因此,柱面的個數也就是盤面上的磁軌數。  


2.2磁碟的讀/寫原理和效率 

磁碟上資料必須用一個三維地址唯一標示:柱面號、盤面號、塊號(磁軌上的盤塊)。 

讀/寫磁碟上某一指定資料需要下面3個步驟: 

(1)  首先移動臂根據柱面號使磁頭移動到所需要的柱面上,這一過程被稱為定位或查詢 。 

(2)  如上圖11.3中所示的6盤組示意圖中,所有磁頭都定位到了10個盤面的10條磁軌上(磁頭都是雙向的)。這時根據盤面號來確定指定盤面上的磁軌。 

(3) 盤面確定以後,碟片開始旋轉,將指定塊號的磁軌段移動至磁頭下。 


經過上面三個步驟,指定資料的儲存位置就被找到。這時就可以開始讀/寫操作了。 


訪問某一具體資訊,由3部分時間組成: 

● 查詢時間(seek time) Ts: 完成上述步驟(1)所需要的時間。這部分時間代價最高,最大可達到0.1s左右。 


● 等待時間(latency time) Tl: 完成上述步驟(3)所需要的時間。由於碟片繞主軸旋轉速度很快,一般為7200轉/分(電腦硬碟的效能指標之一, 家用的普通硬碟的轉速一般有5400rpm(筆記本)、7200rpm幾種)。因此一般旋轉一圈大約0.0083s。

  

● 傳輸時間(transmission time) Tt: 資料通過系統匯流排傳送到記憶體的時間,一般傳輸一個位元組(byte)大概0.02us=2*10^(-8)s  


磁碟讀取資料是以盤塊(block)為基本單位的。位於同一盤塊中的所有資料都能被一次性全部讀取出來。而磁碟IO代價主要花費在查詢時間Ts上。因此我們應該儘量將相關資訊存放在同一盤塊,同一磁軌中。或者至少放在同一柱面或相鄰柱面上,以求在讀/寫資訊時儘量減少磁頭來回移動的次數,避免過多的查詢時間Ts。  


所以,在大規模資料儲存方面,大量資料儲存在外存磁碟中,而在外存磁碟中讀取/寫入塊(block)中某資料時,首先需要定位到磁碟中的某塊,如何有效地查詢磁碟中的資料,需要一種合理高效的外存資料結構,就是下面所要重點闡述的B-tree結構,以及相關的變種結構:B+-tree結構和B*-tree結構。 


3.B樹  

3.1 什麼是B樹 

具體講解之前,有一點,再次強調下:有的文章裡面出現的B-樹,即為B樹。因為B樹的原英文名稱為B-tree,而國內很多人喜歡把B-tree譯作B-樹,其實,這是個非常不好的直譯,很容易讓人產生誤解。如人們可能會以為B-樹是一種樹,而B樹又是一種一種樹。而事實上是,B-tree就是指的B樹。特此說明。 


我們知道,B樹是為了磁碟或其它儲存裝置而設計的一種多叉(下面你會看到,相對於二叉,B樹每個內結點有多個分支,即多叉)平衡查詢樹。與本blog之前介紹的紅黑樹很相似,但在降低磁碟I/0操作方面要更好一些。許多資料庫系統都一般使用B樹或者B樹的各種變形結構,如下文即將要介紹的B+樹,B*樹來儲存資訊。 


B樹與紅黑樹最大的不同在於,B樹的結點可以有許多子女,從幾個到幾千個。那為什麼又說B樹與紅黑樹很相似呢?因為與紅黑樹一樣,一棵含n個結點的B樹的高度也為O(lgn),但可能比一棵紅黑樹的高度小許多,應為它的分支因子比較大。所以,B樹可以在O(logn)時間內,實現各種如插入(insert),刪除(delete)等動態集合操作。  


如下圖所示,即是一棵B樹,一棵關鍵字為英語中子音字母的B樹,現在要從樹種查詢字母R(包含n[x]個關鍵字的內結點x,x有n[x]+1]個子女(也就是說,一個內結點x若含有n[x]個關鍵字,那麼x將含有n[x]+1個子女)。所有的葉結點都處於相同的深度,帶陰影的結點為查詢字母R時要檢查的結點):

640?wx_fmt=gif640?wx_fmt=jpeg


相信,從上圖你能輕易的看到,一個內結點x若含有n[x]個關鍵字,那麼x將含有n[x]+1個子女。如含有2個關鍵字D H的內結點有3個子女,而含有3個關鍵字Q T X的內結點有4個子女。


B樹的定義,從下文中,你將看到,或者是用階,或者是用度,如下段文字所述:     

  Unfortunately, the literature on B-trees is not uniform in its use of terms relating to B-Trees. (Folk & Zoellick 1992, p. 362) Bayer & McCreight (1972), Comer (1979), and others define the order of B-tree as the minimum number of keys in a non-root node. Folk & Zoellick (1992) points out that terminology is ambiguous because the maximum number of keys is not clear. An order 3 B-tree might hold a maximum of 6 keys or a maximum of 7 keys. (Knuth 1998,TAOCP p. 483) avoids the problem by defining the order to be maximum number of children (which is one more than the maximum number of keys).

640?wx_fmt=jpeg


用階定義的B樹 

B 樹又叫平衡多路查詢樹。一棵m階的B 樹 (注:切勿簡單的認為一棵m階的B樹是m叉樹,雖然存在四叉樹,八叉樹,KD樹,及vp/R樹/R*樹/R+樹/X樹/M樹/線段樹/希爾伯特R樹/優先R樹等空間劃分樹,但與B樹完全不等同)的特性如下:  

①樹中每個結點最多含有m個孩子(m>=2); 

②除根結點和葉子結點外,其它每個結點至少有[ceil(m / 2)]個孩子(其中ceil(x)是一個取上限的函式); 

③若根結點不是葉子結點,則至少有2個孩子(特殊情況:沒有孩子的根結點,即根結點為葉子結點,整棵樹只有一個根節點); 

④所有葉子結點都出現在同一層,葉子結點不包含任何關鍵字資訊(可以看做是外部接點或查詢失敗的接點,實際上這些結點不存在,指向這些結點的指標都為null);(讀者反饋@冷嶽:這裡有錯,葉子節點只是沒有孩子和指向孩子的指標,這些節點也存在,也有元素。@研究者July:其實,關鍵是把什麼當做葉子結點,因為如紅黑樹中,每一個NULL指標即當做葉子結點,只是沒畫出來而已)。 

⑤每個非終端結點中包含有n個關鍵字資訊: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:  

a)Ki (i=1...n)為關鍵字,且關鍵字按順序升序排序K(i-1)< Ki。 b)Pi為指向子樹根的接點,且指標P(i-1)指向子樹種所有結點的關鍵字均小於Ki,但都大於K(i-1)。   

c)關鍵字的個數n必須滿足: [ceil(m / 2)-1]<= n <= m-1。如下圖所示:

640?wx_fmt=gif640?wx_fmt=jpeg


用度定義的B樹 

針對上面的5點,再闡述下:

B樹中每一個結點能包含的關鍵字(如之前上面的D H和Q T X)數有一個上界和下界。這個下界可以用一個稱作B樹的最小度數(演算法導論中文版上譯作度數,最小度數即內節點中節點最小孩子數目)m(m>=2)表示。 


i)每個非根的內結點至多有m個子女,每個非根的結點必須至少含有m-1個關鍵字,如果樹是非空的,則根結點至少包含一個關鍵字; 

ii)每個結點可包含至多2m-1個關鍵字。所以一個內結點至多可有2m個子女。如果一個結點恰好有2m-1個關鍵字,我們就說這個結點是滿的(而稍後介紹的B*樹作為B樹的一種常用變形,B*樹中要求每個內結點至少為2/3滿,而不是像這裡的B樹所要求的至少半滿); 

iii)當關鍵字數m=2(t=2的意思是,mmin=2,m可以>=2)時的B樹是最簡單的(有很多人會因此誤認為B樹就是二叉查詢樹,但二叉查詢樹就是二叉查詢樹,B樹就是B樹,B樹是一棵含有m(m>=2)個關鍵字的平衡多路查詢樹),此時,每個內結點可能因此而含有2個、3個或4個子女,亦即一棵2-3-4樹,然而在實際中,通常採用大得多的t值。 


B樹中的每個結點根據實際情況可以包含大量的關鍵字資訊和分支(當然是不能超過磁碟塊的大小,根據磁碟驅動(disk drives)的不同,一般塊的大小在1k~4k左右);這樣樹的深度降低了,這就意味著查詢一個元素只要很少結點從外存磁碟中讀入記憶體,很快訪問到要查詢的資料。如果你看完上面關於B樹定義的介紹,思維感覺不夠清晰,請繼續參閱下文第6小節、B樹的插入、刪除操作 部分。 


3.2 B樹的型別和節點定義 

B樹的型別和節點定義如下圖所示:

640?wx_fmt=gif640?wx_fmt=jpeg

640?wx_fmt=gif640?wx_fmt=jpeg


3.3 檔案查詢的具體過程(涉及磁碟IO操作) 

為了簡單,這裡用少量資料構造一棵3叉樹的形式,實際應用中的B樹結點中關鍵字很多的。上面的圖中比如根結點,其中17表示一個磁碟檔案的檔名;小紅方塊表示這個17檔案內容在硬碟中的儲存位置;p1表示指向17左子樹的指標。  


其結構可以簡單定義為: 

typedef struct {     

  /*檔案數*/     

  int  file_num;   

  

  /*檔名(key)*/     

  char * file_name[max_file_num];     


  /*指向子節點的指標*/      

  BTNode * BTptr[max_file_num+1];     


  /*檔案在硬碟中的儲存位置*/      

  FILE_HARD_ADDR offset[max_file_num]; 

}BTNode; 


假如每個盤塊可以正好存放一個B樹的結點(正好存放2個檔名)。那麼一個BTNODE結點就代表一個盤塊,而子樹指標就是存放另外一個盤塊的地址。 


下面,我們們來模擬下查詢檔案29的過程:  

1 根據根結點指標找到檔案目錄的根磁碟塊1,將其中的資訊匯入記憶體。【磁碟IO操作 1次】      

2 此時記憶體中有兩個檔名17、35和三個儲存其他磁碟頁面地址的資料。根據演算法我們發現:17<29<35,因此我們找到指標p2。  

3 根據p2指標,我們定位到磁碟塊3,並將其中的資訊匯入記憶體。【磁碟IO操作 2次】     

此時記憶體中有兩個檔名26,30和三個儲存其他磁碟頁面地址的資料。根據演算法我們發現:26<29<30,因此我們找到指標p2。  

4 根據p2指標,我們定位到磁碟塊8,並將其中的資訊匯入記憶體。【磁碟IO操作 3次】     

此時記憶體中有兩個檔名28,29。根據演算法我們查詢到檔名29,並定位了該檔案記憶體的磁碟地址。


分析上面的過程,發現需要3次磁碟IO操作和3次記憶體查詢操作。關於記憶體中的檔名查詢,由於是一個有序表結構,可以利用折半查詢提高效率。至於IO操作是影響整個B樹查詢效率的決定因素。 


當然,如果我們使用平衡二叉樹的磁碟儲存結構來進行查詢,磁碟4次,最多5次,而且檔案越多,B樹比平衡二叉樹所用的磁碟IO操作次數將越少,效率也越高。  


3.4 B樹的高度     

根據上面的例子我們可以看出,對於輔存做IO讀的次數取決於B樹的高度。而B樹的高度由什麼決定的呢?     


若B樹某一非葉子節點包含N個關鍵字,則此非葉子節點含有N+1個孩子結點,而所有的葉子結點都在第I層,我們可以得出: 

因為根至少有兩個孩子,因此第2層至少有兩個結點。    

除根和葉子外,其它結點至少有┌m/2┐個孩子,    

因此在第3層至少有2*┌m/2┐個結點,    

在第4層至少有2*(┌m/2┐^2)個結點,    

在第 I 層至少有2*(┌m/2┐^(l-2) )個結點,於是有: N+1 ≥ 2*┌m/2┐I-2;    

考慮第L層的結點個數為N+1,那麼2*(┌m/2┐^(l-2))≤N+1,也就是L層的最少結點數剛好達到N+1個,即: I≤ log┌m/2┐((N+1)/2 )+2;    

所以,當B樹包含N個關鍵字時,B樹的最大高度為l-1(因為計算B樹高度時,葉結點所在層不計算在內),即:l - 1 = log┌m/2┐((N+1)/2 )+1。   


這個B樹的高度公式從側面顯示了B樹的查詢效率是相當高的。 


曾在一次面試中被問到,一棵含有N個總關鍵字數的m階的B樹的最大高度是多少?答曰:log_ceil(m/2)(N+1)/2 + 1 (上面中關於m階B樹的第1點特性已經提到:樹中每個結點含有最多含有m個孩子,即m滿足:ceil(m/2)<=m<=m。而樹中每個結點含孩子數越少,樹的高度則越大,故如此)。在2012微軟4月份的筆試中也問到了此問題。 


此外,還有讀者反饋,說上面的B樹的高度計算公式與演算法導論一書上的不同,而後我特意翻看了演算法導論第18章關於B樹的高度一節的內容,如下圖所示:

640?wx_fmt=gif640?wx_fmt=jpeg


在上圖中書上所舉的例子中,也許,根據我們大多數人的理解,它的高度應該是4,而書上卻說的是“一棵高度為3的B樹”。 


我想,此時,你也就明白了,演算法導論一書上的高度的定義是從“0”開始計數的,而我們中國人的習慣是樹的高度是從“1”開始計數的。


本題解析節選自CSDN上的一篇閱讀量超過50萬的部落格《從B 樹、B+ 樹、B* 樹談到R 樹》 作者:July、weedge、Frankie。程式設計藝術室出品。



640?wx_fmt=gif

今日學習推薦


資料結構班


搞定演算法   直通BAT


一課掌握開發內功,超6K人報名

640?wx_fmt=jpeg


長按識別下方二維碼

檢視更多課程詳情

?

640?wx_fmt=png

長按識別二維碼


640?wx_fmt=gif


640?wx_fmt=gif

備戰AI求職季 | 100道機器學習面試題(上)

機器學習中的數學基礎(微積分和概率統計)

一文詳解機器學習中最好用的提升方法:Boosting 與 AdaBoost

34個最優秀好用的Python開源框架

【實戰分享】電影推薦系統專案實戰應用

萬字長文概述NLP中的深度學習技術


諮詢,檢視課程,請點選“閱讀原文

「 在看的,麻煩點一下再走~ 」


相關文章