为了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地址