Compare commits
5 Commits
afee59b093
...
d30a8c82da
Author | SHA1 | Date |
---|---|---|
Wouter Groeneveld | d30a8c82da | |
Wouter Groeneveld | 959e23575d | |
wgroeneveld | 01368089b1 | |
wgroeneveld | f29428f591 | |
wgroeneveld | bee1b816cd |
|
@ -0,0 +1,179 @@
|
|||
package be.brainbaking.datastructures.trees;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class BTree {
|
||||
|
||||
private Node root;
|
||||
private final int t;
|
||||
|
||||
public BTree(Node node, int t) {
|
||||
this.root = node;
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
public BTree(int t) {
|
||||
this(Node.createRoot(), 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* case 1: if x is a leaf and x has >= t keys, just delete it.
|
||||
* case 2: if x is an internal node
|
||||
* a) if x's left child has >= t keys, move the largest key to the key to delete.
|
||||
* b) if x's right child has >= t keys, move the smallest key to the key to delete.
|
||||
* c) if none of the children have >= t keys, merge the children and delete the key.
|
||||
* case 3: if x is a leaf and x has == t - 1 keys, then
|
||||
* a) if x has a sibling with at least t keys
|
||||
* - move parent's key -> x
|
||||
* - find extreme(key) sibling with at least t keys (left/right), move x's key -> parent
|
||||
* - then proceed as case 1
|
||||
* b) if x's sibling also has t - 1 keys
|
||||
* - merge x with sibling: move parent's key -> x (as t key)
|
||||
* - then delete that key
|
||||
* @param key
|
||||
*/
|
||||
public void delete(String key) {
|
||||
BTreeSearchResult searchResult = search(key);
|
||||
if(!searchResult.isFound()) return;
|
||||
Node node = searchResult.getNode();
|
||||
Node parent = searchResult.getParent();
|
||||
|
||||
if(node.isLeaf() && node.getNumberOfKeys() >= t) { // case 1
|
||||
node.deleteKey(key);
|
||||
} else if(node.isLeaf() && node.getNumberOfKeys() == t - 1) { // case 3
|
||||
List<Node> siblings = parent.getSiblingsOf(node);
|
||||
|
||||
Optional<Node> maybeSibling = getSiblingWithTNumberOfKeys(siblings);
|
||||
if(maybeSibling.isPresent()) {
|
||||
// 3.a
|
||||
String parentKeyToPushDown = parent.getKeyBetweenChildren(node, maybeSibling.get());
|
||||
String extremeKeyToPushUp = maybeSibling.get().getExtremeKeyComparedTo(parentKeyToPushDown);
|
||||
|
||||
pushKeyDown(node, parent, parentKeyToPushDown);
|
||||
pullKeyUp(parent, maybeSibling, extremeKeyToPushUp);
|
||||
|
||||
node.deleteKey(key);
|
||||
} else if(siblings.stream().allMatch(s -> s.getNumberOfKeys() == t - 1)) {
|
||||
// 3.b
|
||||
Node rightMostSibling = siblings.stream().filter(s -> s.getNumberOfKeys() == t - 1).reduce((one, two) -> two).get();
|
||||
|
||||
node.mergeWith(rightMostSibling);
|
||||
String parentKeyToPushDown = parent.getKeyBetweenChildren(node, rightMostSibling);
|
||||
|
||||
parent.getChildren().remove(rightMostSibling);
|
||||
pushKeyDown(node, parent, parentKeyToPushDown);
|
||||
|
||||
node.deleteKey(key);
|
||||
}
|
||||
} else if(!node.isLeaf()) { // case 2
|
||||
List<Node> children = node.getChildenBetweenKey(key);
|
||||
Node left = children.get(0);
|
||||
Node right = children.get(1);
|
||||
|
||||
if(left.getNumberOfKeys() >= t) {
|
||||
// 2.a
|
||||
switchKeys(node, key, left, left.getLastKey());
|
||||
left.deleteKey(key);
|
||||
} else if(right.getNumberOfKeys() >= t) {
|
||||
// 2.b
|
||||
switchKeys(node, key, right, right.getFirstKey());
|
||||
right.deleteKey(key);
|
||||
} else {
|
||||
// 2.c
|
||||
left.mergeWith(right);
|
||||
node.getChildren().remove(right);
|
||||
|
||||
node.deleteKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void switchKeys(Node node, String key, Node nodeToSwitch, String keyToSwith) {
|
||||
node.getKeys().remove(key);
|
||||
node.addKey(keyToSwith);
|
||||
nodeToSwitch.getKeys().remove(keyToSwith);
|
||||
nodeToSwitch.addKey(key);
|
||||
}
|
||||
|
||||
private void pullKeyUp(Node parent, Optional<Node> maybeSibling, String extremeKeyToPushUp) {
|
||||
maybeSibling.get().getKeys().remove(extremeKeyToPushUp);
|
||||
parent.addKey(extremeKeyToPushUp);
|
||||
}
|
||||
|
||||
private void pushKeyDown(Node node, Node parent, String parentKeyToPushDown) {
|
||||
parent.getKeys().remove(parentKeyToPushDown);
|
||||
node.addKey(parentKeyToPushDown);
|
||||
}
|
||||
|
||||
|
||||
private Optional<Node> getSiblingWithTNumberOfKeys(List<Node> siblings) {
|
||||
return siblings.stream().filter(s -> s.getNumberOfKeys() == t).findFirst();
|
||||
}
|
||||
|
||||
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, null);
|
||||
}
|
||||
|
||||
private BTreeSearchResult searchInNode(Node node, String key, Node parent) {
|
||||
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, parent);
|
||||
}
|
||||
|
||||
if(node.isLeaf()) return new BTreeSearchResult();
|
||||
return searchInNode(node.getChildren().get(i), key, node);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package be.brainbaking.datastructures.trees;
|
||||
|
||||
public class BTreeSearchResult {
|
||||
|
||||
private final Node parent;
|
||||
private final Node node;
|
||||
private final int index;
|
||||
|
||||
public BTreeSearchResult() {
|
||||
this(null, -1, null);
|
||||
}
|
||||
|
||||
public boolean isFound() {
|
||||
return node != null;
|
||||
}
|
||||
|
||||
public Node getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public BTreeSearchResult(Node node, int index, Node parent) {
|
||||
this.node = node;
|
||||
this.parent = parent;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package be.brainbaking.datastructures.trees;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Voegt key gesorteerd toe.
|
||||
* Dit heb ik enkel voor bepaalde gevallen nodig, maar gebruik ik hier gegeneraliseerd.
|
||||
* Betekent een performance hit van worst-case O(n[x]) (bvb switchkeys) -> uit context af te leiden waar
|
||||
* @param key
|
||||
*/
|
||||
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 List<Node> getChildenBetweenKey(String key) {
|
||||
int keyIndex = keys.indexOf(key);
|
||||
return Arrays.asList(children.get(keyIndex), children.get(keyIndex + 1)); // kan volgens specs niet crashen, zie def. BTree props
|
||||
}
|
||||
|
||||
public String getKeyBetweenChildren(Node child1, Node child2) {
|
||||
return keys.get(Math.max(children.indexOf(child1), children.indexOf(child2)) - 1);
|
||||
}
|
||||
|
||||
public String getExtremeKeyComparedTo(String parentKey) {
|
||||
return parentKey.compareTo(keys.get(0)) < 0 ? keys.get(0) : keys.get(keys.size() - 1);
|
||||
}
|
||||
|
||||
public List<Node> getSiblingsOf(Node child) {
|
||||
List<Node> siblings = new ArrayList<>();
|
||||
int index = children.indexOf(child);
|
||||
if(index > 0) {
|
||||
siblings.add(children.get(index - 1));
|
||||
}
|
||||
if(index < children.size() - 1) {
|
||||
siblings.add(children.get(index + 1));
|
||||
}
|
||||
|
||||
return siblings;
|
||||
}
|
||||
|
||||
public void mergeWith(Node node) {
|
||||
if(node.getKeys().get(0).compareTo(getKeys().get(0)) >= 0) {
|
||||
rightSideMergeWith(node);
|
||||
} else {
|
||||
leftSideMergeWith(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void leftSideMergeWith(Node node) {
|
||||
keys = Stream.concat(node.keys.stream(), keys.stream()).collect(Collectors.toList());
|
||||
children = Stream.concat(node.children.stream(), children.stream()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void rightSideMergeWith(Node node) {
|
||||
keys = Stream.concat(keys.stream(), node.keys.stream()).collect(Collectors.toList());
|
||||
children = Stream.concat(children.stream(), node.children.stream()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
// n[x] = aantal sleutels = size(c[x]) - 1; er zijn altijd n[x] + 1 kinderen
|
||||
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 String getLastKey() {
|
||||
return keys.get(getNumberOfKeys() - 1);
|
||||
}
|
||||
|
||||
public String getFirstKey() {
|
||||
return keys.get(0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void deleteKey(String key) {
|
||||
keys.remove(key);
|
||||
}
|
||||
}
|
|
@ -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,260 @@
|
|||
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;
|
||||
|
||||
// deletion examples: https://www.youtube.com/watch?v=fKubKYzwDl0 - cases omgekeerde van p451
|
||||
public class BTreeTest {
|
||||
|
||||
@Test
|
||||
public void deleteCase1() {
|
||||
Node root = new Node(false);
|
||||
root.addKey("D");
|
||||
root.addKey("G");
|
||||
|
||||
Node child1 = new Node(true);
|
||||
child1.addKey("A");
|
||||
child1.addKey("B");
|
||||
child1.addKey("C");
|
||||
|
||||
Node child2 = new Node(true);
|
||||
child2.addKey("E");
|
||||
child2.addKey("F");
|
||||
|
||||
Node child3 = new Node(true);
|
||||
child3.addKey("H");
|
||||
child3.addKey("I");
|
||||
|
||||
root.addChild(child1);
|
||||
root.addChild(child2);
|
||||
root.addChild(child3);
|
||||
|
||||
BTree tree = new BTree(root, 3);
|
||||
tree.delete("B");
|
||||
|
||||
assertArrayEquals(Arrays.asList("A", "C").toArray(), tree.getRoot().getChildren().get(0).getKeys().toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteCase2A() {
|
||||
Node root = new Node(false);
|
||||
root.addKey("Q");
|
||||
root.addKey("U");
|
||||
|
||||
Node child1 = new Node(true);
|
||||
child1.addKey("O");
|
||||
child1.addKey("P");
|
||||
|
||||
Node child2 = new Node(true);
|
||||
child2.addKey("R");
|
||||
child2.addKey("S");
|
||||
child2.addKey("T");
|
||||
|
||||
Node child3 = new Node(true);
|
||||
child3.addKey("W");
|
||||
child3.addKey("X");
|
||||
|
||||
root.addChild(child1);
|
||||
root.addChild(child2);
|
||||
root.addChild(child3);
|
||||
|
||||
BTree tree = new BTree(root, 3);
|
||||
tree.delete("U");
|
||||
|
||||
assertArrayEquals(Arrays.asList("Q", "T").toArray(), tree.getRoot().getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("O", "P").toArray(), tree.getRoot().getChildren().get(0).getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("R", "S").toArray(), tree.getRoot().getChildren().get(1).getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("W", "X").toArray(), tree.getRoot().getChildren().get(2).getKeys().toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteCase2B() {
|
||||
Node root = new Node(false);
|
||||
root.addKey("I");
|
||||
root.addKey("M");
|
||||
|
||||
Node child1 = new Node(true);
|
||||
child1.addKey("G");
|
||||
child1.addKey("H");
|
||||
|
||||
Node child2 = new Node(true);
|
||||
child2.addKey("J");
|
||||
child2.addKey("K");
|
||||
child2.addKey("L");
|
||||
|
||||
Node child3 = new Node(true);
|
||||
child3.addKey("O");
|
||||
child3.addKey("P");
|
||||
|
||||
root.addChild(child1);
|
||||
root.addChild(child2);
|
||||
root.addChild(child3);
|
||||
|
||||
BTree tree = new BTree(root, 3);
|
||||
tree.delete("I");
|
||||
|
||||
assertArrayEquals(Arrays.asList("J", "M").toArray(), tree.getRoot().getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("G", "H").toArray(), tree.getRoot().getChildren().get(0).getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("K", "L").toArray(), tree.getRoot().getChildren().get(1).getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("O", "P").toArray(), tree.getRoot().getChildren().get(2).getKeys().toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteCase2C() {
|
||||
Node root = new Node(false);
|
||||
root.addKey("R");
|
||||
root.addKey("U");
|
||||
root.addKey("X");
|
||||
|
||||
Node child1 = new Node(true);
|
||||
child1.addKey("P");
|
||||
child1.addKey("Q");
|
||||
|
||||
Node child2 = new Node(true);
|
||||
child2.addKey("S");
|
||||
child2.addKey("T");
|
||||
|
||||
Node child3 = new Node(true);
|
||||
child3.addKey("V");
|
||||
child3.addKey("W");
|
||||
|
||||
Node child4 = new Node(true);
|
||||
child4.addKey("Y");
|
||||
child4.addKey("Z");
|
||||
|
||||
root.addChild(child1);
|
||||
root.addChild(child2);
|
||||
root.addChild(child3);
|
||||
root.addChild(child4);
|
||||
|
||||
BTree tree = new BTree(root, 3);
|
||||
tree.delete("U");
|
||||
|
||||
assertArrayEquals(Arrays.asList("R", "X").toArray(), tree.getRoot().getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("P", "Q").toArray(), tree.getRoot().getChildren().get(0).getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("S", "T", "V", "W").toArray(), tree.getRoot().getChildren().get(1).getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("Y", "Z").toArray(), tree.getRoot().getChildren().get(2).getKeys().toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteCase3A() {
|
||||
Node root = new Node(false);
|
||||
root.addKey("J");
|
||||
root.addKey("M");
|
||||
|
||||
Node child1 = new Node(true);
|
||||
child1.addKey("G");
|
||||
child1.addKey("H");
|
||||
|
||||
Node child2 = new Node(true);
|
||||
child2.addKey("K");
|
||||
child2.addKey("L");
|
||||
|
||||
Node child3 = new Node(true);
|
||||
child3.addKey("O");
|
||||
child3.addKey("P");
|
||||
child3.addKey("R");
|
||||
|
||||
root.addChild(child1);
|
||||
root.addChild(child2);
|
||||
root.addChild(child3);
|
||||
|
||||
BTree tree = new BTree(root, 3);
|
||||
tree.delete("L");
|
||||
|
||||
assertArrayEquals(Arrays.asList("J", "O").toArray(), tree.getRoot().getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("K", "M").toArray(), tree.getRoot().getChildren().get(1).getKeys().toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteCase3B() {
|
||||
Node root = new Node(false);
|
||||
root.addKey("Q");
|
||||
root.addKey("T");
|
||||
|
||||
Node child1 = new Node(true);
|
||||
child1.addKey("O");
|
||||
child1.addKey("P");
|
||||
|
||||
Node child2 = new Node(true);
|
||||
child2.addKey("R");
|
||||
child2.addKey("S");
|
||||
|
||||
Node child3 = new Node(true);
|
||||
child3.addKey("W");
|
||||
child3.addKey("X");
|
||||
|
||||
root.addChild(child1);
|
||||
root.addChild(child2);
|
||||
root.addChild(child3);
|
||||
|
||||
BTree tree = new BTree(root, 3);
|
||||
tree.delete("S");
|
||||
|
||||
assertArrayEquals(Arrays.asList("Q").toArray(), tree.getRoot().getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("O", "P").toArray(), tree.getRoot().getChildren().get(0).getKeys().toArray());
|
||||
assertArrayEquals(Arrays.asList("R", "T", "W", "X").toArray(), tree.getRoot().getChildren().get(1).getKeys().toArray());
|
||||
}
|
||||
|
||||
@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,147 @@
|
|||
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 merge_addsKeysAndChildren() {
|
||||
Node child = new Node(true);
|
||||
child.addKey("I");
|
||||
|
||||
Node node = new Node(false);
|
||||
node.addKey("G");
|
||||
node.addKey("H");
|
||||
node.addChild(new Node(true));
|
||||
|
||||
Node siblingToMergeWith = new Node(false);
|
||||
siblingToMergeWith.addKey("K");
|
||||
siblingToMergeWith.addKey("L");
|
||||
Node siblingChild = new Node(true);
|
||||
siblingChild.addKey("X");
|
||||
siblingToMergeWith.addChild(siblingChild);
|
||||
|
||||
node.mergeWith(siblingToMergeWith);
|
||||
|
||||
assertArrayEquals(Arrays.asList("G", "H", "K", "L").toArray(), node.getKeys().toArray());
|
||||
assertEquals(2, node.getChildren().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getKeyBetweenChildren_basedOnChildIndex() {
|
||||
Node parent = new Node(false);
|
||||
parent.addKey("J");
|
||||
parent.addKey("M");
|
||||
|
||||
Node child1 = new Node(true);
|
||||
child1.addKey("G");
|
||||
child1.addKey("H");
|
||||
parent.addChild(child1);
|
||||
Node child2 = new Node(true);
|
||||
child2.addKey("K");
|
||||
child2.addKey("L");
|
||||
parent.addChild(child2);
|
||||
Node child3 = new Node(true);
|
||||
child3.addKey("O");
|
||||
child3.addKey("P");
|
||||
child3.addKey("R");
|
||||
parent.addChild(child3);
|
||||
|
||||
assertEquals("J", parent.getKeyBetweenChildren(child1, child2));
|
||||
assertEquals("M", parent.getKeyBetweenChildren(child2, child3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtremeKey_returnsFirstKeyBiggerThanParentKey_leftSide() {
|
||||
Node parent = new Node(false);
|
||||
parent.addKey("J");
|
||||
parent.addKey("M");
|
||||
|
||||
Node child = new Node(true);
|
||||
child.addKey("O");
|
||||
child.addKey("P");
|
||||
child.addKey("R");
|
||||
parent.addChild(child);
|
||||
|
||||
assertEquals("O", child.getExtremeKeyComparedTo("M"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtremeKey_returnsLastKeySmallerThanParentKey_rightSide() {
|
||||
Node parent = new Node(false);
|
||||
parent.addKey("J");
|
||||
parent.addKey("M");
|
||||
|
||||
Node child = new Node(true);
|
||||
child.addKey("K");
|
||||
child.addKey("L");
|
||||
parent.addChild(child);
|
||||
|
||||
assertEquals("L", child.getExtremeKeyComparedTo("M"));
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package com.com.brainbaking.greedy;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Huffman {
|
||||
|
||||
private final String toEncode;
|
||||
private final Node root;
|
||||
|
||||
public Huffman(String toEncode) {
|
||||
this.toEncode = toEncode;
|
||||
|
||||
root = buildMinRoot(buildQueue());
|
||||
}
|
||||
|
||||
private Node buildMinRoot(Queue<Node> nodes) {
|
||||
Node root = null;
|
||||
|
||||
while(nodes.size() > 1) {
|
||||
Node first = nodes.poll();
|
||||
Node second = nodes.poll();
|
||||
|
||||
root = new Node(first, second);
|
||||
nodes.add(root);
|
||||
}
|
||||
|
||||
// in geval van maar 1 karakter
|
||||
return root == null ? nodes.poll() : root;
|
||||
}
|
||||
|
||||
private Queue<Node> buildQueue() {
|
||||
Set<Integer> chars = buildCharSet();
|
||||
Queue<Node> tree = new PriorityQueue<>();
|
||||
|
||||
for(Integer character : chars) {
|
||||
int frequency = (int) toEncode.chars().filter(c -> c == character).count();
|
||||
tree.add(new Node(character, frequency));
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
private Set<Integer> buildCharSet() {
|
||||
return toEncode.chars().boxed().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public String encode() {
|
||||
Map<Character, String> encodeMap = new HashMap<>();
|
||||
buildEncodedMap(root, encodeMap, "");
|
||||
|
||||
String encoded = "";
|
||||
for(int i = 0; i < toEncode.length(); i++) {
|
||||
encoded += encodeMap.get(toEncode.charAt(i));
|
||||
}
|
||||
return encoded;
|
||||
|
||||
// wat is hier mis mee?
|
||||
//return toEncode.chars().mapToObj(c -> encodeMap.get(c)).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
private void buildEncodedMap(Node node, Map<Character,String> map, String encoded) {
|
||||
if(node.isRoot()) {
|
||||
// in geval van maar 1 karakter
|
||||
map.put(node.getCharacter(), encoded.equals("") ? "0" : encoded);
|
||||
return;
|
||||
}
|
||||
|
||||
buildEncodedMap(node.getLeft(), map, encoded + "0");
|
||||
buildEncodedMap(node.getRight(), map, encoded + "1");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.com.brainbaking.greedy;
|
||||
|
||||
public class Node implements Comparable<Node> {
|
||||
|
||||
private final char character;
|
||||
private final int frequency;
|
||||
|
||||
private Node left;
|
||||
private Node right;
|
||||
|
||||
public boolean isRoot() {
|
||||
return left == null && right == null;
|
||||
}
|
||||
|
||||
public Node getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public Node getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public char getCharacter() {
|
||||
return character;
|
||||
}
|
||||
|
||||
public int getFrequency() {
|
||||
return frequency;
|
||||
}
|
||||
|
||||
public Node(Node left, Node right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.character = '-';
|
||||
this.frequency = left.getFrequency() + right.getFrequency();
|
||||
}
|
||||
|
||||
public Node(Integer character, int frequency) {
|
||||
this.character = (char) character.intValue();
|
||||
this.frequency = frequency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Node o) {
|
||||
return frequency - o.frequency;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package com.brainbaking.greedy;
|
||||
|
||||
import com.com.brainbaking.greedy.Huffman;
|
||||
import org.junit.Test;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
|
||||
public class HuffmanTest {
|
||||
|
||||
@Test
|
||||
public void huffman_OnlyOnCharacter() {
|
||||
Huffman huffman = new Huffman("aaa");
|
||||
|
||||
String result = huffman.encode();
|
||||
|
||||
assertEquals("000", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void huffman_charAFrequences5TimesButBAndCOnlyOnce() {
|
||||
Huffman huffman = new Huffman("baaaaac");
|
||||
|
||||
String result = huffman.encode();
|
||||
|
||||
assertEquals("001111101", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void huffmanEncoded() {
|
||||
Huffman huffman = new Huffman("hallo dit is een test wasda jong");
|
||||
|
||||
String result = huffman.encode();
|
||||
|
||||
assertEquals("110001101100110011000111101110100101111010000111001001011011101000100001011111001110100010111101111011111000011001110", result);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,10 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class Lists {
|
||||
|
||||
public static ListsWrapper exchange(int oneBasedA) {
|
||||
return new ListsWrapper(oneBasedA);
|
||||
}
|
||||
|
||||
public static void swap(List<Integer> list, int oneBasedA, int oneBasedB) {
|
||||
int temp = list.get(oneBasedA - 1);
|
||||
list.set(oneBasedA - 1, list.get(oneBasedB - 1));
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package be.brainbaking.lists;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ListsWrapper {
|
||||
|
||||
private final int oneBasedA;
|
||||
private int oneBasedB;
|
||||
|
||||
public ListsWrapper with(int oneBasedB) {
|
||||
this.oneBasedB = oneBasedB;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void in(List<Integer> list) {
|
||||
Lists.swap(list, oneBasedA, oneBasedB);
|
||||
}
|
||||
|
||||
public ListsWrapper(int oneBasedA) {
|
||||
this.oneBasedA = oneBasedA;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import static be.brainbaking.lists.Lists.asString;
|
||||
import static be.brainbaking.lists.Lists.exchange;
|
||||
|
||||
public class QuickSort implements Sortable {
|
||||
|
||||
|
@ -68,11 +69,11 @@ public class QuickSort implements Sortable {
|
|||
for(int j = oneBasedStartIndex; j <= oneBasedEndIndex - 1; j++) {
|
||||
if(list.get(j - 1) <= x) {
|
||||
i++;
|
||||
Lists.swap(list, i, j);
|
||||
exchange(i).with(j).in(list);
|
||||
}
|
||||
}
|
||||
|
||||
Lists.swap(list, i + 1, oneBasedEndIndex);
|
||||
exchange(i + 1).with(oneBasedEndIndex).in(list);
|
||||
System.out.println("partitioned: " + asString(list));
|
||||
return i + 1;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue