B+樹原理以及Java代碼實現

最初查找二叉樹,因爲樹的高度會隨着有序序列輸入而急劇增加,後來出現平衡二叉樹,紅黑樹。B樹能夠海量數據的快速查詢檢索,B樹主要分爲B樹(B-樹),B+樹,B*樹等。數組

B樹(B-樹)數據結構

M路搜索樹,參數M定義節點的分支個數;app

對於根節點孩子數目爲[2,M],對於其他節點孩子數目爲[M/2,M];less

每一個節點含有關鍵字屬性,至少M/2-1  至少M-1;關鍵字個數=孩子個數-1;ide

節點有兩種類型:  葉子節點    處於同一層性能

非葉子結點   關鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];即關鍵字時有序的;孩子指針:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;學習

 

 

關鍵字集合分佈在整顆樹中;任何一個關鍵字出現且只出如今一個結點中;搜索有可能在非葉子結點結束;其搜索性能等價於在關鍵字全集內作一次二分查找;ui

B+樹this

對於B樹的改進,每一個節點具備關鍵字以及孩子指針屬性:spa

非葉子結點的子樹指針與關鍵字個數相同;

非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間);

爲全部葉子結點增長一個鏈指針;

全部關鍵字都在葉子結點出現;

 

 

全部關鍵字都出如今葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字剛好是有序的;不可能在非葉子結點命中;非葉子結點至關因而葉子結點的索引(稀疏索引),葉子結點至關因而存儲(關鍵字)數據的數據層;更適合文件索引系統;

B*樹

+樹的變體,在B+樹的非根和非葉子結點再增長指向兄弟的指針;

B*樹定義非葉子結點關鍵字個數至少爲(2/3)*M,即塊的最低使用率爲2/3(代替B+樹的1/2);

 

 

B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的數據複製到新結點,最後在父結點中增長新結點的指針;B+樹的分裂隻影響原結點和父結點,而不會影響兄弟結點,因此它不須要指向兄弟的指針;

 

B*樹的分裂:當一個結點滿時,若是它的下一個兄弟結點未滿,那麼將一部分數據移到兄弟結點中,再在原結點插入關鍵字,最後修改父結點中兄弟結點的關鍵字(由於兄弟結點的關鍵字範圍改變了);若是兄弟也滿了,則在原結點與兄弟結點之間增長新結點,並各複製1/3的數據到新結點,最後在父結點增長新結點的指針;B*樹分配新結點的機率比B+樹要低,空間使用率更高;

 

B+樹Java代碼實現

 

實現B+的構造,插入以及查詢方法

 

定義B+樹節點關鍵字以及孩子指針數據結構

 

定義B+樹參數M

 

對於每一個節點  孩子指針利用Entry[M]來表示,m表示關鍵字的個數;Entry表示具體的關鍵字信息  Key  Value以及Next指針

