btrees hf18
This commit is contained in:
parent
bee1b816cd
commit
f29428f591
|
@ -0,0 +1,78 @@
|
||||||
|
package be.brainbaking.datastructures.trees;
|
||||||
|
|
||||||
|
public class BTree {
|
||||||
|
|
||||||
|
private Node root;
|
||||||
|
private final int t;
|
||||||
|
|
||||||
|
public BTree(int t) {
|
||||||
|
this.root = Node.createRoot();
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getRoot() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String key) {
|
||||||
|
Node node = root;
|
||||||
|
|
||||||
|
if(node.isFull(t)) {
|
||||||
|
NodeSplitResult splitResult = node.split(t);
|
||||||
|
Node newNode = Node.createFromSplitResult(node, splitResult);
|
||||||
|
|
||||||
|
root = newNode;
|
||||||
|
insertNonFull(newNode, key);
|
||||||
|
} else {
|
||||||
|
insertNonFull(node, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertNonFull(Node node, String key) {
|
||||||
|
if(node.isLeaf()) {
|
||||||
|
node.addKey(key);
|
||||||
|
} else {
|
||||||
|
int i = findRightChildIndexToSearchThrough(node, key);
|
||||||
|
Node nodeToSeekThrough = node.getChildren().get(i - 1);
|
||||||
|
|
||||||
|
if(nodeToSeekThrough.isFull(t)) {
|
||||||
|
NodeSplitResult splitResult = nodeToSeekThrough.split(t);
|
||||||
|
node.addChild(i - 1, splitResult.getNewNode());
|
||||||
|
node.addKey(splitResult.getSplitKey());
|
||||||
|
|
||||||
|
if(splitResult.getSplitKey().compareTo(key) > 0) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insertNonFull(node.getChildren().get(i - 1), key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findRightChildIndexToSearchThrough(Node node, String key) {
|
||||||
|
int i = node.getNumberOfKeys();
|
||||||
|
while(i >= 1 && key.compareTo(node.getKeys().get(i - 1)) < 0) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BTreeSearchResult search(String key) {
|
||||||
|
return searchInNode(root, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BTreeSearchResult searchInNode(Node node, String key) {
|
||||||
|
int i = 0;
|
||||||
|
while(i < node.getNumberOfKeys() && key.compareTo(node.getKeys().get(i)) > 0) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if(i < node.getNumberOfKeys() && key == node.getKeys().get(i)) {
|
||||||
|
return new BTreeSearchResult(node, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node.isLeaf()) return new BTreeSearchResult();
|
||||||
|
return searchInNode(node.getChildren().get(i), key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package be.brainbaking.datastructures.trees;
|
||||||
|
|
||||||
|
public class BTreeSearchResult {
|
||||||
|
|
||||||
|
private final Node node;
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
public BTreeSearchResult() {
|
||||||
|
node = null;
|
||||||
|
index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFound() {
|
||||||
|
return node != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BTreeSearchResult(Node node, int index) {
|
||||||
|
this.node = node;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package be.brainbaking.datastructures.trees;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each node has the following fields:
|
||||||
|
* n[x], the number of keys
|
||||||
|
* the n[x] keys themselves, stored in nondecreasing order key1[x] <= key2[x] <= ... <= keyn[x]
|
||||||
|
* leaf[x], a boolean value that is TRUE if x is a leaf and FALSE if x is an internal node
|
||||||
|
*
|
||||||
|
* each internal node x also contains n[x] + 1 pointers cn[x] to its children.
|
||||||
|
* The keys keyi[x] separate the ranges of keys stored in each subtree:
|
||||||
|
* if ki is any key stored in the subtree with root ci[x], then
|
||||||
|
* k1 <= key1[x] <= k2 <= key2[x]
|
||||||
|
*
|
||||||
|
* All leaves have the same depth, which is the tree's height h.
|
||||||
|
* There are lower and upper bounds on the number of keys a node can contain. (fixed int, "t" >= 2)
|
||||||
|
* min. t - 1 keys
|
||||||
|
* max. 2t - 1 keys: "full"
|
||||||
|
*/
|
||||||
|
public class Node {
|
||||||
|
|
||||||
|
private boolean leaf;
|
||||||
|
private List<String> keys;
|
||||||
|
private List<Node> children;
|
||||||
|
|
||||||
|
public boolean isFull(int t) {
|
||||||
|
return getNumberOfKeys() >= 2* t - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node(boolean leaf) {
|
||||||
|
keys = new ArrayList<>();
|
||||||
|
children = new ArrayList<>();
|
||||||
|
this.leaf = leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addKey(String key) {
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < keys.size(); i++) {
|
||||||
|
if(keys.get(i).compareTo(key) >= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.add(i, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChild(Node node) {
|
||||||
|
addChild(children.size(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChild(int index, Node node) {
|
||||||
|
// dit lijkt mij nog een performance hit te zijn die niet in default in BTrees zit
|
||||||
|
// ik wou het object-oriented aanpakken maar moet hier dan nog max O(index) aflopen.
|
||||||
|
children.add(index, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeSplitResult split(int t) {
|
||||||
|
if(!isFull(t)) throw new UnsupportedOperationException("node niet vol, split zelf maar wa jong");
|
||||||
|
|
||||||
|
Node newNode = new Node(isLeaf());
|
||||||
|
|
||||||
|
for(int i = 0; i < t - 1; i++) {
|
||||||
|
newNode.addKey(keys.get(i + t));
|
||||||
|
}
|
||||||
|
// ?? Aantal sleutels en aantal children kan niet gelijk zijn?
|
||||||
|
if(!isLeaf()) {
|
||||||
|
for(int i = 0; i < t; i++) {
|
||||||
|
newNode.children.add(children.get(i + t - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> newKeys = new ArrayList<>();
|
||||||
|
for(int i = 0; i < t - 1; i++) {
|
||||||
|
newKeys.add(keys.get(i));
|
||||||
|
}
|
||||||
|
String key = keys.get(t - 1);
|
||||||
|
keys = newKeys;
|
||||||
|
|
||||||
|
if(!isLeaf()) {
|
||||||
|
List<Node> newChildren = new ArrayList<>();
|
||||||
|
for(int i = 0; i < t; i++) {
|
||||||
|
newChildren.add(children.get(i));
|
||||||
|
}
|
||||||
|
children = newChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new NodeSplitResult(newNode, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Node createRoot() {
|
||||||
|
return new Node(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumberOfKeys() {
|
||||||
|
return keys.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Node> getChildren() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getKeys() {
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeaf() {
|
||||||
|
return leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Node createFromSplitResult(Node node, NodeSplitResult splitResult) {
|
||||||
|
Node root = new Node(false);
|
||||||
|
|
||||||
|
root.addKey(splitResult.getSplitKey());
|
||||||
|
root.addChild(node);
|
||||||
|
root.addChild(splitResult.getNewNode());
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package be.brainbaking.datastructures.trees;
|
||||||
|
|
||||||
|
public class NodeSplitResult {
|
||||||
|
|
||||||
|
private final Node newNode;
|
||||||
|
private final String splitKey;
|
||||||
|
|
||||||
|
public Node getNewNode() {
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSplitKey() {
|
||||||
|
return splitKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeSplitResult(Node newNode, String splitKey) {
|
||||||
|
this.newNode = newNode;
|
||||||
|
this.splitKey = splitKey;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package be.brainbaking.datastructures.trees;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class BTreeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searching_afterAddingExampleAndHavingToSplit() {
|
||||||
|
BTree tree = new BTree(4);
|
||||||
|
|
||||||
|
tree.add("A");
|
||||||
|
tree.add("D");
|
||||||
|
tree.add("F");
|
||||||
|
tree.add("H");
|
||||||
|
tree.add("L");
|
||||||
|
tree.add("N");
|
||||||
|
tree.add("P");
|
||||||
|
tree.add("Q");
|
||||||
|
|
||||||
|
assertEquals(1, tree.search("D").getIndex());
|
||||||
|
assertEquals(0, tree.search("H").getIndex()); // want die key is naar boven verhuisd bij splitten
|
||||||
|
assertEquals(2, tree.search("P").getIndex());
|
||||||
|
assertEquals(3, tree.search("Q").getIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addingOnly() {
|
||||||
|
BTree tree = new BTree(4);
|
||||||
|
|
||||||
|
tree.add("A");
|
||||||
|
tree.add("D");
|
||||||
|
tree.add("F");
|
||||||
|
tree.add("H");
|
||||||
|
tree.add("L");
|
||||||
|
tree.add("N");
|
||||||
|
tree.add("P");
|
||||||
|
|
||||||
|
// nog eentje toevoegen zou moeten splitsen (4*2 groot)
|
||||||
|
tree.add("Q");
|
||||||
|
|
||||||
|
assertArrayEquals(Arrays.asList("H").toArray(), tree.getRoot().getKeys().toArray());
|
||||||
|
assertArrayEquals(Arrays.asList("A", "D", "F").toArray(), tree.getRoot().getChildren().get(0).getKeys().toArray());
|
||||||
|
assertArrayEquals(Arrays.asList("L", "N", "P", "Q").toArray(), tree.getRoot().getChildren().get(1).getKeys().toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleOperations_addAndSearch() {
|
||||||
|
BTree tree = new BTree(5);
|
||||||
|
tree.add("key1");
|
||||||
|
|
||||||
|
BTreeSearchResult result = tree.search("key1");
|
||||||
|
assertEquals(true, result.isFound());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleOperations_searchKeyNotFound() {
|
||||||
|
BTree tree = new BTree(5);
|
||||||
|
tree.add("key100");
|
||||||
|
|
||||||
|
BTreeSearchResult result = tree.search("key1");
|
||||||
|
assertEquals(false, result.isFound());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package be.brainbaking.datastructures.trees;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
|
||||||
|
public class NodeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createFromSplitResult_nodeALeftAndBRight() {
|
||||||
|
Node root = new Node(false);
|
||||||
|
root.addKey("R");
|
||||||
|
|
||||||
|
Node node = new Node(true);
|
||||||
|
node.addKey("A");
|
||||||
|
|
||||||
|
NodeSplitResult result = new NodeSplitResult(node, "B");
|
||||||
|
Node newRoot = Node.createFromSplitResult(root, result);
|
||||||
|
|
||||||
|
assertSame(root, newRoot.getChildren().get(0));
|
||||||
|
assertSame(node, newRoot.getChildren().get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addKey_addsToCorrectPositionAutomatically() {
|
||||||
|
Node node = new Node(true);
|
||||||
|
node.addKey("A");
|
||||||
|
node.addKey("C");
|
||||||
|
node.addKey("B");
|
||||||
|
|
||||||
|
assertArrayEquals(Arrays.asList("A", "B", "C").toArray(), node.getKeys().toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void split() {
|
||||||
|
|
||||||
|
Node node = new Node(false);
|
||||||
|
Node child = new Node(true);
|
||||||
|
|
||||||
|
node.addChild(child);
|
||||||
|
node.addKey("N");
|
||||||
|
node.addKey("W");
|
||||||
|
|
||||||
|
child.addKey("P");
|
||||||
|
child.addChild(new Node(true));
|
||||||
|
child.addKey("Q");
|
||||||
|
child.addChild(new Node(true));
|
||||||
|
child.addKey("R");
|
||||||
|
child.addChild(new Node(true));
|
||||||
|
child.addKey("S");
|
||||||
|
child.addChild(new Node(true));
|
||||||
|
child.addKey("T");
|
||||||
|
child.addChild(new Node(true));
|
||||||
|
child.addKey("U");
|
||||||
|
child.addChild(new Node(true));
|
||||||
|
child.addKey("V");
|
||||||
|
child.addChild(new Node(true));
|
||||||
|
|
||||||
|
NodeSplitResult result = child.split(4);
|
||||||
|
|
||||||
|
assertEquals(3, result.getNewNode().getNumberOfKeys());
|
||||||
|
assertEquals(3, child.getNumberOfKeys());
|
||||||
|
assertEquals("S", result.getSplitKey());
|
||||||
|
assertArrayEquals(Arrays.asList("P", "Q", "R").toArray(), child.getKeys().toArray());
|
||||||
|
assertArrayEquals(Arrays.asList("T", "U", "V").toArray(), result.getNewNode().getKeys().toArray());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue