diff --git a/src/termproject/TwoFourTree.java b/src/termproject/TwoFourTree.java index 4408aa3..70ddfd7 100644 --- a/src/termproject/TwoFourTree.java +++ b/src/termproject/TwoFourTree.java @@ -1,9 +1,13 @@ package termproject; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Random; + /** * Title: Term Project 2-4 Trees * Description: An abstract data type for a 2-4 tree - * Copyright: Copyright (c) 2001 + * Copyright: Copyright (c) 2017 * @author Joel Beckmeyer & Daniel Parker * @version 1.0 */ @@ -28,7 +32,14 @@ public class TwoFourTree treeRoot = root; } - private TFNode search(Object key) { + /** + * Searches the tree for a node containing the given key. + * + * @param key Object to search for + * @return node which contains key, or insertion point for this key + * @throws TwoFourTreeException if root is null + */ + private TFNode search(Object key) throws TwoFourTreeException { TFNode current = treeRoot; TFNode parent = null; if(treeRoot == null) { @@ -39,10 +50,14 @@ public class TwoFourTree // we find the key while(current != null) { int index = FFGTE(current, key); - if(treeComp.isEqual(current.getItem(index).key(), key)) { - break; - } + // ensure that the index given is not out of bounds + if(index != current.getNumItems()) { + if(treeComp.isEqual(current.getItem(index).key(), key)) { + break; + } + } + parent = current; current = current.getChild(index); } @@ -56,9 +71,19 @@ public class TwoFourTree } } + /** + * Finds the index of the first item that is greater than or equal to the + * given key. + * + * @param node TFnode to be searched + * @param key key to find + * @return index of first item greater than or equal to given key + */ private int FFGTE(TFNode node, Object key) { int i; - for(i = 0; i < node.getNumItems(); ++i) { + // loop through item array, comparing each item until we find first item + // greater than or equal to key + for(i = 0; i < node.getNumItems(); i++) { if(treeComp.isGreaterThanOrEqualTo(node.getItem(i).key(), key)) { break; } @@ -66,9 +91,16 @@ public class TwoFourTree return i; } + /** + * Finds the index of the given node in its parent. + * + * @param node the node to be found in the parent + * @return index of node in its parent + */ private int WCIT(TFNode node) { TFNode parent = node.getParent(); int i; + // loop through child array until we find the given node for(i = 0; i < parent.getNumItems() + 1; ++i) { if(parent.getChild(i) == node) { break; @@ -77,27 +109,37 @@ public class TwoFourTree return i; } - private TFNode getInOrderSuccessor(TFNode current, Object key) { - if(current == null) { - return null; + /** + * Finds the in-order successor of the given node-key combination. + * + * @param node the node to start at + * @param key the key to follow + * @return the in-order successor node + */ + private TFNode getInOrderSuccessor(TFNode node, int index) { + TFNode parent = null; + // go down the right child of our key + TFNode current = node.getChild(index + 1); + + // go left until we hit a leaf + while(current != null) { + parent = current; + current = current.getChild(0); } - int index = FFGTE(current, key); - TFNode next = getInOrderSuccessor(current.getChild(index), key); - if(next == null) { - return current; - }else { - return next; - } + return parent; } + /** + * Checks for and fixes node overflow. + * + * @param node the node to check for overflow + */ private void fixOverflow(TFNode node) { if(node.getNumItems() <= MAX_ITEMS) { return; } - TFNode left = node.getChild(3); - TFNode right = node.getChild(4); TFNode parent = node.getParent(); // special case when root overflows (we must increase height of tree) @@ -110,30 +152,236 @@ public class TwoFourTree // removes offending data from current node int index = WCIT(node); + + // preserves data that we want to move around + TFNode left = node.getChild(3); + node.setChild(3, null); + TFNode right = node.getChild(4); + node.setChild(4, null); Item toSibling = node.deleteItem(3); Item toParent = node.deleteItem(2); - node.setChild(3, null); - node.setChild(4, null); - - // creates and populates new sibling node + + // creates and hooks up new sibling TFNode sibling = new TFNode(); - sibling.addItem(0, toSibling); sibling.setParent(parent); + sibling.addItem(0, toSibling); sibling.setChild(0, left); sibling.setChild(1, right); if(left != null) { left.setParent(sibling); right.setParent(sibling); } - - // inserts data into parent node and hooks up sibling + + // connects children to parents parent.insertItem(index, toParent); + parent.setChild(index, node); parent.setChild(index + 1, sibling); + //} // recursively call on parent fixOverflow(parent); } + /** + * Checks for and fixes node underflow. + * + * @param node the node to check for underflow + */ + private void fixUnderflow(TFNode node) { + // checks for underflow + if(node.getNumItems() < 1) { + + // special case where root is underflowed + if(node == treeRoot) { + treeRoot = node.getChild(0); + node.setParent(null); + } + + // different cases to check and run + else if(isPossibleLTrans(node)) { + leftTransfer(node); + } + else if(isPossibleRTrans(node)) { + rightTransfer(node); + } + else if(isPossibleLFusion(node)) { + leftFusion(node); + } + else { + rightFusion(node); + } + } + } + + /** + * Checks if left transfer is possible. + * + * @param node node to check for possible transfer + * @return true if possible + */ + private boolean isPossibleLTrans(TFNode node) { + // checks if the given node has a left sibling + int index = WCIT(node); + TFNode parent = node.getParent(); + + if(index > 0) { + TFNode sibling = parent.getChild(index - 1); + + // checks if existing left sibling has 2+ items + return sibling.getNumItems() >= 2; + }else { + return false; + } + } + + /** + * Checks if right transfer is possible. + * + * @param node node to check for possible transfer + * @return true if possible + */ + private boolean isPossibleRTrans(TFNode node) { + // checks if the given node has a right sibling + int index = WCIT(node); + TFNode parent = node.getParent(); + + if(index < parent.getNumItems()) { + TFNode sibling = parent.getChild(index + 1); + + // checks if existing right sibling has 2+ items + return sibling.getNumItems() >= 2; + }else { + return false; + } + } + + /** + * Performs a left transfer operation. + * + * @param node underflowed node to perform on + */ + private void leftTransfer(TFNode node) { + int index = WCIT(node); + TFNode parent = node.getParent(); + TFNode sibling = parent.getChild(index - 1); + + // preserving data that would otherwise be lost + TFNode lastChild = sibling.getChild(sibling.getNumItems()); + sibling.setChild(sibling.getNumItems(), null); + Item lastItem = sibling.deleteItem(sibling.getNumItems() - 1); + + // swap old sibling item with parent and add parent item to underflowed + // node + Item parentItem = parent.replaceItem(index - 1, lastItem); + node.addItem(0, parentItem); + + + // move old child 0 to child position 1, then add child from sibling as + // child 0 + TFNode oldChild = node.getChild(0); + node.setChild(1, oldChild); + node.setChild(0, lastChild); + if(lastChild != null) { + lastChild.setParent(node); + } + } + + /** + * Performs a right transfer operation. + * + * @param node underflowed node to perform on + */ + private void rightTransfer(TFNode node) { + int index = WCIT(node); + TFNode parent = node.getParent(); + TFNode sibling = parent.getChild(index + 1); + + // preserving data that would otherwise be lost + TFNode firstChild = sibling.getChild(0); + Item firstItem = sibling.removeItem(0); + + // swap old sibling item with parent and add parent item to underflowed + // node + Item parentItem = parent.replaceItem(index, firstItem); + node.addItem(0, parentItem); + + + // insert child from sibling as 2nd(index 1) child + node.setChild(1, firstChild); + if(firstChild != null) { + firstChild.setParent(node); + } + } + + /** + * Checks if left fusion operation is possible + * + * @param node node to check for possible fusion + * @return true if possible + */ + private boolean isPossibleLFusion(TFNode node) { + // checks if a left sibling exists + return WCIT(node) > 0; + } + + /** + * Performs a left fusion operation + * + * @param node underflowed node to perform fusion on + */ + private void leftFusion(TFNode node) { + int index = WCIT(node); + TFNode parent = node.getParent(); + + // delete underflowed node + parent.setChild(index, null); + + // preserving data + TFNode sibling = parent.getChild(index - 1); + Item parentItem = parent.removeItem(index - 1); + TFNode child = node.getChild(0); + + // insert data in sibling + sibling.insertItem(sibling.getNumItems(), parentItem); + sibling.setChild(sibling.getNumItems(), child); + if(child != null) { + child.setParent(sibling); + } + + // fix parent pointer + parent.setChild(index - 1, sibling); + if(node == null) { + System.out.println("node was null: "); + } + + // recursively check underflow on parent + fixUnderflow(parent); + } + + /** + * Performs a right fusion operation + * + * @param node underflowed node to perform fusion on + */ + private void rightFusion(TFNode node) { + int index = WCIT(node); + TFNode parent = node.getParent(); + + // preserving data + Item parentItem = parent.removeItem(index); + TFNode child = node.getChild(0); + TFNode sibling = parent.getChild(index); + + // insert data in sibling + sibling.insertItem(0, parentItem); + sibling.setChild(0, child); + if(child != null) { + child.setParent(sibling); + } + + // recursively check underflow on parent + fixUnderflow(parent); + } public int size() { return size; } @@ -171,13 +419,25 @@ public class TwoFourTree if(treeRoot == null) { treeRoot = new TFNode(); } - - TFNode node = getInOrderSuccessor(treeRoot, key); + + Item data = new Item(key, element); + TFNode node = search(key); int index = FFGTE(node, key); - node.insertItem(index, new Item(key, element)); - if(node.getNumItems() > MAX_ITEMS) { - fixOverflow(node); + if(index != node.getNumItems()) { + // external node that contains a duplicate + if(node.getChild(0) == null) { + node.insertItem(index, data); + // internal node that contains a duplicate + }else { + node = getInOrderSuccessor(node, index); + node.insertItem(0, data); + } + // if we are at last index + }else { + node.insertItem(index, data); } + + fixOverflow(node); } /** @@ -191,10 +451,27 @@ public class TwoFourTree TFNode node = search(key); int index = FFGTE(node, key); - if(node.getItem(index).key() != key) { - throw new ElementNotFoundException("element is not in tree"); + // if we are at an external node and we got the last index of node, + // we know that the key was not in tree + if(index == node.getNumItems() && node.getChild(0) == null) { + this.printAllElements(); + throw new ElementNotFoundException("key is not in tree: " + key); } + Object returnData; + // if we are at an external node, simply remove data from node + if(node.getChild(0) == null) { + returnData = node.removeItem(index).element(); + // else, we are at an internal node, we must replace data with in-order + // successor + }else { + TFNode successor = getInOrderSuccessor(node, index); + returnData = node.replaceItem(index, successor.removeItem(0)).element(); + node = successor; + } + + fixUnderflow(node); + return returnData; } public static void main(String[] args) { @@ -202,85 +479,120 @@ public class TwoFourTree TwoFourTree myTree = new TwoFourTree(myComp); myTree.insertElement(47, 47); - myTree.printAllElements(); - myTree.insertElement(83, 83); - myTree.printAllElements(); - myTree.insertElement(22, 22); - myTree.printAllElements(); - myTree.insertElement(16, 16); - myTree.printAllElements(); - myTree.insertElement(49, 49); - myTree.printAllElements(); - myTree.insertElement(100, 100); - myTree.printAllElements(); - myTree.insertElement(38, 38); - myTree.printAllElements(); - myTree.insertElement(3, 3); - myTree.printAllElements(); - myTree.insertElement(53, 53); - myTree.printAllElements(); - myTree.insertElement(66, 66); - myTree.printAllElements(); - myTree.insertElement(19, 19); - myTree.printAllElements(); - myTree.insertElement(23, 23); - myTree.printAllElements(); - myTree.insertElement(24, 24); - myTree.printAllElements(); - myTree.insertElement(88, 88); - myTree.printAllElements(); - myTree.insertElement(1, 1); - myTree.printAllElements(); - myTree.insertElement(97, 97); - myTree.printAllElements(); - myTree.insertElement(94, 94); - myTree.printAllElements(); - myTree.insertElement(35, 35); - myTree.printAllElements(); - myTree.insertElement(51, 51); - myTree.printAllElements(); - System.out.println("done"); + //myTree.printAllElements(); + //System.out.println("removing\n"); - /*myTree = new TwoFourTree(myComp); - final int TEST_SIZE = 10000; + myTree.removeElement(19); + myTree.removeElement(66); + myTree.removeElement(100); + myTree.removeElement(83); + myTree.removeElement(51); + myTree.removeElement(94); + myTree.removeElement(49); + myTree.removeElement(88); + //myTree.printAllElements(); + System.out.println("test 1: simple test done"); - for (int i = 0; i < TEST_SIZE; i++) { - myTree.insertElement(new Integer(i), new Integer(i)); - myTree.printAllElements(); - myTree.checkTree(); + System.out.println(); + myTree = new TwoFourTree(myComp); + int testSize = 10000; + + Random rng = new Random(); + Queue nums = new LinkedList(); + for (int i = 0; i < testSize; i++) { + int j = rng.nextInt(testSize / 10); + nums.add(j); + myTree.insertElement(j, j); +// if(i > testSize - 30) { +// System.out.println("inserting " + j); +// myTree.printAllElements(); +// myTree.checkTree(); +// } } System.out.println("removing"); - for (int i = 0; i < TEST_SIZE; i++) { - int out = (Integer) myTree.removeElement(new Integer(i)); - if (out != i) { - throw new TwoFourTreeException("main: wrong element removed"); + for (int i = testSize - 1; i >= 0; i--) { + int j = nums.remove(); + if (i < 30){ + System.out.println("removing "+j); + } + //myTree.printAllElements(); + int out = (int)myTree.removeElement(j); + if (out != j) { + throw new TwoFourTreeException("main: wrong element removed: " + out +" ; " + j); } - if (i > TEST_SIZE - 15) { - myTree.printAllElements(); + if (i < 30){ + myTree.printAllElements(); + } + + } + System.out.println("test 2: random done"); + myTree.printAllElements(); + + System.out.println(); + myTree = new TwoFourTree(myComp); + testSize = 1000; + for (int i = 0; i < testSize; i++) { + myTree.insertElement(0, 0); + } + System.out.println("removing"); + int out; + for (int i = testSize - 1; i >= 0; i--) { + out = (int)myTree.removeElement(0); + if (out != 0) { + throw new TwoFourTreeException("main: wrong element removed: " + out); } } -*/ - System.out.println("done"); + System.out.println("test 3: extreme duplicate test done"); + myTree.printAllElements(); + + System.out.println(); + for (int i = 0; i < testSize; i++) { + myTree.insertElement(i, i); + } + System.out.println("removing"); + for (int i = testSize - 1; i >= 0; i--) { + out = (int)myTree.removeElement(i); + if (out != i) { + throw new TwoFourTreeException("main: wrong element removed: " + out +" ; " + i); + } + } + System.out.println("test 4: reverse sorted order remove done"); + myTree.printAllElements(); + + System.out.println(); + for (int i = 0; i < testSize; i++) { + myTree.insertElement(i, i); + } + System.out.println("removing"); + for (int i = 0; i < testSize; i++) { + out = (int)myTree.removeElement(i); + if (out != i) { + throw new TwoFourTreeException("main: wrong element removed: " + out +" ; " + i); + } + } + System.out.println("test 5: sorted order remove done"); + myTree.printAllElements(); + } public void printAllElements() {