From f29428f59105e6c87a65373129d083eb6a6b2730 Mon Sep 17 00:00:00 2001 From: wgroeneveld Date: Tue, 3 Apr 2018 15:50:57 +0200 Subject: [PATCH] btrees hf18 --- .../datastructures/trees/BTree.java | 78 +++++++++++ .../trees/BTreeSearchResult.java | 30 +++++ .../datastructures/trees/Node.java | 122 ++++++++++++++++++ .../datastructures/trees/NodeSplitResult.java | 20 +++ .../datastructures/trees/BTreeTest.java | 69 ++++++++++ .../datastructures/trees/NodeTest.java | 71 ++++++++++ 6 files changed, 390 insertions(+) create mode 100644 datastructures/java/src/be/brainbaking/datastructures/trees/BTree.java create mode 100644 datastructures/java/src/be/brainbaking/datastructures/trees/BTreeSearchResult.java create mode 100644 datastructures/java/src/be/brainbaking/datastructures/trees/Node.java create mode 100644 datastructures/java/src/be/brainbaking/datastructures/trees/NodeSplitResult.java create mode 100644 datastructures/java/test/be/brainbaking/datastructures/trees/BTreeTest.java create mode 100644 datastructures/java/test/be/brainbaking/datastructures/trees/NodeTest.java diff --git a/datastructures/java/src/be/brainbaking/datastructures/trees/BTree.java b/datastructures/java/src/be/brainbaking/datastructures/trees/BTree.java new file mode 100644 index 0000000..db05cc9 --- /dev/null +++ b/datastructures/java/src/be/brainbaking/datastructures/trees/BTree.java @@ -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); + } +} diff --git a/datastructures/java/src/be/brainbaking/datastructures/trees/BTreeSearchResult.java b/datastructures/java/src/be/brainbaking/datastructures/trees/BTreeSearchResult.java new file mode 100644 index 0000000..bbd00eb --- /dev/null +++ b/datastructures/java/src/be/brainbaking/datastructures/trees/BTreeSearchResult.java @@ -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; + } + +} diff --git a/datastructures/java/src/be/brainbaking/datastructures/trees/Node.java b/datastructures/java/src/be/brainbaking/datastructures/trees/Node.java new file mode 100644 index 0000000..4b00296 --- /dev/null +++ b/datastructures/java/src/be/brainbaking/datastructures/trees/Node.java @@ -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 keys; + private List 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 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 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 getChildren() { + return children; + } + + public List 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; + } +} diff --git a/datastructures/java/src/be/brainbaking/datastructures/trees/NodeSplitResult.java b/datastructures/java/src/be/brainbaking/datastructures/trees/NodeSplitResult.java new file mode 100644 index 0000000..61b374f --- /dev/null +++ b/datastructures/java/src/be/brainbaking/datastructures/trees/NodeSplitResult.java @@ -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; + } +} diff --git a/datastructures/java/test/be/brainbaking/datastructures/trees/BTreeTest.java b/datastructures/java/test/be/brainbaking/datastructures/trees/BTreeTest.java new file mode 100644 index 0000000..ee88b8b --- /dev/null +++ b/datastructures/java/test/be/brainbaking/datastructures/trees/BTreeTest.java @@ -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()); + } + +} diff --git a/datastructures/java/test/be/brainbaking/datastructures/trees/NodeTest.java b/datastructures/java/test/be/brainbaking/datastructures/trees/NodeTest.java new file mode 100644 index 0000000..7fc866a --- /dev/null +++ b/datastructures/java/test/be/brainbaking/datastructures/trees/NodeTest.java @@ -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()); + } +}