public class BTree<Key extends Comparable<Key>, Value> { //參數M 定義每一個節點的連接數
    private static final int M = 4; private Node root; //樹的高度 最底層爲0 表示外部節點 根具備最大高度
    private int height; //鍵值對總數
    private int n; //節點數據結構定義
    private static final class Node{ //值的數量
        private int m; private Entry[] children = new Entry[M]; private Node(int k){ m = k; } } //節點內部每一個數據項定義
    private static class Entry{ private Comparable key; private final Object val; //下一個節點
        private Node next; public Entry(Comparable key, Object val, Node next){ this.key = key; this.val = val; this.next = next; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Entry [key="); builder.append(key); builder.append("]"); return builder.toString(); } }

查詢方法

根據B+樹定義,非葉子結點存儲索引信息,葉子結點存儲數據信息,利用height來判斷當前是哪一層,若是height=0說明,當前節點爲葉子結點,對於葉子結點,則執行順序查找便可;若是當前節點爲非葉子節點,則查找key在當前節點的哪個孩子指針中,若是key大於當前節點全部關鍵字即j==m-1     若是key小於某個關鍵字j+1  則說明key位於j孩子指針中。

public Value get(Key key){ return search(root, key, height); } private Value search(Node x, Key key, int ht) { Entry[] children = x.children; //外部節點 這裏使用順序查找 //若是M很大 能夠改爲二分查找
        if(ht == 0){ for(int j=0; j<x.m; j++){ if(equal(key, x.children[j].key)) return (Value)children[j].val; } } //內部節點 尋找下一個節點
        else{ for(int j=0; j<x.m; j++){ //最後一個節點 或者 插入值小於某個孩子節點值
                if(j+1==x.m || less(key, x.children[j+1].key)) return search(x.children[j].next, key, ht-1); } } return null; }

插入方法

利用遞歸思想,insert實如今某個節點中插入數據,若是當前節點爲葉子結點,則找到待插入的位置j,將j之後數據後移一位,將當前數據插入,插入以後若是當前節點已滿,則將當前節點的後M/2個元素產生一個新節點返回;若是當前節點爲非葉子節點,找到待插入數據位於哪個孩子節點中,遞歸調用insert方法,若是返回值非空,則說明下一層插入時,出現節點分裂,將新節點指向父節點;

public void put(Key key, Value val){ //插入後的節點 若是節點分裂,則返回分裂後產生的新節點
        Node u = insert(root, key, val, height); n++; //根節點沒有分裂 直接返回
        if(u == null) return; //分裂根節點
        Node t = new Node(2); //舊根節點第一個孩子 新分裂節點
        t.children[0] = new Entry(root.children[0].key, null, root); t.children[1] = new Entry(u.children[0].key, null, u); root = t; height++; } private Node insert(Node h, Key key, Value val, int ht) { int j; //新建待插入數據數據項
        Entry t = new Entry(key, val, null); // 外部節點 找到帶插入的節點位置j
        if(ht == 0){ for(j=0; j<h.m; j++){ if(less(key, h.children[j].key)) break; } }else{ //內部節點 找到合適的分支節點
            for(j=0; j<h.m; j++){ if(j+1==h.m || less(key, h.children[j+1].key)){ Node u = insert(h.children[j++].next, key, val, ht-1); if(u == null) return null; t.key = u.children[0].key; t.next = u; break; } } } //System.out.println(j + t.toString()); //j爲帶插入位置,將順序數組j位置之後後移一位 將t插入
        for(int i=h.m; i>j; i++){ h.children[i] = h.children[i-1]; } System.out.println(j + t.toString()); h.children[j] = t; h.m++; if(h.m < M) return null; //若是節點已滿 則執行分類操做
        else return split(h); } private Node split(Node h) { //將已滿節點h的後通常M/2節點分裂到新節點並返回
        Node t = new Node(M/2); h.m = M / 2; for(int j=0; j<M/2; j++) t.children[j] = h.children[M/2+j]; return t; }

節點分裂過程

在下圖M=3中插入19,首先找到19的插入位置,插入;

 

 

插入以後,出現節點已滿

 

 

將已滿節點進行分裂,將已滿節點後M/2節點生成一個新節點,將新節點的第一個元素指向父節點;

 

 

父節點出現已滿,將父節點繼續分裂

 

 

一直分裂,若是根節點已滿,則須要分類根節點,此時樹的高度增長

 

 

對於根節點分裂,新建一個節點  將舊根節點第一個元素以及分類節點第一個元素組合做爲新的根節點。

public void put(Key key, Value val){ //插入後的節點 若是節點分裂,則返回分裂後產生的新節點
        Node u = insert(root, key, val, height); n++; //根節點沒有分裂 直接返回
        if(u == null) return; //分裂根節點
        Node t = new Node(2); //舊根節點第一個孩子 新分裂節點第一個孩子組成新節點做爲根
        t.children[0] = new Entry(root.children[0].key, null, root); t.children[1] = new Entry(u.children[0].key, null, u); root = t; height++; }

性能:

含有N個元素的M階B+樹,一次查找或者插入的時間複雜度爲log(M)N~log(M/2)N,當M較大時,該值基本爲常數;

https://mp.weixin.qq.com/s/vkvYJnKfQyuUeD_BDQy_1g

獲取更多學習資料,能夠加羣:473984645或掃描下方二維碼

相關文章
相關標籤/搜索