|
Lab 3: Putting Classes Together Lab 4: Inheritance and Interfaces Project 2: ADTs: Arrays, Linked Lists and Trees |
Project2: Sample SolutionThis page contains Java code for performing the experiments asked for in Project 2 and some plots of the output for comparisons. Experimental Results. All data points are the average of 500 operations.
// CS251 Project 2 Solution
// Author: Matthew Fricke
// Date: Fall 2012
// Email: mfricke@unm.edu. Please send bug reports to this email address.
// Description: A very simple class that acts as a wrapper for the unsorted array to be used in the experiments.
public class UnsortedArray
{
private Student[] contents;
private int data_size = 0;
UnsortedArray( int size )
{
contents = new Student[size];
}
// Adds a student to the array in constant time since it is just appended to the end of the array.
public void addData( int k, Student data )
{
contents[data_size++] = data;
}
// Search through all the elements in the array to see if any of them match the key we are looking for.
// This takes linear time in the size of the array.
public Student getData( int k )
{
// Dummy student to wrap the key so we can use the comparitor and keep track of the number of comparisons.
Student key = new Student();
key.setID(k);
for( int i = 0; i < data_size; i++ )
{
if ( key.compareTo( contents[i] ) == 0 )
{
return contents[i];
}
}
return null;
}
// Display the array for debugging purposes.
public void displayArray()
{
for ( int i = 0; i < data_size; i++ )
System.out.print(" "+contents[i]);
}
}
SortedArray.java
// CS251 Project 2 Solution
// Author: Matthew Fricke
// Date: Fall 2012
// Email: mfricke@unm.edu. Please send bug reports to this email address.
// Description: A class to hold the sorted array we will be using in the experiments.
// Uses binarySearch to find and retrieve a Student in the array.
public class SortedArray
{
private Student[] contents;
private int data_size = 0;
SortedArray( int size )
{
contents = new Student[size];
}
// Add a student to the array by iterating over the array until a student is found with an ID greater
// than the ID of the student to be added. Move all the data elements from that point to the end of the array
// up by one to make room to insert the new Student. Then add the student to the correct position. This
// maintains the sorted property of the array. This method takes linear time in the size of the array.
public void addData( int k, Student data )
{
Student key = new Student();
key.setID(k);
int i = 0;
while( i < data_size && key.compareTo(contents[i]) > 0 )
{
i++;
}
for( int j = data_size+1; j > i; j-- )
{
contents[j] = contents[j-1];
}
contents[i] = data;
data_size++;
}
// Display the array for debugging
public void displayArray()
{
for ( int i = 0; i < data_size; i++ )
System.out.print(" "+contents[i].getID() );
System.out.println();
}
// Wrapper method that calls binarySearch to retrieve a student matching the key "k" or returns null
// if there is no such student.
public Student getData( int k )
{
// Wrap the key in a dummy student object so we can use the student comparitor
Student key = new Student();
key.setID(k);
return binarySearch( key, 0, data_size );
}
// Binary search return the student in logarithmic time in the size of the array.
public Student binarySearch(Student key, int lowerbound, int upperbound )
{
// Choose a Student in the middle of the array
int position = ( lowerbound + upperbound) / 2;
// Check if the ID of the student is greater than, smaller than, or equal to the ID being sought.
// If greater or equal then only consider the left side of the array and repeat.
// If lesser then only consider the right side of the array and repeat.
// If the upperbound is less than the lowerbound, i.e. the subarray we are left with is empty
// then return null since no students in the array had the ID we were looking for. Otherwise return the
// single element that's left since that is the element we are looking for.
while( key.compareTo( contents[position] ) != 0 && lowerbound <= upperbound )
{
if (key.compareTo(contents[position]) < 0 )
{
upperbound = position - 1;
}
else
{
lowerbound = position + 1;
}
position = (lowerbound + upperbound)/2;
}
if (lowerbound <= upperbound)
{
return contents[position];
}
else
{
return null;
}
}
}
SortedLinkedList.java
// CS251 Project 2 Solution
// Author: Matthew Fricke
// Date: Fall 2012
// Email: mfricke@unm.edu. Please send bug reports to this email address.
// Description: A singly-linked list that maintains the sorted property on the
// addData and getData operations.
// This list is just for Project 2 so it only requires an addData method
// and a getData method.
public class SortedLinkedList
{
private Node head = null;
// Display the List for debugging.
public void displayList()
{
Node current = head;
while( current != null )
{
System.out.print( " " + current.getData().getID() );
current = current.getNext();
}
System.out.println();
}
// Add a Student to the linked list.
// Precondition: The Students in the list are in sorted order with the
// Student at the head of the list having the least ID, or the list is empty.
// Precondtion: The list is null terminated.
// Postcondition: The Student is added to the list. The list is still
// in sorted order and is still null terminated.
public void addData( int k, Student data )
{
// Create a dummy student with the ID set to k so that
// we can use the Student.compareTo( Student ) method which
// makes it easy to count the number of comparisons.
Student key = new Student();
key.setID(k);
// Handle the case where this is the first element in the list
if ( head == null )
{
head = new Node( data );
return;
}
// If this Student has ID less than the Student at the head of the list
// then add the Student to the head of the list.
if ( key.compareTo( head.getData() ) < 0 )
{
Node temp = head;
head = new Node( data );
head.setNext(temp);
return;
}
// Iterate through the list until a Student is found with ID greater
// than the new Student's ID. Insert the new Student into the list
// just before the Student with the larger ID.
Node current = head.getNext();
Node predecessor = head;
while ( current != null )
{
if ( key.compareTo( current.getData() ) < 0 )
{
predecessor.setNext( new Node( data ) );
predecessor.getNext().setNext( current );
return;
}
predecessor = current;
current = current.getNext();
}
predecessor.setNext( new Node( data ) );
}
// Return the Student that matches the key k. If no Student matches
// the key then return null.
public Student getData( int k )
{
// Create a dummy student so that the Student's comparator method
// can be used. The comparator can keep track of how many comparisons
// occur for the experiments.
Student key = new Student();
key.setID(k);
// Start at the head and iterate through the list until either:
// 1) A student is found with an ID equal to the search key. Return that
// student.
// 2) A student is found with an ID greater than the search key.
// Since the list is sorted the search key cannot match any students
// further down the list. So return.
// 3) The end of the list of the list. Then no student in the list
// had matched the search key.
Node current = head;
while( current != null )
{
if ( key.compareTo( current.getData() ) == 0 )
{
return current.getData();
}
else if ( key.compareTo( current.getData() ) < 0 )
{
return null;
}
current = current.getNext();
}
return null;
}
// The list nodes that contain the Students
private class Node
{
private Node next = null;
private Student data = null;
Node( Student d )
{
data = d;
}
public Student getData()
{
return data;
}
public void setData( Student d )
{
data = d;
}
public Node getNext()
{
return next;
}
public void setNext( Node node )
{
next = node;
}
}
}
SortedBinaryTree.java
// CS251 Project 2 Solution
// Author: Matthew Fricke
// Date: Fall 2012
// Description: A binary tree that maintains the recursive sorted property that
// all data elements in the right subtree have keys that are greater than the root's key
// and all data elements in the left subtree have keys less than the root's key.
// Assume that Student IDs which are used as the keys are unique.
class SortedBinaryTree
{
private Node root = null;
// This code taken from Prof Kelley's website so I can see what is going on when I build the tree
// Begin Prof Kelley's code
public void displayTreeSide()
{
displayTreeSide( root, "", "", "" );
}
private void displayTreeSide( Node n, String leftPre, String rootPre, String rightPre ) {
if(n == null) return;
String label = String.valueOf(n.getData().getLastName()) + "(" + n.getData().getID() + ")";
String rootSpaces = String.format("%" + label.length() + "s", "");
leftPre += rootSpaces;
rightPre += rootSpaces;
displayTreeSide( n.left, leftPre + " ", leftPre + " ,-- ", leftPre + " | " );
System.out.println( rootPre + label );
displayTreeSide( n.right, rightPre + " | ", rightPre + " `-- ", rightPre + " ");
}
// End Prof Kelley's code
// Wrapper function to getData that specifies to start at the root of the tree
// Return the Student with ID equal to the key or null if no such student is found
// Input: An integer key corresponding to the ID of the student to be returned
// Output: A student object with ID equal to "key" or null if no such student exists in the tree
public Student getData( int key )
{
// Create a dummy student with ID equal to the key being sought so that the Student comparitor
// can be used. The comparitor keeps track of the number of comparisons for the experiments.
Student s = new Student();
s.setID( key );
return getData( s, root );
}
// Recursive method used to retrieve the student object from the tree that matched the ID of the
// student passed in as an argument. Returns null if there is no such student in the tree. Assumes
// that Student IDs are unique so that at most one Student will match.
public Student getData( Student key, Node parent )
{
Student parent_data = parent.getData();
if (Global.debug) System.out.println("Considering " + parent_data.getID() );
int compare = key.compareTo( parent_data );
// Return the student in the parent node if the ID matches otherwise call getData()
// recursively on the right or left subtree depending on whether the ID being sought is
// less than or greater than the ID of the parent's student.
if ( compare == 0 )
{
if (Global.debug) System.out.println( "Found Key" );
return parent_data;
}
else if ( parent.getLeftChild()!=null & compare < 0 )
{
return getData( key, parent.getLeftChild() );
}
else if ( parent.getRightChild()!=null & compare > 0 )
{
return getData( key, parent.getRightChild() );
}
// Should not reach this code since the if statement above ought to cover all cases.
if (Global.debug) System.out.println("Error " + parent_data.getID() );
return null;
}
// Wrapper to the recursive addData() method. Handles the case where this student is the first student
// to be added to the tree.
public boolean addData( int key, Student data )
{
// Dummy student to wrap the key so we can use the student class's comparitor method and keep
// track of how many comparisons have been performed
Student student_key = new Student();
student_key.setID(key);
if ( root == null )
{
root = new Node( data );
}
else
{
addNode( root, student_key, data );
}
return true;
}
// Recursive method that adds a student to the sorted binary tree in the correct location.
// Input: The parent node that is the root of this subtree. The key of the data element to add.
// The Student to be added.
// Output: A boolean value indicating success or failure.
public boolean addNode( Node parent, Student key, Student data )
{
// There are four cases. Either the key of the student to be added is greater than the key of the root
// or less.
// If less then look at the left subtree, if greater look at the right subtree.
// If the subtree being looked at is null then add the student there, else call getData recursively on
// the root of the subtree. Since Student IDs are unique there will always be a location to which the
// Student can be added.
if ( key.compareTo( parent.getData() ) < 0 )
{
if ( parent.getLeftChild() == null )
{
parent.setLeftChild( new Node( data ) );
}
else
{
addNode( parent.getLeftChild(), key, data );
}
}
else
{
if ( parent.getRightChild() == null )
{
parent.setRightChild( new Node( data ) );
}
else
{
addNode( parent.getRightChild(), key, data );
}
}
// Always return true. Makes it possible to add error checks if the tree is someday extended to handle
// non-unique IDs.
return true;
}
// The node class that is used as building blocks to make the binary tree
// and which holds the student data elements.
private class Node
{
public Student data = null;
public Node left = null;
public Node right = null;
Node( Student d )
{
data = d;
}
public Student getData()
{
return data;
}
public boolean setData( Student d )
{
data = d;
return true;
}
public Node getLeftChild()
{
return left;
}
public Node getRightChild()
{
return right;
}
public boolean setLeftChild( Node n )
{
if ( left == null )
{
left = n;
return true;
}
return false;
}
public boolean setRightChild( Node n )
{
if ( right == null )
{
right = n;
return true;
}
return false;
}
}
}
driver.java
// CS251 Project 2 Solution
// Author: Matthew Fricke
// Date: Fall 2012
// Email: mfricke@unm.edu. Please send bug reports to this email address.
// This driver creates a sorted array, an unsorted array, a sorted list, and a sorted binary tree to hold Student objects, and performs some efficiency experiments on the datastructures.
import java.util.Arrays; // For sorting arrays
import java.util.Collections; // For shuffling arrays
import java.text.DecimalFormat; // For formating the output to some precision
class driver
{
// Print correct usage and exit
public static void usage()
{
System.out.println("Usage: driver [(un)sorted array|list|tree] [number of data items to insert] [number of gets to perform]");
System.exit(1);
}
// main() expects three arguments
public static void main( String[] args )
{
// Do some input checks
// Check that the user passed the correct number of arguments
if ( args.length != 3 ) usage();
// Get the datastructure argument and make sure it is a valid choice
String datastructure = args[0];
if ( !Arrays.asList("unsorted array", "sorted_array", "list", "tree").contains(datastructure) ) usage();
// Set the number of items to add to the datastructure
// and the number of gets to perform in the experiments
int num_adds = 0;
int num_gets = 0;
// Check the arguments passed in by the user to make sure they are integers
try
{
num_adds = Integer.parseInt(args[1]);
num_gets = Integer.parseInt(args[2]);
}
// Catch the exception if arguments are not integers and exit
catch ( Exception e )
{
usage();
}
double total_time_taken = 0;
// Create an unsorted and sorted array twice the size of the number
// of items we want to add so we have room to pay with adding more items
UnsortedArray ua = new UnsortedArray( 2*num_adds );
SortedArray sa = new SortedArray( 2*num_adds );
// Create a sorted linked list and a sorted binary tree
SortedBinaryTree bst = new SortedBinaryTree();
SortedLinkedList sl = new SortedLinkedList();
// create the data items to add and give them some silly names
Student[] students = new Student[num_adds];
for ( int i = 0; i < num_adds; i++ )
{
String suffix = "";
switch ( i % 10 )
{
case 1: suffix = "st"; break;
case 2: suffix = "nd"; break;
case 3: suffix = "rd"; break;
default: suffix = "th";
}
String first_name = "Bill";
String last_name = "Frankenfurter the " + i + suffix;
students[i] = new Student( i*2, first_name, last_name );
}
// Randomly reorder the students just created so our tree
// doesn't turn into a linked list and so adding items to the sorted list
// doesn't always add items to the end or to the front.
Collections.shuffle(Arrays.asList(students));
// Add the students one by one to the datastructure specified by the user.
// Time how long each addition takes and count how many comparisons occur.
for ( int i = 0; i < num_adds; i++ )
{
if (Global.debug) System.out.println("Adding: " + students[i].getID() );
double start = System.nanoTime();
int key = students[i].getID();
if ( datastructure.equals("tree") ) bst.addData(key, students[i]);
else if ( datastructure.equals("list") ) sl.addData(key, students[i]);
else if (datastructure.equals("sorted_array")) sa.addData( key, students[i] );
else if (datastructure.equals("unsorted_array")) ua.addData( key, students[i] );
total_time_taken += System.nanoTime() - start;
}
double comparison_average_for_add = Student.getComparisonCount()/num_adds;
double time_average_for_add = total_time_taken/num_adds;
// Reshuffle the students and perform some specified number of get operations.
Collections.shuffle(Arrays.asList(students));
// Do one get on the tree to prime the cache. The first get takes much
// longer than the others. This could be because of prefetching? Mystery.
if (datastructure.equals("tree")) bst.getData( students[0].getID() );
// Initialize the time and counter variables
Student.resetComparisonCount();
total_time_taken = 0;
for ( int i = 0; i < num_gets; i++ )
{
if (Global.debug) System.out.println("Getting: " + students[i%num_adds].getID() );
int key = students[i%num_adds].getID();
double start_time = System.nanoTime();
Student s = null;
if (datastructure.equals("tree")) s = bst.getData( key );
else if (datastructure.equals("list")) s = sl.getData( key );
else if (datastructure.equals("array")) s = sa.getData( key );
else if (datastructure.equals("sorted_array")) s = sa.getData( key );
else if (datastructure.equals("unsorted_array")) s = ua.getData( key );
total_time_taken += System.nanoTime() - start_time;
if (Global.debug)
{
if ( s == null ) System.out.println( key + " not found");
else System.out.println( s.getLastName() );
}
}
// If the debug flag is set print out the datastructures
if (Global.debug) bst.displayTreeSide();
if (Global.debug) sl.displayList();
if (Global.debug) sa.displayArray();
if (Global.debug) ua.displayArray();
// Format the output values to one decimal place
DecimalFormat df = new DecimalFormat("#.#");
// Compute the averages
double comparison_average_for_get = Student.getComparisonCount()/num_gets;
double time_average_for_get = total_time_taken/num_gets;
// Print the results to standard out
System.out.println( df.format( comparison_average_for_add ) + " "
+ df.format( time_average_for_add ) + " "
+ df.format( comparison_average_for_get ) + " "
+ df.format( time_average_for_get ) );
}
}
|