爲了cmu數據庫的Lab2做準備html
→ B-Tree (1971)node
→ B+Tree (1973)ios
→ B*Tree (1977?)c++
→ B link-Tree (1981)git
正常來說b+樹的全部元素都須要在葉子結點出現。
github
對於葉子結點的存儲有兩種形式算法
一種是存指針。一種存數據數據庫
若爲空樹,建立一個葉子結點,而後將記錄插入其中,此時這個葉子結點也是根結點,插入操做結束。數組
針對葉子類型結點:根據key值找到葉子結點,向這個葉子結點插入記錄。插入後,若當前結點key的個數小於等於m-1,則插入結束。不然將這個葉子結點分裂成左右兩個葉子結點,左葉子結點包含前m/2個記錄,右結點包含剩下的記錄,將第m/2+1個記錄的key進位到父結點中(父結點必定是索引類型結點),進位到父結點的key左孩子指針向左結點,右孩子指針向右結點。將當前結點的指針指向父結點,而後執行第3步。數據結構
針對索引類型結點(內部結點):若當前結點key的個數小於等於m-1,則插入結束。不然,將這個索引類型結點分裂成兩個索引結點,左索引結點包含前\(\frac{(m-1)}{2}\)個key,右結點包含\(m- \frac{(m-1)}{2}\)個key,將第\(\frac{m}{2}\)個key進位到父結點中,進位到父結點的key左孩子指向左結點, 進位到父結點的key右孩子指向右結點。將當前結點的指針指向父結點,而後重複這一步。
cmu這裏給了演示網站 https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
假設咱們要插入5, 8,10,15 ,16 , 20 ,19 。以m=3爲例子
插入10
因爲此時根節點有3個結點>2(m-1)所以會分裂。並且這個時候是對葉子類型結點的處理。把前m/2=1個結點分給左葉子。右葉子包含剩下的結點。中間的 m/2+1第二個結點成爲父節點。
插入15。15會插到根節點的右邊。而後就會出現和上面同樣的狀況。所以咱們繼續分裂
插入16
插入20
20 會放到16的右邊。而後這個結點須要分裂。15成爲左孩子,16 ,20 成爲右孩子,16提爲父結點就ok啦
插入19
好了關於b+樹的插入模擬咱們就到這裏了。下面來寫一下代碼
一些在b+
樹插入時代碼的時候思考的問題
split的時候須要找父結點怎麼解決
一種是維護一個parent指針
查找插入結點
維護關鍵字有序如何作
由於用的數組存的關鍵字。因此就按照數組插入o(n)的複雜度
*key
表示關鍵字**ptr
表示結點IS_LEAF
來表示是否爲頁子結點。#include <iostream> #include <queue> using namespace std; int MAX = 2; // BP node class Node { bool IS_LEAF; int *key, size; Node** ptr; Node* parent; friend class BPTree; public: Node():key(new int[MAX+1]),ptr(new Node* [MAX+1]),parent(NULL){} ~Node(); }; // BP tree class BPTree { Node* root; void insertInternal(int,Node*,Node*,Node*); void split(int ,Node *,Node *); int insertVal(int ,Node *); public: BPTree():root(NULL){} void insert(int x); void display(); };
insertVal
函數負責找到插入的位置並返回
int BPTree::insertVal(int x, Node *cursor) { int i = 0; while (x > cursor->key[i] && i < cursor->size) i++; for (int j = cursor->size; j > i; j--) cursor->key[j] = cursor->key[j - 1]; cursor->key[i] = x; cursor->size++; return i; }
insert
函數負責進行插入這裏分爲幾種狀況
split
void BPTree::insert(int x) { if (root == NULL) { root = new Node; root->key[0] = x; root->IS_LEAF = true; root->size = 1; root->parent = NULL; } else { Node *cursor = root; Node *parent; while (cursor->IS_LEAF == false) { parent = cursor; for (int i = 0; i < cursor->size; i++) { if (x < cursor->key[i]) { cursor = cursor->ptr[i]; break; } if (i == cursor->size - 1) { cursor = cursor->ptr[i + 1]; break; } } } if (cursor->size < MAX) { insertVal(x,cursor); cursor->parent = parent; cursor->ptr[cursor->size] = cursor->ptr[cursor->size - 1]; cursor->ptr[cursor->size - 1] = NULL; } else split(x, parent, cursor); } }
這裏要分兩種狀況
insertInternal
函數void BPTree::split(int x, Node * parent, Node *cursor) { Node* LLeaf=new Node; Node* RLeaf=new Node; insertVal(x,cursor); LLeaf->IS_LEAF=RLeaf->IS_LEAF=true; LLeaf->size=(MAX+1)/2; RLeaf->size=(MAX+1)-(MAX+1)/2; for(int i=0;i<MAX+1;i++)LLeaf->ptr[i]=cursor->ptr[i]; LLeaf->ptr[LLeaf->size]= RLeaf; RLeaf->ptr[RLeaf->size]= LLeaf->ptr[MAX]; LLeaf->ptr[MAX] = NULL; for (int i = 0;i < LLeaf->size; i++) { LLeaf->key[i]= cursor->key[i]; } for (int i = 0,j=LLeaf->size;i < RLeaf->size; i++,j++) { RLeaf->key[i]= cursor->key[j]; } if(cursor==root){ Node* newRoot=new Node; newRoot->key[0] = RLeaf->key[0]; newRoot->ptr[0] = LLeaf; newRoot->ptr[1] = RLeaf; newRoot->IS_LEAF = false; newRoot->size = 1; root = newRoot; LLeaf->parent=RLeaf->parent=newRoot; } else {insertInternal(RLeaf->key[0],parent,LLeaf,RLeaf);} }
基本思路都是差很少的。就是須要注意遞歸調用
insertInternal
void BPTree::insertInternal(int x,Node* cursor,Node* LLeaf,Node* RRLeaf) { if (cursor->size < MAX) { auto i=insertVal(x,cursor); for (int j = cursor->size;j > i + 1; j--) { cursor->ptr[j]= cursor->ptr[j - 1]; } cursor->ptr[i]=LLeaf; cursor->ptr[i + 1] = RRLeaf; } else { Node* newLchild = new Node; Node* newRchild = new Node; Node* virtualPtr[MAX + 2]; for (int i = 0; i < MAX + 1; i++) { virtualPtr[i] = cursor->ptr[i]; } int i=insertVal(x,cursor); for (int j = MAX + 2;j > i + 1; j--) { virtualPtr[j]= virtualPtr[j - 1]; } virtualPtr[i]=LLeaf; virtualPtr[i + 1] = RRLeaf; newLchild->IS_LEAF=newRchild->IS_LEAF = false; //這裏和葉子結點上有區別的 newLchild->size= (MAX + 1) / 2; newRchild->size= MAX - (MAX + 1) /2; for (int i = 0;i < newLchild->size;i++) { newLchild->key[i]= cursor->key[i]; } for (int i = 0, j = newLchild->size+1;i < newRchild->size;i++, j++) { newRchild->key[i]= cursor->key[j]; } for (int i = 0;i < LLeaf->size + 1;i++) { newLchild->ptr[i]= virtualPtr[i]; } for (int i = 0, j = LLeaf->size + 1;i < RRLeaf->size + 1;i++, j++) { newRchild->ptr[i]= virtualPtr[j]; } if (cursor == root) { Node* newRoot = new Node; newRoot->key[0]= cursor->key[newLchild->size]; newRoot->ptr[0] = newLchild; newRoot->ptr[1] = newRchild; newRoot->IS_LEAF = false; newRoot->size = 1; root = newRoot; newLchild->parent=newRchild->parent=newRoot; } else { insertInternal(cursor->key[newLchild->size],cursor->parent,newLchild,newRchild); } } }
這裏用了一個簡單的層次遍歷。來實現展現函數
void BPTree::display() { queue<Node*>q; q.push(root); while(!q.empty()){ int size_t=q.size(); while(size_t--){ auto t=q.front(); for(int i=0;i<t->size+1;i++){ if(!t->IS_LEAF){ q.push(t->ptr[i]); } } for(int i=0;i<t->size;i++){ cout<<t->key[i]<<","; } cout<<" "; q.pop(); } cout<<endl; } }
假設咱們要插入5, 8,10,15 ,16 , 20 ,19
。以m=3(MAX=2)爲例子
獲得的結果以下
程序運行結果以下
,表示在一個結點內
三個空格表示不一樣的結點
能夠發現代碼是正確的。完整的代碼見下面的GitHub地址