diff --git a/pom.xml b/pom.xml index 0a2d511..94f864a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,11 @@ 4.0.0 - com.stack.project - stack-app + com.treelib.project + treelib-app jar 1.0-SNAPSHOT - stack-app + treelib-app http://maven.apache.org @@ -28,8 +28,8 @@ maven-compiler-plugin 3.8.1 - 22 - 22 + 17 + 17 @@ -40,7 +40,7 @@ true - com.stack.project.MainClass + com.treelib.project.MainClass diff --git a/src/main/java/com/treelibrary/BST.java b/src/main/java/com/treelibrary/BST.java new file mode 100644 index 0000000..bfc41c7 --- /dev/null +++ b/src/main/java/com/treelibrary/BST.java @@ -0,0 +1,41 @@ +package com.treelibrary; + +import java.util.function.Consumer; + +public interface BST { + + public Node getRoot(); + boolean isEmpty(); + int count(); + Node insert(T data); + Node find(T data); + T remove(Node node); + Node findMin(Node root); + Node findMax(Node root); + Node next(Node node); + Node prev(Node node); + + /** + * Traverses the BST in in-order and performs an action on each node. + * + * @param action the action to be performed on each node. + */ + void traverseInOrder(Consumer action); + + /** + * Traverses the BST in pre-order and performs an action on each node. + * + * @param action the action to be performed on each node. + */ + void traversePreOrder(Consumer action); + + /** + * Traverses the BST in post-order and performs an action on each node. + * + * @param action the action to be performed on each node. + */ + void traversePostOrder(Consumer action); + + int height(); + void clear(); +} diff --git a/src/main/java/com/treelibrary/Impl/BSTImpl.java b/src/main/java/com/treelibrary/Impl/BSTImpl.java new file mode 100644 index 0000000..4fba8c5 --- /dev/null +++ b/src/main/java/com/treelibrary/Impl/BSTImpl.java @@ -0,0 +1,399 @@ +package com.treelibrary.Impl; + +import java.util.function.Consumer; + +import com.treelibrary.BST; +import com.treelibrary.Node; + + +public class BSTImpl> implements BST { + + private Node rootNode; + + public BSTImpl() { + rootNode = null; + } + + + + @Override + public boolean isEmpty() { + return (null == getRoot()); + } + + public Node getRoot() { + return rootNode; + } + + @Override + public int count() { + int count = 0; + Node iter = this.getRoot(); + count = nodeCounter(iter); + + return count; + } + private int nodeCounter(Node node) { + + if (null == node) { + return 0; + } + + return 1 + nodeCounter( node.getLeft()) + nodeCounter(node.getRight()); + + } + + @Override + public Node insert(T data) { + + Node newNode = new NodeImpl(data); + + if ( rootNode == null) { + + rootNode = newNode; + return rootNode; + + } + + Node iterator = rootNode; + + while ( null != iterator) { + + + if (data.compareTo(iterator.getData()) < 0) { + + if (null == iterator.getLeft()) { + + iterator.setLeft(newNode); + newNode.setParent(iterator); + return newNode; + + } + + iterator = iterator.getLeft(); + + } + if (data.compareTo(iterator.getData()) > 0) { + + if (null == iterator.getRight()) { + + iterator.setRight(newNode); + newNode.setParent(iterator); + return newNode; + + } + + iterator = iterator.getRight(); + + } + } + + return newNode; + } + + @Override + public Node find(T data) { + + Node iterator = rootNode; + + while (null != iterator) { + + if (data.compareTo(iterator.getData()) == 0) { + + return iterator; + + } + + if (data.compareTo(iterator.getData()) < 0) { + + iterator = iterator.getLeft(); + + } + if (data.compareTo(iterator.getData()) > 0) { + + iterator = iterator.getRight(); + + } + } + return null; + } + @Override + public String toString() { + + StringBuilder objectString = new StringBuilder("|"); + Node iterator = this.getRoot(); + recursiveToString(iterator, objectString); + objectString.append("|"); + return objectString.toString(); + + } + + private void recursiveToString(Node node, StringBuilder objectString) { + + if (null == node) { + return; + } + + objectString.append(" " + node.getData().toString()); + recursiveToString(node.getLeft(), objectString); + recursiveToString(node.getRight(), objectString); + + } + + + @Override + public T remove(Node node) { + + if (null == node) { + return null; + } + + T data = node.getData(); + //3 cases to consider: node to delete is a leaf + if (node.isLeaf()) { + + if (node.isLeftChild()) { + + node.getParent().setLeft(null); + + } + else if (node.isRightChild()) { + + node.getParent().setRight(null); + + } + else {//root + rootNode = null; + } + + return data; + } + // node to delete has one child + if (node.hasOneChild() && node.hasRightChild()) { + + if (node.isLeftChild()) { + node.getParent().setLeft(node.getRight()); + } else if (node.isRightChild()) { + node.getParent().setRight(node.getRight()); + } else { // Node is the root + rootNode = node.getRight(); + } + + node.getRight().setParent(node.getParent()); + + return data; + } + if (node.hasOneChild() && node.hasLeftChild()) { + if (node.isLeftChild()) { + node.getParent().setLeft(node.getLeft()); + } else if (node.isRightChild()) { + node.getParent().setRight(node.getLeft()); + } else { // Node is the root + rootNode = node.getLeft(); + } + node.getLeft().setParent(node.getParent()); + return data; + } + //node to delete has 2 children + if (node.hasLeftChild() && node.hasRightChild()) { + + Node successor = findMin(node.getRight()); + + // Set the data of the node to be removed with the successor's data + node.setData(successor.getData()); + + // Special case: If the in-order successor is the direct child of the node + if (successor.getParent() == node) { + node.setRight(successor.getRight()); + if (successor.getRight() != null) { + successor.getRight().setParent(node); + } + } else { + // Remove the in-order successor + if (successor.getRight() != null) { + successor.getRight().setParent(successor.getParent()); + } + successor.getParent().setLeft(successor.getRight()); + } + return data; + } + + return data; + } + + + + + @Override + public Node findMin(Node root) { + + if (root == null) { + return null; + } + + Node iterator = root; + + while (iterator.getLeft() != null) { + + iterator = iterator.getLeft(); + + } + + return iterator; + } + + @Override + public Node findMax(Node root) { + + if (root == null) { + return null; + } + + Node iterator = root; + + while (iterator.getRight() != null) { + + iterator = iterator.getRight(); + + } + + return iterator; + + } + + @Override + public Node next(Node node) {//inorder successor + + Node successor = null; + + if(node.hasRightChild()) { + + return findMin(node.getRight()); + + } + //if node is maximum int the tree + if (findMax(rootNode) == node ) { + + return null; + + } + + Node iterator = node; + Node iteratorParent = node.getParent(); + //find node that is a left child of its parent, the parent is a successor + while (iteratorParent != null && iterator == iteratorParent.getRight()) { + + iterator = iteratorParent; + iteratorParent = iterator.getParent(); + + } + + return iteratorParent; + } + + @Override + public Node prev(Node node) {//predecessor + if (node == null) { + return null; + } + + if (node.hasLeftChild()){ + return findMax(node.getLeft()); + } + Node iterator = node; + Node iteratorParent = node.getParent(); + + while (iteratorParent != null && iterator == iteratorParent.getLeft()) { + + iterator = iteratorParent; + iteratorParent = iterator.getParent(); + + } + return iteratorParent; + } + + @Override + public void traverseInOrder(Consumer action) { + traverseInOrderRecursively(rootNode, action); + } + private void traverseInOrderRecursively(Node root, Consumer action){ + + if (root == null) { + return; + } + + traverseInOrderRecursively(root.getLeft(), action); + action.accept(root.getData()); + traverseInOrderRecursively(root.getRight(), action); + } + + @Override + public void traversePreOrder(Consumer action) { + traversePreOrderRecursively(rootNode, action); + } + private void traversePreOrderRecursively(Node root, Consumer action){ + + if (root == null) { + return; + } + action.accept(root.getData()); + + traversePreOrderRecursively(root.getLeft(), action); + traversePreOrderRecursively(root.getRight(), action); + } + + @Override + public void traversePostOrder(Consumer action) { + traversePostOrder(rootNode, action); + } + + private void traversePostOrder(Node root, Consumer action){ + + if (root == null) { + return; + } + + traversePostOrder(root.getLeft(), action); + traversePostOrder(root.getRight(), action); + + action.accept(root.getData()); + } + @Override + public int height() { + if(isEmpty()) { + return -1; + } + return maxDepth(rootNode); + } + + private int maxDepth(Node node) { + + if (node == null){ + return -1; + } + + int rightDepth = maxDepth(node.getRight()); + int leftDepth = maxDepth(node.getLeft()); + + return Math.max(rightDepth, leftDepth) + 1; + } + + @Override + public void clear() { + //because java has garbage collector we could just use rootNode = null; + clearTree(rootNode); + } + + private void clearTree(Node root) { + if (root == null) { + return; + } + + clearTree(root.getLeft()); + clearTree(root.getRight()); + + root.setLeft(null); + root.setRight(null); + root.setData(null); + } +} diff --git a/src/main/java/com/treelibrary/Impl/NodeImpl.java b/src/main/java/com/treelibrary/Impl/NodeImpl.java new file mode 100644 index 0000000..d3beec3 --- /dev/null +++ b/src/main/java/com/treelibrary/Impl/NodeImpl.java @@ -0,0 +1,94 @@ +package com.treelibrary.Impl; + +import com.treelibrary.Node; +public class NodeImpl implements Node { + private T data; + private Node left; + private Node right; + private Node parent; + + public NodeImpl(T data) { + this.data = data; + this.left = null; + this.right = null; + this.parent = null; + } + + @Override + public T getData() { + return data; + } + + @Override + public void setData(T data) { + this.data = data; + } + + @Override + public Node getLeft() { + return left; + } + + @Override + public void setLeft(Node left) { + this.left = left; + } + + @Override + public Node getRight() { + return right; + } + + @Override + public void setRight(Node right) { + this.right = right; + } + + public boolean isLeaf() { + + return (!hasLeftChild()) && (!hasRightChild()); + + } + public boolean hasRightChild() { + + return null != this.getRight(); + + } + public boolean hasLeftChild() { + + return null != this.getLeft(); + + } + + public boolean hasOneChild() { + + return (this.hasRightChild() && !this.hasLeftChild()) || this.hasLeftChild() && !this.hasRightChild(); + + } + + @Override + public Node getParent() { + return parent; + } + + @Override + public void setParent(Node node) { + + this.parent = node; + + } + + @Override + public boolean isLeftChild() { + + return (null != parent) && (parent.getLeft() == this); + + } + + @Override + public boolean isRightChild() { + + return (null != parent) && (parent.getRight() == this); + + } +} diff --git a/src/main/java/com/treelibrary/Node.java b/src/main/java/com/treelibrary/Node.java new file mode 100644 index 0000000..2ee22dd --- /dev/null +++ b/src/main/java/com/treelibrary/Node.java @@ -0,0 +1,23 @@ +package com.treelibrary; + +public interface Node { + T getData(); + void setData(T data); + + Node getLeft(); + void setLeft(Node left); + + Node getRight(); + void setRight(Node right); + + Node getParent(); + void setParent(Node node); + + boolean isLeaf(); + boolean isLeftChild(); + boolean isRightChild(); + boolean hasRightChild(); + boolean hasLeftChild(); + public boolean hasOneChild(); + +} diff --git a/src/test/java/com/treelibrary/AppTest.java b/src/test/java/com/treelibrary/AppTest.java deleted file mode 100644 index e0f238f..0000000 --- a/src/test/java/com/treelibrary/AppTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.treelibrary; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git a/src/test/java/com/treelibrary/BSTTest.java b/src/test/java/com/treelibrary/BSTTest.java new file mode 100644 index 0000000..f4ad72f --- /dev/null +++ b/src/test/java/com/treelibrary/BSTTest.java @@ -0,0 +1,264 @@ +package com.treelibrary; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + + +import com.treelibrary.Impl.BSTImpl; + +import static org.junit.jupiter.api.Assertions.*; + +public class BSTTest { + + private BST bst; + + @BeforeEach + public void setUp() { + bst = new BSTImpl<>(); + } + + @Test + public void testIsEmpty() { + assertEquals(true, bst.isEmpty()); + assertEquals(0, bst.count()); + } + + @Test + public void testInsert() { + Node node7 = bst.insert(7); + assertEquals(node7.getData(), 7); + assertEquals(bst.count(), 1); + + Node node8 = bst.insert(8); + assertEquals(bst.count(), 2); + + Node node9 = bst.insert(9); + assertEquals(bst.count(), 3); + + Node node2 = bst.insert(2); + + assertEquals(node7.getRight(), node8); + assertEquals(node7.getLeft(), node2); + assertEquals(node8.getLeft(), null); + assertEquals(node8.getRight(), node9); + + assertEquals(bst.count(), 4); + } + + @Test + public void testCount() { + assertEquals(true, bst.isEmpty()); + } + + @Test + public void testFind() { + // Test to find an element in the BST + } + + @Test + public void testRemoveEmpty() { + + assertEquals(null, bst.remove(bst.getRoot())); + } + @Test + public void testRemoveLeaf() { + Node node8 = bst.insert(8); + assertEquals(bst.count(), 1); + assertEquals(8, bst.remove(node8)); + assertEquals(bst.count(), 0); + Node node17 = bst.insert(17); + Node node4 = bst.insert(4); + Node node3 = bst.insert(3); + + assertEquals(3, bst.remove(node3)); + assertEquals(4, bst.remove(node4)); + + assertEquals(bst.count(), 1); + } + + + @Test + public void testRemoveOneChild() { + Node node17 = bst.insert(17); + Node node4 = bst.insert(4); + Node node3 = bst.insert(3); + assertEquals(4, bst.remove(node4)); + } + + @Test + public void testRemove2Children() { + // Inserting nodes + Node node17 = bst.insert(17); + Node node4 = bst.insert(4); + Node node20 = bst.insert(20); + Node node3 = bst.insert(3); + Node node9 = bst.insert(9); + Node node7 = bst.insert(7); + Node node8 = bst.insert(8); + + assertEquals(4, node17.getLeft().getData()); + assertEquals(20, node17.getRight().getData()); + assertEquals(3, node4.getLeft().getData()); + assertEquals(9, node4.getRight().getData()); + assertEquals(7, node9.getLeft().getData()); + assertEquals(8, node7.getRight().getData()); + + + // Before removal, the structure is: + // 17 + // / \ + // 4 20 + // / \ + // 3 9 + // / + // 7 + // \ + // 8 + + // Test that node 4 is removed correctly (it has two children) + assertEquals(4, bst.remove(node4)); + + // The tree should now be: + // 17 + // / \ + // 7 20 + // / \ + // 3 9 + // / + // 8 + + // Verify the structure + assertEquals(7, node17.getLeft().getData()); // 17's left child should now be 7 + assertEquals(9, node17.getLeft().getRight().getData()); // 7's right child should be 9 + assertEquals(8, node9.getLeft().getData()); // 9's left child should still be 8 + assertEquals(7, node17.getLeft().getData()); + assertEquals(3, node17.getLeft().getLeft().getData()); + assertEquals(node9, node8.getParent()); // 8 should have 9 as parent + } + + @Test + public void testFindMin() { + // Test to find the minimum value in the BST + } + + @Test + public void testFindMax() { + // Test to find the maximum value in the BST + } + + @Test + public void testNext() { + + Node node20 = bst.insert(20); + Node node22 = bst.insert(22); + Node node8 = bst.insert(8); + Node node12 = bst.insert(12); + Node node10 = bst.insert(10); + Node node14 = bst.insert(14); + Node node4 = bst.insert(4); + + + assertEquals(bst.next(node12), node14); + //highest value in a tree + assertEquals(bst.next(node22), null); + + assertEquals(bst.next(node8), node10); + assertEquals(bst.next(node20), node22); + + //no right tree, going up with the parent + assertEquals(bst.next(node14), node20); + } + + @Test + public void testPrev() { + // Test to find the in-order predecessor of a given node + } + + @Test + public void testTraverseInOrder() { + bst.insert(10); + bst.insert(5); + bst.insert(20); + bst.insert(3); + bst.insert(7); + bst.insert(15); + bst.insert(25); + + // List to collect the output + List result = new ArrayList<>(); + + // Traverse in-order and collect results + bst.traverseInOrder(result::add); + + // Expected in-order traversal + List expected = List.of(3, 5, 7, 10, 15, 20, 25); + + // Validate the result + assertEquals(expected, result); + } + + @Test + public void testTraversePreOrder() { + bst.insert(10); + bst.insert(5); + bst.insert(20); + bst.insert(3); + bst.insert(7); + bst.insert(15); + bst.insert(25); + + // List to collect the output + List result = new ArrayList<>(); + + // Traverse pre-order and collect results + bst.traversePreOrder(result::add); + + // Expected pre-order traversal + List expected = List.of(10, 5, 3, 7, 20, 15, 25); + + // Validate the result + assertEquals(expected, result); + } + + @Test + public void testTraversePostOrder() { + bst.insert(10); + bst.insert(5); + bst.insert(20); + bst.insert(3); + bst.insert(7); + bst.insert(15); + bst.insert(25); + + // List to collect the output + List result = new ArrayList<>(); + + // Traverse post-order and collect results + bst.traversePostOrder(result::add); + + // Expected post-order traversal + List expected = List.of(3, 7, 5, 15, 25, 20, 10); + + // Validate the result + assertEquals(expected, result); + } + + @Test + @DisplayName("Height test") + public void testHeight() { + assertEquals(bst.height(), -1); + Node node20 = bst.insert(20); + Node node22 = bst.insert(22); + Node node8 = bst.insert(8); + Node node12 = bst.insert(12); + assertEquals(bst.height(), 2); + } + + @Test + public void testClear() { + // Test to clear the BST and validate it is empty afterwards + } +}