B树与B+树

B 树定义

一颗 B 树 T 具有以下性质的有根树:(根为T.root)

  1. 每个结点 x 具有 n 个关键字,每个关键字非降序存放,有一个布尔型 leaf 表明 x 结点是不是叶子结点;
  2. 每个结点 x 内部还包含(x.n + 1)个孩子结点指针指向 x.c(1) ... x.c(i),叶子结点此属性为空;
  3. 关键字 x.key(i) 对存储在各个子树中的关键字范围(或是说关键字集合)加以分割;
  4. 每个叶子结点具有相同的高度 h;
  5. 每个结点的所包含的关键字个数有上界和下界:除开根结点的每个节点必须至少有(t-1)个关键字(至少有 t 个孩子);每个节点之多可以有(2t-1)个关键字(至多有 2t 个孩子)。(t 是一个被称为 B 树的最小度数,t >= 2,用来衡量最小度数的上界和下界)

有关 B 树的一些特性,注意与后面的 B+ 树区分:

  1. 关键字集合分布在整颗树中;
  2. 任何一个关键字出现且只出现在一个结点中;
  3. 搜索有可能在非叶子结点结束;
  4. 其搜索性能等价于在关键字全集内做一次二分查找;

B 树结点 BTreeNode

 1 class BTreeNode<K, V> {
 2         /** 节点的项,按键非降序存放 */
 3         private List<Entry<K,V>> entrys;
 4         /** 内节点的子节点 */
 5         private List<BTreeNode<K, V>> children;
 6         /** 是否为叶子节点 */
 7         private boolean leaf;
 8 
 9         ... ...  
10 }  

B 树结点内的键值对 Entry

1 class Entry<K, V> {
2         private K key;
3         private V value;
4         ... ...
5 }    

结点内搜寻结果的封装 SearchResult

这个结果封装类有两个作用。一是查找成功的情况,返回要的Key在这个结点的位置和值;二是查找失败的情况,返回这个Key可能所在的孩子结点中,以方便后续的查找。

 1     class SearchResult<V>
 2     {
 3         private boolean exist;
 4         private int index;
 5         private V value;
 6 
 7         public SearchResult(boolean exist, int index)
 8         {
 9             this.exist = exist;
10             this.index = index;
11         }
12 
13         public SearchResult(boolean exist, int index, V value)
14         {
15             this(exist, index);
16             this.value = value;
17         }
18         …… 
19     }

搜索 B 树

就像二叉搜索树一样,搜索在递归过程中所遇到的结点构成的一条从树根向下的简单路径,B树的高为 h,关键字个数为 n,由于结点 x.n < 2t,所以搜索整个树页面的时间复杂度为 O(log(t)h)。在结点点中搜索键值对的方法searchKey,采用的是二分查找,时间复杂度为O(log(2)t)。所以总的时间复杂度为 O((log(2)n) * (log(t)h)).

 1     private V search(BTreeNode<K, V> node, K key)
 2     {
 3         SearchResult<V> result = node.searchKey(key);
 4         if(result.isExist())
 5             return result.getValue();
 6         else
 7         {
 8             if(node.isLeaf())
 9                 return null;
10             else
11                 search(node.childAt(result.getIndex()), key);
12 
13         }
14         return null;
15     }

分裂操作 splitNode()

当一个结点的键值对个数等于规定的上界 maxSize,就要把结点分裂成两部分。主要把childNode中的右边的移动到到新的结点中,总的时间复杂度为 O(t)。

 1     private void splitNode(BTreeNode<K, V> parentNode, BTreeNode<K, V> childNode, int index)
 2     {
 3         assert childNode.size() == maxKeySize;
 4 
 5         BTreeNode<K, V> siblingNode = new BTreeNode<K, V>(kComparator);
 6         siblingNode.setLeaf(childNode.isLeaf());
 7         // 将满子节点中索引为[t, 2t - 2]的(t - 1)个项插入新的节点中
 8         for(int i = 0; i < minKeySize; ++ i)
 9             siblingNode.addEntry(childNode.entryAt(t + i));
10         // 提取满子节点中的中间项,其索引为(t - 1)
11         Entry<K, V> entry = childNode.entryAt(t - 1);
12         // 删除满子节点中索引为[t - 1, 2t - 2]的t个项
13         for(int i = maxKeySize - 1; i >= t - 1; -- i)
14             childNode.removeEntry(i);
15         if(!childNode.isLeaf()) // 如果满子节点不是叶节点,则还需要处理其子节点
16         {
17             // 将满子节点中索引为[t, 2t - 1]的t个子节点插入新的节点中
18             for(int i = 0; i < minKeySize + 1; ++ i)
19                 siblingNode.addChild(childNode.childAt(t + i));
20             // 删除满子节点中索引为[t, 2t - 1]的t个子节点
21             for(int i = maxKeySize; i >= t; -- i)
22                 childNode.removeChild(i);
23         }
24         // 将entry插入父节点
25         parentNode.insertEntry(entry, index);
26         // 将新节点插入父节点
27         parentNode.insertChild(siblingNode, index + 1);
28     }

