算法导论之B树
開宗明義,B樹是為磁盤或其他直接存取輔助設(shè)備而設(shè)計(jì)的一種平衡查找樹。一般設(shè)計(jì)的簡單數(shù)據(jù)結(jié)構(gòu)都是面向主存而設(shè)計(jì)的,主存讀取速度快但容量小;而磁盤讀取速度慢而容量大,于是針對磁盤而設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)就不同于為主存而設(shè)計(jì)的。就樹結(jié)構(gòu)上來說,紅黑樹的二叉性質(zhì)和高深度適合主存,而B樹正是應(yīng)磁盤特點(diǎn)而設(shè)計(jì)的高級樹結(jié)構(gòu),其高度比紅黑樹小很多,但廣度要大很多,其分支不只2個,分支因子越大,高度越小。
在考察算法性能時,需要考慮兩方面:磁盤存取次數(shù)和CPU運(yùn)行時間。主存存儲量小,一旦要處理的數(shù)據(jù)量比較龐大,就需要以頁在磁盤和主存之間交換,選擇頁復(fù)制到主存中操作再將修改過的頁寫回磁盤,這來回就是磁盤存取消耗的性能,因此交換頁在操作系統(tǒng)磁盤模塊是一個很重要的算法。
B樹設(shè)計(jì)上,一個結(jié)點(diǎn)的大小相當(dāng)于一個完整的磁盤頁,其子女?dāng)?shù)就由磁盤頁的大小來決定。這樣一個節(jié)點(diǎn)的讀取就交換一個頁,減少磁盤IO次數(shù)。下面定義B樹:
一棵B樹T是具有如下性質(zhì)的有根樹(根為root[T]):
1)每個結(jié)點(diǎn)x有以下域構(gòu)成:
?? a)n[x],當(dāng)前存儲在結(jié)點(diǎn)x中的關(guān)鍵字?jǐn)?shù);
?? b)n[x]個關(guān)鍵字本身,以非降序存放,key1[x]=<key2[x]=<…=<keyn[x][x]
?? c)leaf[x],布爾值,如果x是葉子該值為true,如果x是內(nèi)結(jié)點(diǎn)則為false;
2)每個內(nèi)結(jié)點(diǎn)x還包含n[x]+1個指向其子女的指針c1[x],c1[x],…,cn[x]+1[x],葉結(jié)點(diǎn)沒有子女,故而ci域沒有定義;
3)各關(guān)鍵字keyi[x]對儲存在各子樹中的關(guān)鍵字范圍加以分割,如果ki為存儲在以ci[x]為根的子樹中的關(guān)鍵字,那么:
k1=<key1[x]=< k2=< key2[x]=<…=< kn[x]=<keyn[x]+1[x]
子節(jié)點(diǎn)的值是被父結(jié)點(diǎn)的值區(qū)隔開。
4)每個葉結(jié)點(diǎn)具有相同深度,即樹的高度h;
5)每一個節(jié)點(diǎn)能包含的關(guān)鍵字?jǐn)?shù)有一個上界和下界,界可用一個B樹的最小度數(shù)的固定整數(shù)t>=2來表示;
??? a)每個非根的結(jié)點(diǎn)必須至少有t-1個關(guān)鍵字,每個非根的內(nèi)結(jié)點(diǎn)至少有t個子女,如果樹是非空的,則根節(jié)點(diǎn)至少包含一個關(guān)鍵字;
??? b)每個節(jié)點(diǎn)可包含至多2t-1個關(guān)鍵字,故一個內(nèi)結(jié)點(diǎn)至多可有2t個子女,如果一個結(jié)點(diǎn)是滿的,那就有2t-1個關(guān)鍵字。
如果t=2,就是每個非根結(jié)點(diǎn)的關(guān)鍵字?jǐn)?shù)為2,其每個內(nèi)結(jié)點(diǎn)有2個、3個或4個子女,是一顆2-3-4樹。t其實(shí)就是限制了每個結(jié)點(diǎn)的關(guān)鍵字?jǐn)?shù),在t-1和2t-1范圍內(nèi),取閉區(qū)間。
B樹設(shè)計(jì)上一般考慮一個結(jié)點(diǎn)大小和一個磁盤頁相當(dāng),因此磁盤存取次數(shù)和B樹高度成正比。通過B樹最壞情況的高度來衡量性能。如果n>=1,對任意一顆包含n個關(guān)鍵字、高度為h、最小度數(shù)t>=2的B樹T,則:
定義了B樹和分析其性能,下面對B樹的基本操作進(jìn)行描述。
B樹的基本操作和二叉樹類似,不同的是每個結(jié)點(diǎn)不只是二叉決定,而是根據(jù)B樹的度(結(jié)點(diǎn)關(guān)鍵字?jǐn)?shù))做決定。B樹的每個內(nèi)結(jié)點(diǎn)x,都需要做n[x]+1路的分支決定。
1)B樹的搜索操作
從根結(jié)點(diǎn)出發(fā)搜索關(guān)鍵字k,在算法上只需依次從頂層定位關(guān)鍵字值序的空間就可以。關(guān)注下,B樹搜索操作的復(fù)雜度:搜索是從樹根一直下降的過程,需存取的磁盤頁面數(shù)為樹的高度就是:O(h)=O(logtn),t是節(jié)點(diǎn)的度,n是關(guān)鍵字?jǐn)?shù),h是樹高度;對于CPU運(yùn)行時間消耗來說,每個節(jié)點(diǎn)的循環(huán)式為O(t),總共進(jìn)行h次循環(huán),那總的時間就是O(th)=O(tlogtn)。
2)B樹的插入操作
B樹插入操作比較復(fù)雜,必須對滿結(jié)點(diǎn)進(jìn)行分裂操作,以維持B樹結(jié)點(diǎn)至多2t-1個關(guān)鍵字的性質(zhì)。從根節(jié)點(diǎn)出發(fā),尋找待插入關(guān)鍵字的序值位置,如果結(jié)點(diǎn)滿,則分裂,將中間值插入到父節(jié)點(diǎn),如果父節(jié)點(diǎn)也因此滿,需要進(jìn)一步分裂,遞歸如是。文中有一個思路很好,就是在尋找關(guān)鍵字要插入的結(jié)點(diǎn)過程中,在查找沿途過程中發(fā)現(xiàn)有滿節(jié)點(diǎn)(包含葉結(jié)點(diǎn)本身)就分類,而不是等最后插入時發(fā)現(xiàn)了才分裂。看看滿節(jié)點(diǎn)分裂算法的描述:
Fun_Btree_split_child(x,i,y){//x是父結(jié)點(diǎn),y是子節(jié)點(diǎn),i是x的分裂點(diǎn)
??? Allocat_node(z);//分配一個z結(jié)點(diǎn)
??? Leaf[z]=leaf[y];//把y中t個最大關(guān)鍵字(包含其t+1個子女)復(fù)制給z
??? n[z]=t-1;
??? for j=1 to t-1
??????? do keyj[z]=keyj+t[y];
??? if not leaf[y] //y有子女
??????? then for j=1 to t
??????????? do cj[z]=cj+t[y]
??? n[y]=t-1;
//到此已經(jīng)將y節(jié)點(diǎn),從中間分類成兩個節(jié)點(diǎn),y和z,其中z是較大關(guān)鍵字的那部分
//下面就是將y的中間關(guān)鍵字提升到父結(jié)點(diǎn)x中,左右分別指向y和z
for j=n[x]+1downto i+1
??????? do cj+1[x]=cj[x];//x的子女向右移動一個位置
??? ci+1[x]=z;
??? for j=n[x] downto i
??????? do keyj+1[x]=keyj[x];//x的關(guān)鍵字向右移動一個位置
??? keyi[x]=keyt[y]
??? n[x]=n[x+1]
??? Disk-Write(y);
??? Disk-Write(z);
Disk-Write(x);//回寫磁盤,一個結(jié)點(diǎn)一頁
}
有了分裂函數(shù),插入算法就不描述了,在遇到滿結(jié)點(diǎn)時調(diào)用分裂函數(shù)即可。對高度為h的B樹,插入的磁盤存取次數(shù)是O(h),cpu運(yùn)行時間是O(th)=O(tlogtn)。
3)B數(shù)的刪除操作
B樹刪除操作和插入一樣麻煩,需要確保刪除后結(jié)點(diǎn)數(shù)不小于t-1個關(guān)鍵字的性質(zhì)。刪除的算法思路就是合并節(jié)點(diǎn),復(fù)雜度和插入一樣。這里不具體描述。
B樹的應(yīng)用是很廣泛的,對于算法復(fù)雜度來說,關(guān)注CPU時間是不夠的,因?yàn)槎鄶?shù)情況,需要整體考慮設(shè)備性能,而磁盤IO能力是一個很關(guān)鍵但常常是制約性能的指標(biāo)。 《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
- 上一篇: 二叉查找树Java实现代码
- 下一篇: (转载)四种常见的 POST 提交数据方