1. 程式人生 > >水人的專欄

水人的專欄

為什麼說B+-tree比B 樹更適合實際應用中作業系統的檔案索引和資料庫索引?

1) B+-tree的磁碟讀寫代價更低 
B+-tree的內部結點並沒有指向關鍵字具體資訊的指標。因此其內部結點相對B 樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入記憶體中的需要查詢的關鍵字也就越多。相對來說IO讀寫次數也就降低了。 
舉個例子,假設磁碟中的一個盤塊容納16bytes,而一個關鍵字2bytes,一個關鍵字具體資訊指標2bytes。一棵9階B-tree(一個結點最多8個關鍵字)的內部結點需要2個盤快。而B+ 樹內部結點只需要1個盤快。當需要把內部結點讀入記憶體中的時候,B 樹就比B+ 樹多一次盤塊查詢時間(在磁碟中就是碟片旋轉的時間)。
看節點定義可以看出來。
2) B+-tree的查詢效率更加穩定
由於非終結點並不是最終指向檔案內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查詢必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
3)B+樹還有一個最大的好處,方便掃庫,B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支援range-query非常方便,而B樹不支援。這是資料庫選用B+樹的最主要原因。

“有很多基於頻率的搜尋是選用B樹,越頻繁query的結點越往根上走,前提是需要對query做統計,而且要對key做一些變化。”

B*-tree:
B*-tree是B+-tree的變體,在B+ 樹非根和非葉子結點再增加指向兄弟的指標;B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3(代替B+樹的1/2)。


B-tree:
定義:m階,子女節點個數從ceil(m/2)到m/2,關鍵字個數從ceil(m/2)-1到m-1,比子女節點個數剛好少1.
/**
 *  插入關鍵字虛擬碼:
	1、找到應該插入位置的節點,一定是葉子節點,直接插入;
	2、如果該葉子節點關鍵字個數大於m-1;分裂該葉子節點;

	分裂節點虛擬碼:
	1、分裂該節點,產生一個新節點;
	2、將中間關鍵字插入父節點中;
	3、如果父節點關鍵字個數大於m-1,遞迴分裂父節點,否則直接返回;

 *  如果name已經存在,返回false
 **/
/**
 *  刪除關鍵字虛擬碼----最終是在葉子節點上刪除資料;
    1、查詢包含這個關鍵字key的節點node:
	2、如果這個節點是內節點:
		a 找到左子樹中含有最大關鍵字的節點leafnode,及其最大的關鍵字keyx;
	    b 在葉子節點leafnode上刪除關鍵字keyx,並用keyx代替原來將要刪除的關鍵字key
		c 維護leafnode
	   如果這個節點是葉子節點:
	    a 直接刪除這個關鍵字,移動後面的其他關鍵字
		b 維護這個節點

	維護節點虛擬碼:
		a 如果關鍵字滿足要求,直接返回;
		b 如果左右兄弟節點有足夠多的關鍵字,向其借一個,返回;
		c 如果左右兄弟節點都沒有足夠的關鍵字,合併一個兄弟節點,回溯維護父節點。
 *
 *  如果name已經存在,返回false
 **/

B+tree:
定義:內部節點全部是索引關鍵字,data都在葉子節點。
內部節點:子女節點個數為ceil[m/2]到m,關鍵字可以和子女節點對應也可以少1(不同的書有不同的說法).關鍵字i是子樹i+1的最小關鍵字。
葉子節點:全部是key-value值,個數ceil(L/2)-->L( L << M in practice)。
根節點:單個或是2-->M個子節點。

實際中上:
每個節點通常佔用一個I/O塊;
1/2級節點常駐記憶體;
大多數情況很多內部節點的關鍵字個數少於(m-1),對記憶體的極大浪費。

各大IT公司面試常常問到的問題——海量資料問題。以前通常回答二級索引,即一級索引常駐記憶體,通過一級索引找到二級索引,讀入記憶體,再通過二級索引找到最終要找的具體資料,而“索引”,一直設想的都是HASH,現在回頭想來,HASH其實是不合適的。因為HASH只能提供對映,而不能提供範圍資訊。這個問題的正確答案應該是B樹或者B+樹。 

Insert()
/**
    1、找到葉子節點,直接插入;
    2、>L, 則SplitLeafNode 或者是 SplitRootNode()

    SplitLeafNode()
    1 Node ==> LeftNode( ceil(L/2) ) + Right( remaining );
    2 向父節點插入rightNode和right節點中最小的關鍵字 InsertInternalNode(key, rightNode)
    
    InsertInternalNode()
    1 直接插入關鍵字及其右子樹;
    2 子女節點個數>M,則 SplitInternalNode()

    SplitInternalNode()
    1 Node ==> Left( ceil(M/2)-1 ) + Key(中間) + Right( M/2 )
    2 InsertInternalNode( Key, Right )
 **/
Delete()
/**
    感覺挺麻煩的,細節還是不太清楚
    1、找到葉子節點,刪除,如果有內部節點包含這個關鍵字,則用葉子節點的最小值替換
    2、關鍵字個數小於<L:Rebalance();

    Rebalance()
    1、如果LeftSibling關鍵字個數>=ceil(L/2)+1,LendLeftSibling;
    2、LendRightSibling
    3、MergeLeafNode() or MergeInternalNode()
    
    MergeLeafNode: 注意parent的key只需要刪除就可以了
    MergeInternalNode:注意parent的key要往下移動
 **/

參考:http://blog.csdn.net/v_JULY_v/article/details/6530142
http://en.wikipedia.org/wiki/B%2Btree
一份B+樹ppt,很好,http://www.albertsong.com/read-174.html