Skip to content

Commit bc0375f

Browse files
committed
added BTree implementation and tests
1 parent 191bec3 commit bc0375f

File tree

13 files changed

+405
-0
lines changed

13 files changed

+405
-0
lines changed
48.4 KB
Loading
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
package dataStructures.bTree;
2+
3+
/** This BTree implementation is a simplified implementation, which supports the basic search, insert, delete and
4+
* in-order traversal operations. It is not designed to cover edge cases.
5+
*/
6+
public class BTree {
7+
private BTreeNode root;
8+
private int t;
9+
10+
/**
11+
* Constructs a B-tree with a specified minimum degree.
12+
* @param t The minimum degree of the B-tree.
13+
*/
14+
public BTree(int t) {
15+
this.root = null;
16+
this.t = t;
17+
}
18+
19+
/** Inner class representing a B-tree node
20+
*/
21+
private class BTreeNode {
22+
int[] keys;
23+
BTreeNode[] children;
24+
int keyCount; // necessary for Java implementation due to fixed-size arrays
25+
boolean leaf;
26+
27+
/**
28+
* Constructor for creating a B-tree node.
29+
* @param t The minimum degree of the B-tree.
30+
* @param leaf Indicates whether the node is a leaf.
31+
*/
32+
public BTreeNode(int t, boolean leaf) {
33+
this.keys = new int[2 * t - 1];
34+
this.children = new BTreeNode[2 * t];
35+
this.keyCount = 0;
36+
this.leaf = leaf;
37+
}
38+
}
39+
40+
/**
41+
* Searches for a key in the B-tree.
42+
* @param key The key to search for.
43+
* @return true if the key is found, false otherwise.
44+
*/
45+
public boolean search(int key) {
46+
return search(key, this.root);
47+
}
48+
49+
private boolean search(int key, BTreeNode node) {
50+
int i = 0;
51+
while (i < node.keyCount && key > node.keys[i]) {
52+
i++;
53+
}
54+
55+
if (i < node.keyCount && key == node.keys[i]) {
56+
return true;
57+
}
58+
59+
if (node.leaf) {
60+
return false;
61+
} else {
62+
return search(key, node.children[i]);
63+
}
64+
}
65+
66+
/**
67+
* Inserts a key into the B-tree.
68+
* @param key The key to insert.
69+
*/
70+
public void insert(int key) {
71+
if (this.root == null) {
72+
this.root = new BTreeNode(this.t, true);
73+
this.root.keys[0] = key;
74+
this.root.keyCount = 1;
75+
} else {
76+
if (this.root.keyCount == 2 * t - 1) { // root is full
77+
BTreeNode newRoot = new BTreeNode(this.t, false);
78+
newRoot.children[0] = this.root;
79+
splitChild(newRoot, 0);
80+
insertNonFull(newRoot, key);
81+
this.root = newRoot;
82+
} else {
83+
insertNonFull(root, key);
84+
}
85+
}
86+
}
87+
88+
/**
89+
* Splits a child node of the current node during insertion, promoting the middle key to the parent node.
90+
* This method is called when the child node is full and needs to be split to maintain B-tree properties.
91+
* @param x The parent node.
92+
* @param i The index of the child node to split.
93+
*/
94+
private void splitChild(BTreeNode x, int i) {
95+
BTreeNode y = x.children[i];
96+
BTreeNode z = new BTreeNode(t, y.leaf);
97+
98+
for (int j = x.keyCount; j >= i; j--) {
99+
x.children[j + 1] = x.children[j];
100+
}
101+
102+
x.children[i + 1] = z;
103+
x.keys[i] = y.keys[t - 1]; // promote middle key to parent
104+
x.keyCount++;
105+
106+
for (int j = 0; j < t - 1; j++) {
107+
z.keys[j] = y.keys[j + t];
108+
}
109+
110+
y.keyCount = t - 1;
111+
z.keyCount = t - 1;
112+
}
113+
114+
/**
115+
* Inserts a key into a non-full B-tree node. If the node is full, it recursively splits the necessary child nodes.
116+
* @param x The current B-tree node.
117+
* @param key The key to insert.
118+
*/
119+
private void insertNonFull(BTreeNode x, int key) {
120+
int i = x.keyCount - 1;
121+
122+
if (x.leaf) {
123+
while (i >= 0 && key < x.keys[i]) {
124+
x.keys[i + 1] = x.keys[i];
125+
i--;
126+
}
127+
x.keys[i + 1] = key;
128+
x.keyCount++;
129+
} else {
130+
while (i >= 0 && key < x.keys[i]) {
131+
i--;
132+
}
133+
i++;
134+
135+
if (x.children[i].keyCount == 2 * t - 1) {
136+
splitChild(x, i);
137+
if (key > x.keys[i]) {
138+
i++;
139+
}
140+
}
141+
142+
insertNonFull(x.children[i], key);
143+
}
144+
}
145+
146+
/**
147+
* Prints the keys of the B-tree level by level.
148+
*/
149+
public void printBTree() {
150+
if (root != null) {
151+
printBTree(root, 0);
152+
}
153+
}
154+
155+
private void printBTree(BTreeNode node, int level) {
156+
System.out.println("Level " + level + ": " + node.keyCount + " keys");
157+
158+
for (int i = 0; i < node.keyCount; i++) {
159+
System.out.print(node.keys[i] + " ");
160+
}
161+
System.out.println();
162+
163+
if (!node.leaf) {
164+
for (int i = 0; i <= node.keyCount; i++) {
165+
if (node.children[i] != null) {
166+
printBTree(node.children[i], level + 1);
167+
}
168+
}
169+
}
170+
}
171+
172+
/**
173+
* Deletes the specified key from the B-tree.
174+
* @param key key to be deleted
175+
*/
176+
public void delete(int key) {
177+
deleteRecursive(this.root, key);
178+
}
179+
private void deleteRecursive(BTreeNode x, int key) {
180+
int i = 0;
181+
182+
while (i < x.keyCount && key > x.keys[i]){
183+
i += 1;
184+
}
185+
186+
if (i < x.keyCount && key == x.keys[i]) {
187+
if (x.leaf) {
188+
for (int curr = i; curr < x.keyCount; curr++) {
189+
x.keys[curr] = x.keys[curr + 1];
190+
}
191+
x.keyCount -= 1;
192+
} else {
193+
BTreeNode y = x.children[i];
194+
BTreeNode z = x.children[i + 1];
195+
if (y.keyCount >= this.t) {
196+
int predecessor = getPredecessor(y);
197+
x.keys[i] = predecessor;
198+
deleteRecursive(y, predecessor);
199+
} else if (z.keyCount >= this.t) {
200+
int successor = getSuccessor(z);
201+
x.keys[i] = successor;
202+
deleteRecursive(z, successor);
203+
} else {
204+
mergeNodes(x, i, y, z);
205+
deleteRecursive(y, key);
206+
}
207+
}
208+
} else {
209+
if (x.leaf) {
210+
System.out.println("Key " + key + " does not exist in the B-tree.");
211+
} else {
212+
if (x.children[i].keyCount < this.t) {
213+
fixChild(x, i);
214+
}
215+
deleteRecursive(x.children[i], key);
216+
}
217+
}
218+
}
219+
220+
/**
221+
* Retrieves the predecessor key of the given B-tree node.
222+
* @param x The B-tree node.
223+
* @return The predecessor key.
224+
*/
225+
private int getPredecessor(BTreeNode x) {
226+
while (!x.leaf) {
227+
x = x.children[x.keyCount - 1];
228+
}
229+
return x.keys[x.keyCount - 1];
230+
}
231+
232+
/**
233+
* Retrieves the successor key of the given B-tree node.
234+
* @param x The B-tree node.
235+
* @return The successor key.
236+
*/
237+
public int getSuccessor(BTreeNode x) {
238+
while (!x.leaf) {
239+
x = x.children[0];
240+
}
241+
return x.keys[0];
242+
}
243+
244+
/**
245+
* Merges two child nodes of a parent node in a B-tree.
246+
* This method is called when a child node has fewer keys than required.
247+
*
248+
* @param x The parent node.
249+
* @param i The index of the child node to be merged with its right sibling.
250+
* @param y The left child node to be merged.
251+
* @param z The right child node to be merged.
252+
*/
253+
private void mergeNodes(BTreeNode x, int i, BTreeNode y, BTreeNode z) {
254+
// Append the key from the current node to the left child
255+
y.keys[y.keyCount] = x.keys[i];
256+
y.keyCount++;
257+
258+
// Copy the keys and children of the right child to the left child
259+
System.arraycopy(z.keys, 0, y.keys, y.keyCount, z.keyCount);
260+
System.arraycopy(z.children, 0, y.children, y.keyCount, z.keyCount);
261+
262+
// Adjust the key count of the left child
263+
y.keyCount += z.keyCount;
264+
265+
// Remove the key and child reference from the current node
266+
for (int j = i; j < x.keyCount - 1; j++) {
267+
x.keys[j] = x.keys[j + 1];
268+
x.children[j + 1] = x.children[j + 2];
269+
}
270+
271+
// Decrement the key count of the current node
272+
x.keyCount--;
273+
274+
if (x.keyCount == 0) {
275+
this.root = y;
276+
}
277+
}
278+
279+
/**
280+
* Fixes a child node of a parent node by borrowing keys or merging with siblings.
281+
*
282+
* @param x The parent node.
283+
* @param i The index of the child node to be fixed.
284+
*/
285+
private void fixChild(BTreeNode x, int i) {
286+
if (i > 0 && x.children[i - 1].keyCount >= t) {
287+
borrowFromLeft(x, i);
288+
} else if (i < x.children.length - 1 && x.children[i + 1].keyCount >= t) {
289+
borrowFromRight(x, i);
290+
} else {
291+
if (i > 0) {
292+
mergeNodes(x, i - 1, x.children[i - 1], x.children[i]);
293+
i--; // Adjust i after merging
294+
} else {
295+
mergeNodes(x, i, x.children[i], x.children[i + 1]);
296+
}
297+
}
298+
}
299+
300+
/**
301+
* Borrows a key from the left sibling of a child node in a B-tree.
302+
*
303+
* @param x The parent node.
304+
* @param i The index of the child node.
305+
*/
306+
private void borrowFromLeft(BTreeNode x, int i) {
307+
BTreeNode child = x.children[i];
308+
BTreeNode sibling = x.children[i - 1];
309+
310+
// Move key from parent to the beginning of the child
311+
for (int j = child.keyCount; j > 0; j--) {
312+
child.keys[j] = child.keys[j - 1];
313+
}
314+
child.keys[0] = x.keys[i - 1];
315+
316+
// Update parent key with the last key from the sibling
317+
x.keys[i - 1] = sibling.keys[sibling.keyCount - 1];
318+
319+
// If not a leaf, move child reference from the sibling to the child
320+
if (!child.leaf) {
321+
for (int j = child.keyCount + 1; j > 0; j--) {
322+
child.children[j] = child.children[j - 1];
323+
}
324+
child.children[0] = sibling.children[sibling.keyCount];
325+
}
326+
327+
child.keyCount++;
328+
sibling.keyCount--;
329+
}
330+
331+
/**
332+
* Borrows a key from the right sibling of a child node in a B-tree.
333+
*
334+
* @param x The parent node.
335+
* @param i The index of the child node.
336+
*/
337+
private void borrowFromRight(BTreeNode x, int i) {
338+
BTreeNode child = x.children[i];
339+
BTreeNode sibling = x.children[i + 1];
340+
341+
// Move key from parent to the end of the child
342+
child.keys[child.keyCount] = x.keys[i];
343+
344+
// Update parent key with the first key from the sibling
345+
x.keys[i] = sibling.keys[0];
346+
347+
// If not a leaf, move child reference from the sibling to the child
348+
if (!child.leaf) {
349+
child.children[child.keyCount + 1] = sibling.children[0];
350+
351+
// Shift keys and children in the sibling
352+
for (int j = 0; j < sibling.keyCount - 1; j++) {
353+
sibling.keys[j] = sibling.keys[j + 1];
354+
sibling.children[j] = sibling.children[j + 1];
355+
}
356+
sibling.children[sibling.keyCount - 1] = sibling.children[sibling.keyCount];
357+
}
358+
359+
child.keyCount++;
360+
sibling.keyCount--;
361+
}
362+
363+
}

src/main/java/dataStructures/bTree/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,17 @@ Search:
7373

7474
where n is the number of elements (whatever the structure, it must store at least n nodes)
7575

76+
## How do B Trees relate to (a,b) trees?
77+
A B-Tree is an (a,b) tree with a = ceil(b/2).
78+
79+
There are varying definitions of B-trees but we will be following the CLRS definition: a B tree is parameterized by
80+
a value t >= 2, known as its minimum degree.
81+
- Every internal node other than the root has at least t children.
82+
- Following this definition, t = a in the naming convention of (a,b) trees.
83+
84+
## Split Child Method
85+
![split child](../../../../../docs/assets/images/btreesplitchild.jpeg)
86+
Image Source: https://www.geeksforgeeks.org/insert-operation-in-b-tree/
87+
7688
## References
7789
This description heavily references CS2040S Recitation Sheet 4.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)