mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-13 07:13:37 +08:00
Add BTree implementation (#6248)
This commit is contained in:
323
src/main/java/com/thealgorithms/datastructures/trees/BTree.java
Normal file
323
src/main/java/com/thealgorithms/datastructures/trees/BTree.java
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
package com.thealgorithms.datastructures.trees;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a B-Tree, a self-balancing tree data structure that maintains sorted data
|
||||||
|
* and allows searches, sequential access, insertions, and deletions in logarithmic time.
|
||||||
|
*
|
||||||
|
* B-Trees are generalizations of binary search trees in that a node can have more than two children.
|
||||||
|
* They're widely used in databases and file systems.
|
||||||
|
*
|
||||||
|
* For more information: https://en.wikipedia.org/wiki/B-tree
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class BTree {
|
||||||
|
static class BTreeNode {
|
||||||
|
int[] keys;
|
||||||
|
int t; // Minimum degree (defines range for number of keys)
|
||||||
|
BTreeNode[] children;
|
||||||
|
int n; // Current number of keys
|
||||||
|
boolean leaf;
|
||||||
|
|
||||||
|
BTreeNode(int t, boolean leaf) {
|
||||||
|
this.t = t;
|
||||||
|
this.leaf = leaf;
|
||||||
|
this.keys = new int[2 * t - 1];
|
||||||
|
this.children = new BTreeNode[2 * t];
|
||||||
|
this.n = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void traverse(ArrayList<Integer> result) {
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (!leaf) {
|
||||||
|
children[i].traverse(result);
|
||||||
|
}
|
||||||
|
result.add(keys[i]);
|
||||||
|
}
|
||||||
|
if (!leaf) {
|
||||||
|
children[n].traverse(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BTreeNode search(int key) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < n && key > keys[i]) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i < n && keys[i] == key) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (leaf) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return children[i].search(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertNonFull(int key) {
|
||||||
|
int i = n - 1;
|
||||||
|
if (leaf) {
|
||||||
|
while (i >= 0 && keys[i] > key) {
|
||||||
|
keys[i + 1] = keys[i];
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
keys[i + 1] = key;
|
||||||
|
n++;
|
||||||
|
} else {
|
||||||
|
while (i >= 0 && keys[i] > key) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
if (children[i + 1].n == 2 * t - 1) {
|
||||||
|
splitChild(i + 1, children[i + 1]);
|
||||||
|
if (keys[i + 1] < key) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
children[i + 1].insertNonFull(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void splitChild(int i, BTreeNode y) {
|
||||||
|
BTreeNode z = new BTreeNode(y.t, y.leaf);
|
||||||
|
z.n = t - 1;
|
||||||
|
|
||||||
|
System.arraycopy(y.keys, t, z.keys, 0, t - 1);
|
||||||
|
if (!y.leaf) {
|
||||||
|
System.arraycopy(y.children, t, z.children, 0, t);
|
||||||
|
}
|
||||||
|
y.n = t - 1;
|
||||||
|
|
||||||
|
for (int j = n; j >= i + 1; j--) {
|
||||||
|
children[j + 1] = children[j];
|
||||||
|
}
|
||||||
|
children[i + 1] = z;
|
||||||
|
|
||||||
|
for (int j = n - 1; j >= i; j--) {
|
||||||
|
keys[j + 1] = keys[j];
|
||||||
|
}
|
||||||
|
keys[i] = y.keys[t - 1];
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(int key) {
|
||||||
|
int idx = findKey(key);
|
||||||
|
|
||||||
|
if (idx < n && keys[idx] == key) {
|
||||||
|
if (leaf) {
|
||||||
|
removeFromLeaf(idx);
|
||||||
|
} else {
|
||||||
|
removeFromNonLeaf(idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (leaf) {
|
||||||
|
return; // Key not found
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean flag = idx == n;
|
||||||
|
if (children[idx].n < t) {
|
||||||
|
fill(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag && idx > n) {
|
||||||
|
children[idx - 1].remove(key);
|
||||||
|
} else {
|
||||||
|
children[idx].remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findKey(int key) {
|
||||||
|
int idx = 0;
|
||||||
|
while (idx < n && keys[idx] < key) {
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFromLeaf(int idx) {
|
||||||
|
for (int i = idx + 1; i < n; ++i) {
|
||||||
|
keys[i - 1] = keys[i];
|
||||||
|
}
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFromNonLeaf(int idx) {
|
||||||
|
int key = keys[idx];
|
||||||
|
if (children[idx].n >= t) {
|
||||||
|
int pred = getPredecessor(idx);
|
||||||
|
keys[idx] = pred;
|
||||||
|
children[idx].remove(pred);
|
||||||
|
} else if (children[idx + 1].n >= t) {
|
||||||
|
int succ = getSuccessor(idx);
|
||||||
|
keys[idx] = succ;
|
||||||
|
children[idx + 1].remove(succ);
|
||||||
|
} else {
|
||||||
|
merge(idx);
|
||||||
|
children[idx].remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPredecessor(int idx) {
|
||||||
|
BTreeNode cur = children[idx];
|
||||||
|
while (!cur.leaf) {
|
||||||
|
cur = cur.children[cur.n];
|
||||||
|
}
|
||||||
|
return cur.keys[cur.n - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSuccessor(int idx) {
|
||||||
|
BTreeNode cur = children[idx + 1];
|
||||||
|
while (!cur.leaf) {
|
||||||
|
cur = cur.children[0];
|
||||||
|
}
|
||||||
|
return cur.keys[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fill(int idx) {
|
||||||
|
if (idx != 0 && children[idx - 1].n >= t) {
|
||||||
|
borrowFromPrev(idx);
|
||||||
|
} else if (idx != n && children[idx + 1].n >= t) {
|
||||||
|
borrowFromNext(idx);
|
||||||
|
} else {
|
||||||
|
if (idx != n) {
|
||||||
|
merge(idx);
|
||||||
|
} else {
|
||||||
|
merge(idx - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void borrowFromPrev(int idx) {
|
||||||
|
BTreeNode child = children[idx];
|
||||||
|
BTreeNode sibling = children[idx - 1];
|
||||||
|
|
||||||
|
for (int i = child.n - 1; i >= 0; --i) {
|
||||||
|
child.keys[i + 1] = child.keys[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!child.leaf) {
|
||||||
|
for (int i = child.n; i >= 0; --i) {
|
||||||
|
child.children[i + 1] = child.children[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child.keys[0] = keys[idx - 1];
|
||||||
|
|
||||||
|
if (!child.leaf) {
|
||||||
|
child.children[0] = sibling.children[sibling.n];
|
||||||
|
}
|
||||||
|
|
||||||
|
keys[idx - 1] = sibling.keys[sibling.n - 1];
|
||||||
|
|
||||||
|
child.n += 1;
|
||||||
|
sibling.n -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void borrowFromNext(int idx) {
|
||||||
|
BTreeNode child = children[idx];
|
||||||
|
BTreeNode sibling = children[idx + 1];
|
||||||
|
|
||||||
|
child.keys[child.n] = keys[idx];
|
||||||
|
|
||||||
|
if (!child.leaf) {
|
||||||
|
child.children[child.n + 1] = sibling.children[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
keys[idx] = sibling.keys[0];
|
||||||
|
|
||||||
|
for (int i = 1; i < sibling.n; ++i) {
|
||||||
|
sibling.keys[i - 1] = sibling.keys[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sibling.leaf) {
|
||||||
|
for (int i = 1; i <= sibling.n; ++i) {
|
||||||
|
sibling.children[i - 1] = sibling.children[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child.n += 1;
|
||||||
|
sibling.n -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void merge(int idx) {
|
||||||
|
BTreeNode child = children[idx];
|
||||||
|
BTreeNode sibling = children[idx + 1];
|
||||||
|
|
||||||
|
child.keys[t - 1] = keys[idx];
|
||||||
|
|
||||||
|
for (int i = 0; i < sibling.n; ++i) {
|
||||||
|
child.keys[i + t] = sibling.keys[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!child.leaf) {
|
||||||
|
for (int i = 0; i <= sibling.n; ++i) {
|
||||||
|
child.children[i + t] = sibling.children[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = idx + 1; i < n; ++i) {
|
||||||
|
keys[i - 1] = keys[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = idx + 2; i <= n; ++i) {
|
||||||
|
children[i - 1] = children[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
child.n += sibling.n + 1;
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BTreeNode root;
|
||||||
|
private final int t;
|
||||||
|
|
||||||
|
public BTree(int t) {
|
||||||
|
this.root = null;
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void traverse(ArrayList<Integer> result) {
|
||||||
|
if (root != null) {
|
||||||
|
root.traverse(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean search(int key) {
|
||||||
|
return root != null && root.search(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insert(int key) {
|
||||||
|
if (search(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (root == null) {
|
||||||
|
root = new BTreeNode(t, true);
|
||||||
|
root.keys[0] = key;
|
||||||
|
root.n = 1;
|
||||||
|
} else {
|
||||||
|
if (root.n == 2 * t - 1) {
|
||||||
|
BTreeNode s = new BTreeNode(t, false);
|
||||||
|
s.children[0] = root;
|
||||||
|
s.splitChild(0, root);
|
||||||
|
int i = 0;
|
||||||
|
if (s.keys[0] < key) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
s.children[i].insertNonFull(key);
|
||||||
|
root = s;
|
||||||
|
} else {
|
||||||
|
root.insertNonFull(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(int key) {
|
||||||
|
if (root == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.remove(key);
|
||||||
|
if (root.n == 0) {
|
||||||
|
root = root.leaf ? null : root.children[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package com.thealgorithms.datastructures.trees;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class BTreeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertSearchDelete() {
|
||||||
|
BTree bTree = new BTree(3); // Minimum degree t = 3
|
||||||
|
|
||||||
|
int[] values = {10, 20, 5, 6, 12, 30, 7, 17};
|
||||||
|
for (int val : values) {
|
||||||
|
bTree.insert(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int val : values) {
|
||||||
|
assertTrue(bTree.search(val), "Should find inserted value: " + val);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Integer> traversal = new ArrayList<>();
|
||||||
|
bTree.traverse(traversal);
|
||||||
|
assertEquals(Arrays.asList(5, 6, 7, 10, 12, 17, 20, 30), traversal);
|
||||||
|
|
||||||
|
bTree.delete(6);
|
||||||
|
assertFalse(bTree.search(6));
|
||||||
|
traversal.clear();
|
||||||
|
bTree.traverse(traversal);
|
||||||
|
assertEquals(Arrays.asList(5, 7, 10, 12, 17, 20, 30), traversal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyTreeSearch() {
|
||||||
|
BTree bTree = new BTree(3);
|
||||||
|
assertFalse(bTree.search(42), "Search in empty tree should return false.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDuplicateInsertions() {
|
||||||
|
BTree bTree = new BTree(3);
|
||||||
|
bTree.insert(15);
|
||||||
|
bTree.insert(15); // Attempt duplicate
|
||||||
|
bTree.insert(15); // Another duplicate
|
||||||
|
|
||||||
|
ArrayList<Integer> traversal = new ArrayList<>();
|
||||||
|
bTree.traverse(traversal);
|
||||||
|
|
||||||
|
// Should contain only one 15
|
||||||
|
long count = traversal.stream().filter(x -> x == 15).count();
|
||||||
|
assertEquals(1, count, "Duplicate keys should not be inserted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteNonExistentKey() {
|
||||||
|
BTree bTree = new BTree(3);
|
||||||
|
bTree.insert(10);
|
||||||
|
bTree.insert(20);
|
||||||
|
bTree.delete(99); // Doesn't exist
|
||||||
|
assertTrue(bTree.search(10));
|
||||||
|
assertTrue(bTree.search(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComplexInsertDelete() {
|
||||||
|
BTree bTree = new BTree(2); // Smaller degree to trigger splits more easily
|
||||||
|
int[] values = {1, 3, 7, 10, 11, 13, 14, 15, 18, 16, 19, 24, 25, 26, 21, 4, 5, 20, 22, 2, 17, 12, 6};
|
||||||
|
|
||||||
|
for (int val : values) {
|
||||||
|
bTree.insert(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int val : values) {
|
||||||
|
assertTrue(bTree.search(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] toDelete = {6, 13, 7, 4, 2, 16};
|
||||||
|
for (int val : toDelete) {
|
||||||
|
bTree.delete(val);
|
||||||
|
assertFalse(bTree.search(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Integer> remaining = new ArrayList<>();
|
||||||
|
bTree.traverse(remaining);
|
||||||
|
|
||||||
|
for (int val : toDelete) {
|
||||||
|
assertFalse(remaining.contains(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user