在 B树插入键值对 insertNotFull()

在一颗高度为 h的 B树 T中,以沿着树单程下行方式插入一个关键字 k 的操作需要时间为 O((log(2)t) * log(t)n) (注:树高h=log(t)n),给定的要插入的结点需要是非满的。

 1     /**
 2      * 在一个非满节点中插入给定的项。
 3      *
 4      * @param node - 非满节点
 5      * @param entry - 给定的项
 6      * @return true,如果B树中不存在给定的项,否则false
 7      */
 8     private boolean insertNotFull(BTreeNode<K, V> node, Entry<K, V> entry)
 9     {
10         assert node.size() < maxKeySize;
11 
12         if(node.isLeaf()) // 如果是叶子节点,直接插入
13             return node.insertEntry(entry);
14         else
15         {
16             /* 找到entry在给定节点应该插入的位置,那么entry应该插入
17              * 该位置对应的子树中
18              */
19             SearchResult<V> result = node.searchKey(entry.getKey());
20             // 如果存在,则直接返回失败
21             if(result.isExist())
22                 return false;
23             BTreeNode<K, V> childNode = node.childAt(result.getIndex());
24             if(childNode.size() == 2*t - 1) // 如果子节点是满节点
25             {
26                 // 则先分裂
27                 splitNode(node, childNode, result.getIndex());
28                 /* 如果给定entry的键大于分裂之后新生成项的键,则需要插入该新项的右边,
29                  * 否则左边。
30                  */
31                 if(compare(entry.getKey(), node.entryAt(result.getIndex()).getKey()) > 0)
32                     childNode = node.childAt(result.getIndex() + 1);
33             }
34             return insertNotFull(childNode, entry);
35         }
36     }

