implement working tree

This commit is contained in:
Joel Beckmeyer 2017-12-14 23:12:50 -05:00
parent 0b266e11ba
commit 474b0a6033

View File

@ -1,9 +1,13 @@
package termproject; package termproject;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
/** /**
* Title: Term Project 2-4 Trees * Title: Term Project 2-4 Trees
* Description: An abstract data type for a 2-4 tree * Description: An abstract data type for a 2-4 tree
* Copyright: Copyright (c) 2001 * Copyright: Copyright (c) 2017
* @author Joel Beckmeyer & Daniel Parker * @author Joel Beckmeyer & Daniel Parker
* @version 1.0 * @version 1.0
*/ */
@ -28,7 +32,14 @@ public class TwoFourTree
treeRoot = root; 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 current = treeRoot;
TFNode parent = null; TFNode parent = null;
if(treeRoot == null) { if(treeRoot == null) {
@ -39,8 +50,12 @@ public class TwoFourTree
// we find the key // we find the key
while(current != null) { while(current != null) {
int index = FFGTE(current, key); 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; parent = current;
@ -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) { private int FFGTE(TFNode node, Object key) {
int i; 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)) { if(treeComp.isGreaterThanOrEqualTo(node.getItem(i).key(), key)) {
break; break;
} }
@ -66,9 +91,16 @@ public class TwoFourTree
return i; 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) { private int WCIT(TFNode node) {
TFNode parent = node.getParent(); TFNode parent = node.getParent();
int i; int i;
// loop through child array until we find the given node
for(i = 0; i < parent.getNumItems() + 1; ++i) { for(i = 0; i < parent.getNumItems() + 1; ++i) {
if(parent.getChild(i) == node) { if(parent.getChild(i) == node) {
break; break;
@ -77,27 +109,37 @@ public class TwoFourTree
return i; return i;
} }
private TFNode getInOrderSuccessor(TFNode current, Object key) { /**
if(current == null) { * Finds the in-order successor of the given node-key combination.
return null; *
* @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); return parent;
TFNode next = getInOrderSuccessor(current.getChild(index), key);
if(next == null) {
return current;
}else {
return next;
}
} }
/**
* Checks for and fixes node overflow.
*
* @param node the node to check for overflow
*/
private void fixOverflow(TFNode node) { private void fixOverflow(TFNode node) {
if(node.getNumItems() <= MAX_ITEMS) { if(node.getNumItems() <= MAX_ITEMS) {
return; return;
} }
TFNode left = node.getChild(3);
TFNode right = node.getChild(4);
TFNode parent = node.getParent(); TFNode parent = node.getParent();
// special case when root overflows (we must increase height of tree) // special case when root overflows (we must increase height of tree)
@ -110,15 +152,19 @@ public class TwoFourTree
// removes offending data from current node // removes offending data from current node
int index = WCIT(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 toSibling = node.deleteItem(3);
Item toParent = node.deleteItem(2); 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(); TFNode sibling = new TFNode();
sibling.addItem(0, toSibling);
sibling.setParent(parent); sibling.setParent(parent);
sibling.addItem(0, toSibling);
sibling.setChild(0, left); sibling.setChild(0, left);
sibling.setChild(1, right); sibling.setChild(1, right);
if(left != null) { if(left != null) {
@ -126,14 +172,216 @@ public class TwoFourTree
right.setParent(sibling); right.setParent(sibling);
} }
// inserts data into parent node and hooks up sibling // connects children to parents
parent.insertItem(index, toParent); parent.insertItem(index, toParent);
parent.setChild(index, node);
parent.setChild(index + 1, sibling); parent.setChild(index + 1, sibling);
//}
// recursively call on parent // recursively call on parent
fixOverflow(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() { public int size() {
return size; return size;
} }
@ -172,12 +420,24 @@ public class TwoFourTree
treeRoot = new TFNode(); treeRoot = new TFNode();
} }
TFNode node = getInOrderSuccessor(treeRoot, key); Item data = new Item(key, element);
TFNode node = search(key);
int index = FFGTE(node, key); int index = FFGTE(node, key);
node.insertItem(index, new Item(key, element)); if(index != node.getNumItems()) {
if(node.getNumItems() > MAX_ITEMS) { // external node that contains a duplicate
fixOverflow(node); 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); TFNode node = search(key);
int index = FFGTE(node, key); int index = FFGTE(node, key);
if(node.getItem(index).key() != key) { // if we are at an external node and we got the last index of node,
throw new ElementNotFoundException("element is not in tree"); // 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) { public static void main(String[] args) {
@ -202,85 +479,120 @@ public class TwoFourTree
TwoFourTree myTree = new TwoFourTree(myComp); TwoFourTree myTree = new TwoFourTree(myComp);
myTree.insertElement(47, 47); myTree.insertElement(47, 47);
myTree.printAllElements();
myTree.insertElement(83, 83); myTree.insertElement(83, 83);
myTree.printAllElements();
myTree.insertElement(22, 22); myTree.insertElement(22, 22);
myTree.printAllElements();
myTree.insertElement(16, 16); myTree.insertElement(16, 16);
myTree.printAllElements();
myTree.insertElement(49, 49); myTree.insertElement(49, 49);
myTree.printAllElements();
myTree.insertElement(100, 100); myTree.insertElement(100, 100);
myTree.printAllElements();
myTree.insertElement(38, 38); myTree.insertElement(38, 38);
myTree.printAllElements();
myTree.insertElement(3, 3); myTree.insertElement(3, 3);
myTree.printAllElements();
myTree.insertElement(53, 53); myTree.insertElement(53, 53);
myTree.printAllElements();
myTree.insertElement(66, 66); myTree.insertElement(66, 66);
myTree.printAllElements();
myTree.insertElement(19, 19); myTree.insertElement(19, 19);
myTree.printAllElements();
myTree.insertElement(23, 23); myTree.insertElement(23, 23);
myTree.printAllElements();
myTree.insertElement(24, 24); myTree.insertElement(24, 24);
myTree.printAllElements();
myTree.insertElement(88, 88); myTree.insertElement(88, 88);
myTree.printAllElements();
myTree.insertElement(1, 1); myTree.insertElement(1, 1);
myTree.printAllElements();
myTree.insertElement(97, 97); myTree.insertElement(97, 97);
myTree.printAllElements();
myTree.insertElement(94, 94); myTree.insertElement(94, 94);
myTree.printAllElements();
myTree.insertElement(35, 35); myTree.insertElement(35, 35);
myTree.printAllElements();
myTree.insertElement(51, 51); myTree.insertElement(51, 51);
myTree.printAllElements();
System.out.println("done"); //myTree.printAllElements();
//System.out.println("removing\n");
/*myTree = new TwoFourTree(myComp); myTree.removeElement(19);
final int TEST_SIZE = 10000; 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++) { System.out.println();
myTree.insertElement(new Integer(i), new Integer(i)); myTree = new TwoFourTree(myComp);
myTree.printAllElements(); int testSize = 10000;
myTree.checkTree();
Random rng = new Random();
Queue<Integer> nums = new LinkedList<Integer>();
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"); System.out.println("removing");
for (int i = 0; i < TEST_SIZE; i++) { for (int i = testSize - 1; i >= 0; i--) {
int out = (Integer) myTree.removeElement(new Integer(i)); int j = nums.remove();
if (out != i) { if (i < 30){
throw new TwoFourTreeException("main: wrong element removed"); 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) { if (i < 30){
myTree.printAllElements(); 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("test 3: extreme duplicate test done");
System.out.println("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() { public void printAllElements() {