B+
B+树可以看作是B树的一种变形,在实现文件索引结构方面比B树使用得更普遍。
B+树与B树区别
m阶B+
树与m阶B树区别:
- 所有叶子节点包含全部关键字信息, 及指向含有这些关键字记录的指针, 且叶子节点中关键字进行有序链接;
- 非叶子节点相当于是叶子节点的索引, 叶子节点相当于是存储(关键字)数据的数据层;
B+树
一棵m阶B+树可以定义如下:
- 树中每个非叶结点最多有m棵子树;
- 根结点(非叶结点)至少有2棵子树。除根结点外,其它的非叶结点至少有⌈m/2⌉棵子树;有n棵子树的非叶结点有n-1个关键码。
- 所有的叶结点都处于同一层次上,包含了全部关键码及指向相应数据对象存放地址的指针,且叶结点本身按关键码从小到大顺序链接;
B+树的优势
读写代价更低
B+树的磁盘读写代价更低, B+树的内部节点没有指向具体数据的指针, 因此内部节点相对B树更小, 如果把同一内部节点的关键字放在同一块磁盘中, 盘块所能容纳的关键字数量也就越多, 一次性读入内存中的需要查找的关键字也就越多, 相对IO读写次数减少, 性能更高;
查询效率更加稳定
非叶节点并不是最终指向文件内容的节点, 只作为叶子节点中关键字的索引; 所以任何关键字的查找必须走一条从根节点到叶子节点的路; 所有关键字查询的路径长度相同, 于是每一个数据的查询效率基本等同;
统计数据手段更灵活
由于B+树把数据都放到了最底一层, 可以很方便的把这些数据一个个链接起来形成链表, 加上B+树数据本来就是按序存放的, 这样的话有利于整表搜索或区间查找, 比B树的区间搜索快很多;
举个例子
假设磁盘中的一个盘块容纳16bytes, 而一个关键字2bytes, 一个关键字具体信息指针2bytes; 一棵9阶BTree的内部节点需要2个盘块; 而B+Tree内部节点只需要1个盘块, 当需要把内部节点读入内存的时候, B树就比B+树多一次盘块查找时间, 在磁盘中就是盘片旋转的时间; (这就是省了指针, 只存索引项的好处)
B+树节点结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #define M 5 #define MAXSIZE (M-1) #define MINSIZE (M/2) typedef char KeyType; typedef struct Record {} Record; typedef enum{BRCH = 1, LEAF = 2}NodeType; typedef struct BNode { int num; BNode * parent; NodeType utype; KeyType key[M + 1];
union { struct { Record * recptr[M + 1]; BNode * prev, * next; }; BNode * sub[M + 1]; }; struct BNode* parent; ElemType data[M + 1]; struct BNode* sub[M + 1]; }BNode;
|
B+树和B树的分支结构一样,但叶子结构不一样。
除了上述结构。还有一些规定
1 2 3 4
| #define BRCHMAX (M-1) #define BRCHMIN (M/2) #define LEAFMAX (M) #define LEAFMIN (M/2+1)
|
B+树的Insert
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
| #include<iostream> using namespace std; #define M 5 #define BRCHMAX (M-1) #define BRCHMIN (M/2) #define LEAFMAX (M) #define LEAFMIN (M/2+1) typedef char KeyType; typedef struct Record {} Record; typedef enum{BRCH = 1, LEAF = 2}NodeType; typedef struct BNode { int num; BNode * parent; NodeType utype; KeyType key[M + 1];
union { struct { Record * recptr[M + 1]; BNode * prev, * next; }; BNode * sub[M + 1]; }; struct BNode* parent; ElemType data[M + 1]; struct BNode* sub[M + 1]; }BNode;
BNode* Buynode() { BNode* s = (BNode*)malloc(sizeof(BNode)); if (nullptr == s)exit(1); memset(s, 0, sizeof(BNode)); return s; } BNode* BuyLeaf() { BNode * s = Buynode(); s->parent = nullptr; s->utype = LEAF; return s; } void Init_BTree(BTree& tree) { tree.root = nullptr; tree.first = nullptr; tree.cursize = 0; } Result FindRoot(BNode * ptr, KeyType kx) { Result res = {nullptr, -1, false}; BNode * p = ptr; while(p!=nullptr&&p->utype==BRCH) { p->key[0] = kx; int i = p->num; while(kx < p->key[i]) --i; p = p->sub[i]; } res = FindLeaf(p, kx); return res; } Result FindLeaf(BNode * ptr, KeyType kx) { Result res = {nullptr, -1, false}; BNode * p = ptr; while(p!=nullptr&&p->next!=nullptr && kx > p->key[p->num -1]) { p = p->next; } if(p == nullptr)return res; int pos = p->num -1; while(pos >= 0 && kx < p->key[pos]) { --pos; } res.pnode = p; res.index = pos; if(pos < 0 && p->prev != nullptr) { res.pnode = p->prev; res.index = p->prev->num -1; }else if(pos >= 0 && kx == p->key[pos]) { res.tag = true; } return res; } Result FindKey(BTree& tree, KeyType kx) { Result res = { nullptr, -1,false }; struct BNode* ptr = tree.root; while (ptr != nullptr) { ptr->data[0].key = kx; int i = ptr->num; while (i>=0 && ptr->data[i].key) { --i; } res.pnode = ptr; res.index = i; if (i > 0 && kx == ptr->data[i].key) { res.tag = true; break; } ptr = ptr->sub[i]; } return res; } BNode* MakeRoot(const KeyType kx, BNode* left, BNode* right) { BNode* s = Buynode(); s->num = 1; s->parent = nullptr; s->key[1] = kx; s->sub[0] = left; if (left != nullptr)left->parent = s; s->sub[1] = right; if (right != nullptr)right->parent = s; return s; } void Insert_Item(BNode* ptr, int pos, const ElemType& item, BNode* right) { for (int i = ptr->num; i > pos; --i) { ptr->data[i + 1] = ptr->data[i]; ptr->sub[i + 1] = ptr->sub[i]; } ptr->data[pos + 1] = item; ptr->sub[pos + 1] = right; ptr->num += 1; } ElemType Move_Item(BNode* s, BNode* ptr, int pos) { for (int i = 0, j = pos + 1; j <= ptr->num; ++i, ++j) { s->data[i] = ptr->data[j]; s->sub[i] = ptr->sub[j]; if (s->sub[i] != nullptr) { s->sub[i]->parent = s; } } s->num = MINSIZE; ptr->num = MINSIZE; s->parent = ptr->parent; return s->data[0]; } BNode* Splice(BNode* ptr) { BNode* s = Buynode(); ElemType item = Move_Item(s, ptr, MINSIZE); if (ptr->parent == nullptr) { return MakeRoot(item, ptr, s); } BNode* pa = ptr->parent; int pos = pa->num; pa->data[0] = item; while (pos > 0 && item.key < pa->data[pos].key) { --pos; } Insert_Item(pa, pos, item, s); if (pa->num > MAXSIZE) { return Splice(pa); } else { return nullptr; } } void Insert_Leaf_Item(BNode * ptr, int pos, KeyType kx, Record *rec) { for(int i = ptr->num-1; i>pos; --i) { ptr->key[i+1] = ptr->key[i]; ptr->recptr[i+1] = ptr->recptr[i]; } ptr->key[pos+1] = kx; ptr->recptr[pos+1] = rec; ptr->num+=1; } KeyType Move_Leaf_Item(BNode *s, BNode* ptr) { for(int i = 0;j = LEAFMIN;j<ptr->num;++i,++j) { s->key[i] = key[j]; s->recptr[i] = ptr->recptr[j]; } s->num = LEAFMIN; ptr->num = LEAFMIN; s->parent = ptr->parent; s->next = ptr->next; s->prev = ptr; ptr->next = s; if(s->next != nullptr) { s->next->prev = s; } return s->key[0]; } BNode* Splice_Leaf(BNode * ptr) { BNode * s = BuyLeaf(); KeyType kx = Move_Leaf_Item(s, ptr); if(ptr->parent == nullptr) { return MakeRoot(kx, ptr, s); } } bool Insert(BTree& tree, KeyType kx, Record * rec) { if (tree.root == nullptr) { BNode *s = BuyLeaf(); s->key[0] = kx; s->recptr[0] = rec; s->num = 1; tree.root = tree.first = s; return true; } Result resr = FindRoot(tree.root, kx); Result resf = FindLeaf(tree.first, kx); if(resf.pnode == nullptr){cout << "BTree struct err"<<endl;return false;} if (resf.tag){cout << "exist"<<endl;return false;} BNode* ptr = resf.pnode; int pos = resf.index; Insert_Leaf_Item(ptr, pos, kx, rec); if (ptr->num > LEAFMAX) { BNode* newroot = Splice_Leaf(ptr); if (newroot != nullptr) { tree.root = newroot; } } tree.cursize += 1; return true; }
int main() { BTree myt; Init_BTree(myt); char ch[] = { "qwertyuiopasdfghjklzxcvbnm" }; int i = 0; while (ch[i] != '\0') { ElemType item = { ch[i],nullptr }; cout << Insert(myt, item); i++; } cout << endl; }
|
总结
B树的每一个节点, 存了关键字和对应的数据地址, 而B+树的非叶子节点只存关键字, 不存数据地址。因此B+树的每一个非叶子节点存储的关键字是远远多于B树的; B+树的叶子节点存放关键字和数据, 因此从树的高度上来说B+树的高度要小于B树, 使用的磁盘IO次数少, 因此查询会更快一些。
由于B树每个节点都存储关键字和数据, 因此离根节点近的数据, 查询的就快, 离根节点远的数据, 查询的就慢; B+树所有的数据都存在叶子节点上, 因此在B+树上搜索关键字, 找到对应数据的时间是比较平均的, 没有快慢之分。
如果在B树上做区间查找, 遍历的节点是非常多的; B+树所有叶子节点被连接成了有序链表结构, 因此做整表遍历和区间查找是非常容易的。