B 树代码(

  1 package tree;
  2 
  3 import java.util.ArrayList;
  4 import java.util.Comparator;
  5 import java.util.LinkedList;
  6 import java.util.List;
  7 import java.util.Queue;
  8 import java.util.Random;
  9 
 10 /**
 11  * 一颗B树的简单实现。
 12  * <p/>
 13  * 其实现原理参考《算法导论》第二版第十八章。
 14  * <p/>
 15  * 如果大家想读懂这些源代码,不妨先看看上述章节。
 16  * <p/>
 17  * TODO B树如何存储在文件系统中,大家不妨想想
 18  *
 19  * @author WangPing 欢迎转载,转载请标明原文地址
 20  *
 21  * @param <K> - 键类型
 22  * @param <V> - 值类型
 23  */
 24 public class BTree<K, V>
 25 {
 26     //private static Log logger = LogFactory.getLog(BTree.class);
 27 
 28     /**
 29      * B树节点中的键值对。
 30      * <p/>
 31      * B树的节点中存储的是键值对。
 32      * 通过键访问值。
 33      *
 34      * @param <K> - 键类型
 35      * @param <V> - 值类型
 36      */
 37     private static class Entry<K, V>
 38     {
 39         private K key;
 40         private V value;
 41 
 42         public Entry(K k, V v)
 43         {
 44             this.key = k;
 45             this.value = v;
 46         }
 47 
 48         public K getKey()
 49         {
 50             return key;
 51         }
 52 
 53         public V getValue()
 54         {
 55             return value;
 56         }
 57 
 58         public void setValue(V value)
 59         {
 60             this.value = value;
 61         }
 62 
 63         @Override
 64         public String toString()
 65         {
 66             return key + ":" + value;
 67         }
 68     }
 69 
 70     /**
 71      * 在B树节点中搜索给定键值的返回结果。
 72      * <p/>
 73      * 该结果有两部分组成。第一部分表示此次查找是否成功,
 74      * 如果查找成功,第二部分表示给定键值在B树节点中的位置,
 75      * 如果查找失败,第二部分表示给定键值应该插入的位置。
 76      */
 77     private static class SearchResult<V>
 78     {
 79         private boolean exist;
 80         private int index;
 81         private V value;
 82 
 83         public SearchResult(boolean exist, int index)
 84         {
 85             this.exist = exist;
 86             this.index = index;
 87         }
 88 
 89         public SearchResult(boolean exist, int index, V value)
 90         {
 91             this(exist, index);
 92             this.value = value;
 93         }
 94 
 95         public boolean isExist()
 96         {
 97             return exist;
 98         }
 99 
100         public int getIndex()
101         {
102             return index;
103         }
104 
105         public V getValue()
106         {
107             return value;
108         }
109     }
110 
111     /**
112      * B树中的节点。
113      *
114      * TODO 需要考虑并发情况下的存取。
115      */
116     private static class BTreeNode<K, V>
117     {
118         /** 节点的项,按键非降序存放 */
119         private List<Entry<K,V>> entrys;
120         /** 内节点的子节点 */
121         private List<BTreeNode<K, V>> children;
122         /** 是否为叶子节点 */
123         private boolean leaf;
124         /** 键的比较函数对象 */
125         private Comparator<K> kComparator;
126 
127         private BTreeNode()
128         {
129             entrys = new ArrayList<Entry<K, V>>();
130             children = new ArrayList<BTreeNode<K, V>>();
131             leaf = false;
132         }
133 
134         public BTreeNode(Comparator<K> kComparator)
135         {
136             this();
137             this.kComparator = kComparator;
138         }
139 
140         public boolean isLeaf()
141         {
142             return leaf;
143         }
144 
145         public void setLeaf(boolean leaf)
146         {
147             this.leaf = leaf;
148         }
149 
150         /**
151          * 返回项的个数。如果是非叶子节点,根据B树的定义,
152          * 该节点的子节点个数为({@link #size()} + 1)。
153          *
154          * @return 关键字的个数
155          */
156         public int size()
157         {
158             return entrys.size();
159         }
160 
161         @SuppressWarnings("unchecked")
162         int compare(K key1, K key2)
163         {
164             return kComparator == null ? ((Comparable<K>)key1).compareTo(key2) : kComparator.compare(key1, key2);
165         }
166 
167         /**
168          * 在节点中查找给定的键。
169          * 如果节点中存在给定的键,则返回一个<code>SearchResult</code>,
170          * 标识此次查找成功,给定的键在节点中的索引和给定的键关联的值;
171          * 如果不存在,则返回<code>SearchResult</code>,
172          * 标识此次查找失败,给定的键应该插入的位置,该键的关联值为null。
173          * <p/>
174          * 如果查找失败,返回结果中的索引域为[0, {@link #size()}];
175          * 如果查找成功,返回结果中的索引域为[0, {@link #size()} - 1]
176          * <p/>
177          * 这是一个二分查找算法,可以保证时间复杂度为O(log(t))。
178          *
179          * @param key - 给定的键值
180          * @return - 查找结果
181          */
182         public SearchResult<V> searchKey(K key)
183         {
184             int low = 0;
185             int high = entrys.size() - 1;
186             int mid = 0;
187             while(low <= high)
188             {
189                 mid = (low + high) / 2; // 先这么写吧,BTree实现中,l+h不可能溢出
190                 Entry<K, V> entry = entrys.get(mid);
191                 if(compare(entry.getKey(), key) == 0) // entrys.get(mid).getKey() == key
192                     break;
193                 else if(compare(entry.getKey(), key) > 0) // entrys.get(mid).getKey() > key
194                     high = mid - 1;
195                 else // entry.get(mid).getKey() < key
196                     low = mid + 1;
197             }
198             boolean result = false;
199             int index = 0;
200             V value = null;
201             if(low <= high) // 说明查找成功
202             {
203                 result = true;
204                 index = mid; // index表示元素所在的位置
205                 value = entrys.get(index).getValue();
206             }
207             else
208             {
209                 result = false;
210                 index = low; // index表示元素应该插入的位置
211             }
212             return new SearchResult<V>(result, index, value);
213         }
214 
215         /**
216          * 将给定的项追加到节点的末尾,
217          * 你需要自己确保调用该方法之后,节点中的项还是
218          * 按照关键字以非降序存放。
219          *
220          * @param entry - 给定的项
221          */
222         public void addEntry(Entry<K, V> entry)
223         {
224             entrys.add(entry);
225         }
226 
227         /**
228          * 删除给定索引的<code>entry</code>。
229          * <p/>
230          * 你需要自己保证给定的索引是合法的。
231          *
232          * @param index - 给定的索引
233          * @param 给定索引处的项
234          */
235         public Entry<K, V> removeEntry(int index)
236         {
237             return entrys.remove(index);
238         }
239 
240         /**
241          * 得到节点中给定索引的项。
242          * <p/>
243          * 你需要自己保证给定的索引是合法的。
244          *
245          * @param index - 给定的索引
246          * @return 节点中给定索引的项
247          */
248         public Entry<K, V> entryAt(int index)
249         {
250             return entrys.get(index);
251         }
252 
253         /**
254          * 如果节点中存在给定的键,则更新其关联的值。
255          * 否则插入。
256          *
257          * @param entry - 给定的项
258          * @return null,如果节点之前不存在给定的键,否则返回给定键之前关联的值
259          */
260         public V putEntry(Entry<K, V> entry)
261         {
262             SearchResult<V> result = searchKey(entry.getKey());
263             if(result.isExist())
264             {
265                 V oldValue = entrys.get(result.getIndex()).getValue();
266                 entrys.get(result.getIndex()).setValue(entry.getValue());
267                 return oldValue;
268             }
269             else
270             {
271                 insertEntry(entry, result.getIndex());
272                 return null;
273             }
274         }
275 
276         /**
277          * 在该节点中插入给定的项,
278          * 该方法保证插入之后,其键值还是以非降序存放。
279          * <p/>
280          * 不过该方法的时间复杂度为O(t)。
281          * <p/>
282          * <b>注意:</b>B树中不允许键值重复。
283          *
284          * @param entry - 给定的键值
285          * @return true,如果插入成功,false,如果插入失败
286          */
287         public boolean insertEntry(Entry<K, V> entry)
288         {
289             SearchResult<V> result = searchKey(entry.getKey());
290             if(result.isExist())
291                 return false;
292             else
293             {
294                 insertEntry(entry, result.getIndex());
295                 return true;
296             }
297         }
298 
299         /**
300          * 在该节点中给定索引的位置插入给定的项,
301          * 你需要自己保证项插入了正确的位置。
302          *
303          * @param key - 给定的键值
304          * @param index - 给定的索引
305          */
306         public void insertEntry(Entry<K, V> entry, int index)
307         {
308             /*
309              * 通过新建一个ArrayList来实现插入真的很恶心,先这样吧
310              * 要是有类似C中的reallocate就好了。
311              */
312             List<Entry<K, V>> newEntrys = new ArrayList<Entry<K, V>>();
313             int i = 0;
314             // index = 0或者index = keys.size()都没有问题
315             for(; i < index; ++ i)
316                 newEntrys.add(entrys.get(i));
317             newEntrys.add(entry);
318             for(; i < entrys.size(); ++ i)
319                 newEntrys.add(entrys.get(i));
320             entrys.clear();
321             entrys = newEntrys;
322         }
323 
324         /**
325          * 返回节点中给定索引的子节点。
326          * <p/>
327          * 你需要自己保证给定的索引是合法的。
328          *
329          * @param index - 给定的索引
330          * @return 给定索引对应的子节点
331          */
332         public BTreeNode<K, V> childAt(int index)
333         {
334             if(isLeaf())
335                 throw new UnsupportedOperationException("Leaf node doesn't have children.");
336             return children.get(index);
337         }
338 
339         /**
340          * 将给定的子节点追加到该节点的末尾。
341          *
342          * @param child - 给定的子节点
343          */
344         public void addChild(BTreeNode<K, V> child)
345         {
346             children.add(child);
347         }
348 
349         /**
350          * 删除该节点中给定索引位置的子节点。
351          * </p>
352          * 你需要自己保证给定的索引是合法的。
353          *
354          * @param index - 给定的索引
355          */
356         public void removeChild(int index)
357         {
358             children.remove(index);
359         }
360 
361         /**
362          * 将给定的子节点插入到该节点中给定索引
363          * 的位置。
364          *
365          * @param child - 给定的子节点
366          * @param index - 子节点带插入的位置
367          */
368         public void insertChild(BTreeNode<K, V> child, int index)
369         {
370             List<BTreeNode<K, V>> newChildren = new ArrayList<BTreeNode<K, V>>();
371             int i = 0;
372             for(; i < index; ++ i)
373                 newChildren.add(children.get(i));
374             newChildren.add(child);
375             for(; i < children.size(); ++ i)
376                 newChildren.add(children.get(i));
377             children = newChildren;
378         }
379     }
380 
381     private static final int DEFAULT_T = 2;
382 
383     /** B树的根节点 */
384     private BTreeNode<K, V> root;
385     /** 根据B树的定义,B树的每个非根节点的关键字数n满足(t - 1) <= n <= (2t - 1) */
386     private int t = DEFAULT_T;
387     /** 非根节点中最小的键值数 */
388     private int minKeySize = t - 1;
389     /** 非根节点中最大的键值数 */
390     private int maxKeySize = 2*t - 1;
391     /** 键的比较函数对象 */
392     private Comparator<K> kComparator;
393 
394     /**
395      * 构造一颗B树,键值采用采用自然排序方式
396      */
397     public BTree()
398     {
399         root = new BTreeNode<K, V>();
400         root.setLeaf(true);
401     }
402 
403     public BTree(int t)
404     {
405         this();
406         this.t = t;
407         minKeySize = t - 1;
408         maxKeySize = 2*t - 1;
409     }
410 
411     /**
412      * 以给定的键值比较函数对象构造一颗B树。
413      *
414      * @param kComparator - 键值的比较函数对象
415      */
416     public BTree(Comparator<K> kComparator)
417     {
418         root = new BTreeNode<K, V>(kComparator);
419         root.setLeaf(true);
420         this.kComparator = kComparator;
421     }
422 
423     public BTree(Comparator<K> kComparator, int t)
424     {
425         this(kComparator);
426         this.t = t;
427         minKeySize = t - 1;
428         maxKeySize = 2*t - 1;
429     }
430 
431     @SuppressWarnings("unchecked")
432     int compare(K key1, K key2)
433     {
434         return kComparator == null ? ((Comparable<K>)key1).compareTo(key2) : kComparator.compare(key1, key2);
435     }
436 
437     /**
438      * 搜索给定的键。
439      *
440      * @param key - 给定的键值
441      * @return 键关联的值,如果存在,否则null
442      */
443     public V search(K key)
444     {
445         return search(root, key);
446     }
447 
448     /**
449      * 在以给定节点为根的子树中,递归搜索
450      * 给定的<code>key</code>
451      *
452      * @param node - 子树的根节点
453      * @param key - 给定的键值
454      * @return 键关联的值,如果存在,否则null
455      */
456     private V search(BTreeNode<K, V> node, K key)
457     {
458         SearchResult<V> result = node.searchKey(key);
459         if(result.isExist())
460             return result.getValue();
461         else
462         {
463             if(node.isLeaf())
464                 return null;
465             else
466                 search(node.childAt(result.getIndex()), key);
467 
468         }
469         return null;
470     }
471 
472     /**
473      * 分裂一个满子节点<code>childNode</code>。
474      * <p/>
475      * 你需要自己保证给定的子节点是满节点。
476      *
477      * @param parentNode - 父节点
478      * @param childNode - 满子节点
479      * @param index - 满子节点在父节点中的索引
480      */
481     private void splitNode(BTreeNode<K, V> parentNode, BTreeNode<K, V> childNode, int index)
482     {
483         assert childNode.size() == maxKeySize;
484 
485         BTreeNode<K, V> siblingNode = new BTreeNode<K, V>(kComparator);
486         siblingNode.setLeaf(childNode.isLeaf());
487         // 将满子节点中索引为[t, 2t - 2]的(t - 1)个项插入新的节点中
488         for(int i = 0; i < minKeySize; ++ i)
489             siblingNode.addEntry(childNode.entryAt(t + i));
490         // 提取满子节点中的中间项,其索引为(t - 1)
491         Entry<K, V> entry = childNode.entryAt(t - 1);
492         // 删除满子节点中索引为[t - 1, 2t - 2]的t个项
493         for(int i = maxKeySize - 1; i >= t - 1; -- i)
494             childNode.removeEntry(i);
495         if(!childNode.isLeaf()) // 如果满子节点不是叶节点,则还需要处理其子节点
496         {
497             // 将满子节点中索引为[t, 2t - 1]的t个子节点插入新的节点中
498             for(int i = 0; i < minKeySize + 1; ++ i)
499                 siblingNode.addChild(childNode.childAt(t + i));
500             // 删除满子节点中索引为[t, 2t - 1]的t个子节点
501             for(int i = maxKeySize; i >= t; -- i)
502                 childNode.removeChild(i);
503         }
504         // 将entry插入父节点
505         parentNode.insertEntry(entry, index);
506         // 将新节点插入父节点
507         parentNode.insertChild(siblingNode, index + 1);
508     }
509 
510     /**
511      * 在一个非满节点中插入给定的项。
512      *
513      * @param node - 非满节点
514      * @param entry - 给定的项
515      * @return true,如果B树中不存在给定的项,否则false
516      */
517     private boolean insertNotFull(BTreeNode<K, V> node, Entry<K, V> entry)
518     {
519         assert node.size() < maxKeySize;
520 
521         if(node.isLeaf()) // 如果是叶子节点,直接插入
522             return node.insertEntry(entry);
523         else
524         {
525             /* 找到entry在给定节点应该插入的位置,那么entry应该插入
526              * 该位置对应的子树中
527              */
528             SearchResult<V> result = node.searchKey(entry.getKey());
529             // 如果存在,则直接返回失败
530             if(result.isExist())
531                 return false;
532             BTreeNode<K, V> childNode = node.childAt(result.getIndex());
533             if(childNode.size() == 2*t - 1) // 如果子节点是满节点
534             {
535                 // 则先分裂
536                 splitNode(node, childNode, result.getIndex());
537                 /* 如果给定entry的键大于分裂之后新生成项的键,则需要插入该新项的右边,
538                  * 否则左边。
539                  */
540                 if(compare(entry.getKey(), node.entryAt(result.getIndex()).getKey()) > 0)
541                     childNode = node.childAt(result.getIndex() + 1);
542             }
543             return insertNotFull(childNode, entry);
544         }
545     }
546 
547     /**
548      * 在B树中插入给定的键值对。
549      *
550      * @param key - 键
551      * @param value - 值
552      * @return true,如果B树中不存在给定的项,否则false
553      */
554     public boolean insert(K key, V value)
555     {
556         if(root.size() == maxKeySize) // 如果根节点满了,则B树长高
557         {
558             BTreeNode<K, V> newRoot = new BTreeNode<K, V>(kComparator);
559             newRoot.setLeaf(false);
560             newRoot.addChild(root);
561             splitNode(newRoot, root, 0);
562             root = newRoot;
563         }
564         return insertNotFull(root, new Entry<K, V>(key, value));
565     }
566 
567     /**
568      * 如果存在给定的键,则更新键关联的值,
569      * 否则插入给定的项。
570      *
571      * @param node - 非满节点
572      * @param entry - 给定的项
573      * @return true,如果B树中不存在给定的项,否则false
574      */
575     private V putNotFull(BTreeNode<K, V> node, Entry<K, V> entry)
576     {
577         assert node.size() < maxKeySize;
578 
579         if(node.isLeaf()) // 如果是叶子节点,直接插入
580             return node.putEntry(entry);
581         else
582         {
583             /* 找到entry在给定节点应该插入的位置,那么entry应该插入
584              * 该位置对应的子树中
585              */
586             SearchResult<V> result = node.searchKey(entry.getKey());
587             // 如果存在,则更新
588             if(result.isExist())
589                 return node.putEntry(entry);
590             BTreeNode<K, V> childNode = node.childAt(result.getIndex());
591             if(childNode.size() == 2*t - 1) // 如果子节点是满节点
592             {
593                 // 则先分裂
594                 splitNode(node, childNode, result.getIndex());
595                 /* 如果给定entry的键大于分裂之后新生成项的键,则需要插入该新项的右边,
596                  * 否则左边。
597                  */
598                 if(compare(entry.getKey(), node.entryAt(result.getIndex()).getKey()) > 0)
599                     childNode = node.childAt(result.getIndex() + 1);
600             }
601             return putNotFull(childNode, entry);
602         }
603     }
604 
605     /**
606      * 如果B树中存在给定的键,则更新值。
607      * 否则插入。
608      *
609      * @param key - 键
610      * @param value - 值
611      * @return 如果B树中存在给定的键,则返回之前的值,否则null
612      */
613     public V put(K key, V value)
614     {
615         if(root.size() == maxKeySize) // 如果根节点满了,则B树长高
616         {
617             BTreeNode<K, V> newRoot = new BTreeNode<K, V>(kComparator);
618             newRoot.setLeaf(false);
619             newRoot.addChild(root);
620             splitNode(newRoot, root, 0);
621             root = newRoot;
622         }
623         return putNotFull(root, new Entry<K, V>(key, value));
624     }
625 
626     /**
627      * 从B树中删除一个与给定键关联的项。
628      *
629      * @param key - 给定的键
630      * @return 如果B树中存在给定键关联的项,则返回删除的项,否则null
631      */
632     public Entry<K, V> delete(K key)
633     {
634         return delete(root, key);
635     }
636 
637     /**
638      * 从以给定<code>node</code>为根的子树中删除与给定键关联的项。
639      * <p/>
640      * 删除的实现思想请参考《算法导论》第二版的第18章。
641      *
642      * @param node - 给定的节点
643      * @param key - 给定的键
644      * @return 如果B树中存在给定键关联的项,则返回删除的项,否则null
645      */
646     private Entry<K, V> delete(BTreeNode<K, V> node, K key)
647     {
648         // 该过程需要保证,对非根节点执行删除操作时,其关键字个数至少为t。
649         assert node.size() >= t || node == root;
650 
651         SearchResult<V> result = node.searchKey(key);
652         /*
653          * 因为这是查找成功的情况,0 <= result.getIndex() <= (node.size() - 1),
654          * 因此(result.getIndex() + 1)不会溢出。
655          */
656         if(result.isExist())
657         {
658             // 1.如果关键字在节点node中,并且是叶节点,则直接删除。
659             if(node.isLeaf())
660                 return node.removeEntry(result.getIndex());
661             else
662             {
663                 // 2.a 如果节点node中前于key的子节点包含至少t个项
664                 BTreeNode<K, V> leftChildNode = node.childAt(result.getIndex());
665                 if(leftChildNode.size() >= t)
666                 {
667                     // 使用leftChildNode中的最后一个项代替node中需要删除的项
668                     node.removeEntry(result.getIndex());
669                     node.insertEntry(leftChildNode.entryAt(leftChildNode.size() - 1), result.getIndex());
670                     // 递归删除左子节点中的最后一个项
671                     return delete(leftChildNode, leftChildNode.entryAt(leftChildNode.size() - 1).getKey());
672                 }
673                 else
674                 {
675                     // 2.b 如果节点node中后于key的子节点包含至少t个关键字
676                     BTreeNode<K, V> rightChildNode = node.childAt(result.getIndex() + 1);
677                     if(rightChildNode.size() >= t)
678                     {
679                         // 使用rightChildNode中的第一个项代替node中需要删除的项
680                         node.removeEntry(result.getIndex());
681                         node.insertEntry(rightChildNode.entryAt(0), result.getIndex());
682                         // 递归删除右子节点中的第一个项
683                         return delete(rightChildNode, rightChildNode.entryAt(0).getKey());
684                     }
685                     else // 2.c 前于key和后于key的子节点都只包含t-1个项
686                     {
687                         Entry<K, V> deletedEntry = node.removeEntry(result.getIndex());
688                         node.removeChild(result.getIndex() + 1);
689                         // 将node中与key关联的项和rightChildNode中的项合并进leftChildNode
690                         leftChildNode.addEntry(deletedEntry);
691                         for(int i = 0; i < rightChildNode.size(); ++ i)
692                             leftChildNode.addEntry(rightChildNode.entryAt(i));
693                         // 将rightChildNode中的子节点合并进leftChildNode,如果有的话
694                         if(!rightChildNode.isLeaf())
695                         {
696                             for(int i = 0; i <= rightChildNode.size(); ++ i)
697                                 leftChildNode.addChild(rightChildNode.childAt(i));
698                         }
699                         return delete(leftChildNode, key);
700                     }
701                 }
702             }
703         }
704         else
705         {
706             /*
707              * 因为这是查找失败的情况,0 <= result.getIndex() <= node.size(),
708              * 因此(result.getIndex() + 1)会溢出。
709              */
710             if(node.isLeaf()) // 如果关键字不在节点node中,并且是叶节点,则什么都不做,因为该关键字不在该B树中
711             {
712                 //logger.info("The key: " + key + " isn't in this BTree.");
713                 System.out.println("The key: " + key + " isn't in this BTree.");
714                 return null;
715             }
716             BTreeNode<K, V> childNode = node.childAt(result.getIndex());
717             if(childNode.size() >= t) // // 如果子节点有不少于t个项,则递归删除
718                 return delete(childNode, key);
719             else // 3
720             {
721                 // 先查找右边的兄弟节点
722                 BTreeNode<K, V> siblingNode = null;
723                 int siblingIndex = -1;
724                 if(result.getIndex() < node.size()) // 存在右兄弟节点
725                 {
726                     if(node.childAt(result.getIndex() + 1).size() >= t)
727                     {
728                         siblingNode = node.childAt(result.getIndex() + 1);
729                         siblingIndex = result.getIndex() + 1;
730                     }
731                 }
732                 // 如果右边的兄弟节点不符合条件,则试试左边的兄弟节点
733                 if(siblingNode == null)
734                 {
735                     if(result.getIndex() > 0) // 存在左兄弟节点
736                     {
737                         if(node.childAt(result.getIndex() - 1).size() >= t)
738                         {
739                             siblingNode = node.childAt(result.getIndex() - 1);
740                             siblingIndex = result.getIndex() - 1;
741                         }
742                     }
743                 }
744                 // 3.a 有一个相邻兄弟节点至少包含t个项
745                 if(siblingNode != null)
746                 {
747                     if(siblingIndex < result.getIndex()) // 左兄弟节点满足条件
748                     {
749                         childNode.insertEntry(node.entryAt(siblingIndex), 0);
750                         node.removeEntry(siblingIndex);
751                         node.insertEntry(siblingNode.entryAt(siblingNode.size() - 1), siblingIndex);
752                         siblingNode.removeEntry(siblingNode.size() - 1);
753                         // 将左兄弟节点的最后一个孩子移到childNode
754                         if(!siblingNode.isLeaf())
755                         {
756                             childNode.insertChild(siblingNode.childAt(siblingNode.size()), 0);
757                             siblingNode.removeChild(siblingNode.size());
758                         }
759                     }
760                     else // 右兄弟节点满足条件
761                     {
762                         childNode.insertEntry(node.entryAt(result.getIndex()), childNode.size() - 1);
763                         node.removeEntry(result.getIndex());
764                         node.insertEntry(siblingNode.entryAt(0), result.getIndex());
765                         siblingNode.removeEntry(0);
766                         // 将右兄弟节点的第一个孩子移到childNode
767                         // childNode.insertChild(siblingNode.childAt(0), childNode.size() + 1);
768                         if(!siblingNode.isLeaf())
769                         {
770                             childNode.addChild(siblingNode.childAt(0));
771                             siblingNode.removeChild(0);
772                         }
773                     }
774                     return delete(childNode, key);
775                 }
776                 else // 3.b 如果其相邻左右节点都包含t-1个项
777                 {
778                     if(result.getIndex() < node.size()) // 存在右兄弟,直接在后面追加
779                     {
780                         BTreeNode<K, V> rightSiblingNode = node.childAt(result.getIndex() + 1);
781                         childNode.addEntry(node.entryAt(result.getIndex()));
782                         node.removeEntry(result.getIndex());
783                         node.removeChild(result.getIndex() + 1);
784                         for(int i = 0; i < rightSiblingNode.size(); ++ i)
785                             childNode.addEntry(rightSiblingNode.entryAt(i));
786                         if(!rightSiblingNode.isLeaf())
787                         {
788                             for(int i = 0; i <= rightSiblingNode.size(); ++ i)
789                                 childNode.addChild(rightSiblingNode.childAt(i));
790                         }
791                     }
792                     else // 存在左节点,在前面插入
793                     {
794                         BTreeNode<K, V> leftSiblingNode = node.childAt(result.getIndex() - 1);
795                         childNode.insertEntry(node.entryAt(result.getIndex() - 1), 0);
796                         node.removeEntry(result.getIndex() - 1);
797                         node.removeChild(result.getIndex() - 1);
798                         for(int i = leftSiblingNode.size() - 1; i >= 0; -- i)
799                             childNode.insertEntry(leftSiblingNode.entryAt(i), 0);
800                         if(!leftSiblingNode.isLeaf())
801                         {
802                             for(int i = leftSiblingNode.size(); i >= 0; -- i)
803                                 childNode.insertChild(leftSiblingNode.childAt(i), 0);
804                         }
805                     }
806                     // 如果node是root并且node不包含任何项了
807                     if(node == root && node.size() == 0)
808                         root = childNode;
809                     return delete(childNode, key);
810                 }
811             }
812         }
813     }
814 
815     /**
816      * 一个简单的层次遍历B树实现,用于输出B树。
817      */
818     public void output()
819     {
820         Queue<BTreeNode<K, V>> queue = new LinkedList<BTreeNode<K, V>>();
821         queue.offer(root);
822         while(!queue.isEmpty())
823         {
824             BTreeNode<K, V> node = queue.poll();
825             for(int i = 0; i < node.size(); ++ i)
826                 System.out.print(node.entryAt(i) + " ");
827             System.out.println();
828             if(!node.isLeaf())
829             {
830                 for(int i = 0; i <= node.size(); ++ i)
831                     queue.offer(node.childAt(i));
832             }
833         }
834     }
835 
836     public static void main(String[] args)
837     {
838         Random random = new Random();
839         BTree<Integer, Integer> btree = new BTree<Integer, Integer>(3);
840         List<Integer> save = new ArrayList<Integer>();
841         for(int i = 0; i < 10; ++ i)
842         {
843             int r = random.nextInt(100);
844             save.add(r);
845             System.out.println(r);
846             btree.insert(r, r);
847         }
848 
849         System.out.println("----------------------");
850         btree.output();
851         System.out.println("----------------------");
852         btree.delete(save.get(0));
853         btree.output();
854     }
855 }
View Code

B+ 树与 B 树的区别

B+ 树,是 B 树的一种变体,查询性能更好。m 阶的 B+ 树的特征:

  1. 有 n 棵子树的非叶子结点中含有 n 个关键字(B 树是 n-1 个),这些关键字不保存数据,只用来索引所有数据都保存在叶子节点(B 树是每个关键字都保存数据)。
  2. 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
  3. 所有的非叶子结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。
  4. 通常在 B+ 树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点。
  5. 同一个数字会在不同节点中重复出现,根节点的最大元素就是 B+ 树的最大元素。

B+ 树相比于 B 树的查询优势:

  1. B+ 树的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”;
  2. B+ 树查询必须查找到叶子节点,B树只要匹配到即可不用管元素位置,因此 B+ 树查找更稳定(并不慢);
  3. 对于范围查找来说,B+ 树只需遍历叶子节点链表即可,B树却需要重复地中序遍历。
posted @ 2019-09-08 23:36  賣贾笔的小男孩  阅读(323)  评论(0编辑  收藏  举报