Question text is in black, solutions in blue.
Q1: 20 points Q2: 15 points Q3: 15 points Q4: 15 points Q5: 15 points Q6: 20 points Q7: 20 points Total: 100 points
public class Dog {
private String name;
private int age;
public Dog (String name, int age) {
this.name = name;
this.age = age;}
public String getName {return name;}
public void setName (String name) {this.name = name;}
public int getAge ( ) {return age;}
public void setAge (int age) {this.age = age;}
public boolean equals (Dog other) {
if (!this.name.equals(other.name)) return false;
return (this.age == other.age);}}
public interface ListInterface<T> {
public int size( ); // number of elements in the list
public void add (T element); // put "element" into the list
public boolean contains (T element); // is "element" in the list?
public boolean remove (T element); // returns whether element was there
public T get (T element); // returns item equal to
// "element" or returns null
public String toString( ); // returns string describing list
public void reset( ); // sets current position to beginning
public T getNext( ); // returns item at current position,
// advances current position
}
public interface PriQueueInterface<T extends Comparable>> {
public boolean isEmpty( );
public boolean isFull( );
public void enqueue (T element);
public T dequeue( );}
public class BSTNode<T extends Comparable<T>> {
protected T info;
protected BSTNode left;
protected BSTNode right;
public BSTNode (T info) {this.info = info; left = right = null;}
public void setInfo (T info) {this.info = info;}
public T getInfo ( ) {return info;}
public void setLeft (BSTNode link) {left = link;}
public void setRight (BSTNode link) {right = link;}
public BSTNode getLeft ( ) {return left;}
public BSTNode getRight ( ) {return right;}}
w1 == w2
and w1.equals(w2)
(when w1
and w2
are String
values)
The first is true if w1
and w2
are pointers to the same object. The second is true if the strings they point to have the same letters in the same order.
Worst-case time means that the time is always bounded by cn for some number n. Average-case time means that the average time on a random input is bounded by cn for some c -- in this case the worst-case time could be larger.
Classes are definitions of objects, giving their fields and code for their methods. Interfaces are lists of methods that an implementing class must have.
Overflow is the result of an attempt to push an element onto a full stack. Underflow is the result of an attempt to pop from or peek at an empty stack.
get(x)
(in a list) and
remove(x)
(in a list)
The first returns a pointer to an element of the list equal to x if one exists, without affecting the list. The second removes an element equal to x if one exists, and returns a boolean telling whether an element was removed.
Both support enqueuing and dequeuing. Dequeuing from an ordinary queue removes
and returns
the element that has been in the queue the longest. Dequeuing from a priority
queue removes and returns the largest element in it according to the class'
compareTo
method.
The heap rule says that every parent node's value is ≥ that of each of its children (for a max heap). The BST rule is that every element of the left subtree of a node with value x has value ≤ x, and that every element of the right subtree has value ≥ x. (Many people gave the condition as being on the children rather than on the descendants.)
remove( )
and
remove(x)
in java.util.PriorityQueue
With no parameter, remove
removes the smallest element
in the queue and returns a boolean telling whether it succeeded. With a
parameter x, it removes and returns an element equal to x if one exists.
Insertion sort builds up a sorted list by starting with an empty list and inserting one arbitrary element at a time into its proper place. Selection sort repeatedly takes the minimum element from the old list and places it in the next position of the new list.
A hash table is an array of locations, each containing elements or a list of elements. A hash function is a function that takes a key and gives the location in the hash table where an element with that key belongs.
java.util
version
of a priority queue, which has many methods other than those in
DJW's PriQueueInterface
. How would the
implementation have changed if we had been required to use only
the methods specified in that interface?
To get at an element in the middle of the PQ, we would have to dequeue into an auxiliary structure until we got the element we wanted, then enqueue the elements back. This would be needed every time we searched the queue to see if a word had already occurred. The alphabetical sort at the end could still work with two PQ's, one by frequency and one by alphabetical order.
FrequencyList
class to use a priority queue instead
of a binary search tree. Suppose we wanted to keep the
WordFreq
objects in an alohabetically sorted list
(implementing DJW's ListInterface
) rather than in a
priority queue. How (in general terms) would our adaptation of
the DJW code proceed? Would this have been more or less
complicated to code than the actual assignment?
The adaptation would be much easier. The BST in DJW's code was
used to carry out list operations -- contains, get, add, and remove --
which are methods of the sorted version of ListInterface
.
We would just replace these BST method calls with calls to methods of an
ArrayUnsortedList
.
To evaluate a node: If the node is a leaf, its value is the operand it contains (this is the base case). If not, we recursively apply our evaluator to the left and right child nodes, then apply the operator at our node to those two results. The value of our node is the result.
// uses DJW definitions
ListInterface<Dog> dl = new ArrayUnsortedList<Dog> ( );
Dog cardie = new Dog("Cardie", 5);
Dog otherCardie = new Dog("Cardie", 5);
dl.add(cardie);
dl.add(otherCardie);
System.out.println (Dl.contains(cardie));
Dog x = dl.get(cardie);
dl.remove(cardie);
System.out.println (dl.contains(cardie));
Dog y = dl.get(cardie);
System.out.println (x.equals(y));
After the first six lines, the list contains two Dog
objects with the same name and age, which are thus equal according to the
equals
method of the Dog
class. The first line
printed is true
because the list contains an element equal to
cardie
(namely cardie
itself). We then set
x
to one of the two dogs in the list, and remove one of the two
dogs (we are not guaranteed which comes out). But either way, the second
line is printed is true
because a dog equal to cardie
is still in the list at that time. Finally, we set y
equal to
the element still in the list. Since the two dogs are equal, the third line
printed is also true
.
public class Question3b {
public static void doubleAge (Dog d) {
d.setAge (2 * d.getAge( ));}
public static void switchAges (Dog d1, Dog d2) {
d1.setAge (d2.getAge( ));
d2.setAge (d1.getAge( ));
public static void main (String [ ] args) {
Dog cardie = new Dog ("Cardie", 5);
Dog duncan = new Dog ("Duncan", 3);
doubleAge (duncan);
switchAges (cardie, duncan);
switchAges (duncan, cardie);
doubleAge (cardie);
System.out.println (cardie.getAge( ));
System.out.println (duncan.getAge( ));}}
We set cardie
to an age-5 dog and duncan
to
an age-3 dog. Since doubleAge
does work as advertised, the
age of duncan
becomes 6. Since switchAges
does
not work as advertised, but simply sets both ages to the original age
of its second argument, the two switchAge
calls leave both dogs'
ages at 6. We then double the age of cardie
, so the two lines
printed are 12
and 6
.
Heap<Integer>
object, insert the values 3, 1,
4, 1, 5, and 9. Draw the resulting heap. Then remove the
largest element three times. Draw the resulting heap. (A heap
may have multiple elements with the same value.)
For some reason many people did not see the value "9" to be added, at the start of the second line. I'll present the states of the heap using the standard conversion to a linear structure -- we go from an empty heap to "3", "31", "413", "4131", "54311", and "945113". The three removal operations bring us to "54311", "4131", and "311".
// method to be part of some generic class
public static T[ ] toArray (Stack<T> s) {
// return array containing the elements of s, top element first
// uses java.util.Stack where pop both removes and returns
T[ ] out = (T[ ]) new Object[s.size( )];
for (int i = 0; i < s.size( ); i++)
out[i] = s.pop( );
return out;}
This compiles and runs, but has incorrect behavior. It is meant to
fill the array with all the elements of s, but it puts only
about half the elements of s in because the index of the
for
loop is changing as elements are removed from s.
public class Terrier extends Dog {
String color;
public Terrier (String name, int age, String color) {
super (name, age);
this.color = color;}
public String getColor( ) {return color;}
public String setColor (String c) {color = c;}
public boolean equals (Dog other) {
if (super.equals (other))
return (this.color.equals(other.color));
else return false;}}
First the unintended error -- the type of The intended error also prevents the class from compiling.
The setColor
should have been void
. As written this will not
compile because there is no return statement in that method.
Dog
object other
has no field
color
, as only Terrier
objects have
this field defined. So the reference to
other.color
is bad. If other
were
cast into a Terrier
, the method would compile, and
give a run-time error if called on a non-terrier
Dog
object.
public static void sortStrings (String [ ] arr) {
if (arr.length <= 1) return;
int minIndex = 0;
String min = arr[0];
for (int i = 0; i < arr.length; i++)
if (arr[i].compareTo(min) < 0) {
minIndex = i;
min = arr[i];}
String temp = arr[minIndex];
arr[minIndex] = arr[0]; // additional error corrected during test
arr[0] = temp;
sortStrings (arr);}
This wil compile, but it will run forever if arr.length >=
1
. The recursive call does not make progress toward the base
case, so the recursion never terminates.
public static void PQSelectSortStrings (String [ ] arr) {
// this is a DJW Heap which implements PriQueueInterface
int n = arr.length;
Heap<String> heap = new Heap<String> (n);
for (int i = 0; i < n; i++)
heap.enqueue (arr[i]);
for (int j = 0; j < n; j++)
arr[j] = heap.dequeue( );}
Each for loop runs n times on a heap of size at most n, and the heap operations on a heap of size k take O(log k) time. Thus we can't have more than O(n log n) time. (To be complete, though I didn't insist on this, we should check that the time isn't shorter because the heaps are actually smaller than n and so log k is less than log n. But for half the times through the loop, the heaps have at least n/2 elements and log k is at least (log n) - 1. Then (n/2) (log n - 1) is still O(n log n).
public static void rearrange (String [ ] arr) {
// uses java.util.Stack where pop both removes and returns
int n = arr.length;
boolean flag = false;
Stack<String> s = new Stack<String> ( );
Stack<String> temp = new Stack<String> ( );
for (int i = 0; i < n; i++)
s.push(arr[i]);
int index = 0;
while (s.size( ) > 0) {
while (s.size( ) > 0) {
if (flag) temp.push(s.pop( ));
else {arr[index] = s.pop( );
index++;}
flag = !flag;}
while (temp.size( ) > 0)
s.push(temp.pop( ));}}
The code before the outer while
loop takes O(n) time.
The two inner loops each take time proportional to the size of
the stack s at the start of the run through the outer loop. But this
size, orginally n, is cut in half each time through the outer
loop.
(It's important that the new size is half the old size
rounded down, or we would never remove the last element
of s.) So the total time is proportional to n + n/2 + n/4 +
..., a sum that converges to 2n which is O(n). The total time
is thus still O(n).
public static int square (int n) {
if (n > 0) {
int previousSquare = square (n - 1);
return (previousSquare + 2*n - 1);}
// above line had +1 on test, getting wrong answer, same timing
else return 0;}
Note that the code on the exam sheet didn't actually return n2, though it took the same time as the code above which does. This method does O(1) work before calling itself with parameter one smaller. It thus recurses to a depth of n, spending O(1) time in each of its incarnations, for a total of O(n) time.
UnsortedKistPQ
that implements that DJW PriQueueInterface
by using
a DJW ArrayUnsortedList<T>
object to hold the
elements of the priority queue. I have not given you code for
ArrayUnsortedList
, but you don't need it because you
may only use the methods in it that are mentioned in the
interface ListInterface<T>
. Note, however, that
ArrayUnsortedList
has one constructor with no
parameters, and that an ArrayUnsortedList
object
expands as needed to hold any number of elements.
public classUnsortedListPQ<T extends Comparable<T>>
implements PriQueueInterface<T> {
private ArrayUnsortedList<T> list;
public UnsortedListPQ ( ) {
list = new ArrayUnsortedList<T> ( );}
public boolean isEmpty ( ) {
return list.size( ) == 0;}
public boolean isFull ( ) {
return false;}
public void enqueue (T element) {
list.add (element);}
public T dequeue ( ) {
if (isEmpty( )) throw new QueueUnderflowException( );
list.reset( );
T max = list.getNext( );
for (int i = 0; i < list.size( ) - 1; i++) {
T next = list.getNext( );
if (next.compareTo(max) > 0)
max = next;}
list.remove(max);
return max;}}
public static String
sortedString (String str)
that assumes that each
character in its input String
is one of the char
values from 'a'
thorugh 'z'
,
inclusive, and returns a sorted version of the string. For
example, sortedString("cardie")
should return
"acdeir"
and sortedString("duncan")
should return "acdnnu"
. For full credit, your
method should run in O(n) time on an input string of n
letters. (But you can get partial credit for any correct method.)
I gave six points out of ten for a correct sort
of any running time. It's easiest to copy the characters of the
string into an array and then use any standard sorting method
-- a bubble sort might be simplest but you could also have just
adapted the priority queue selection sort of Question 5 (a).
The idea behind the O(n) sort is that 26 is a constant, so that
O(26n) time is the same as O(n). Several
people made 26 passes over the input string, first copying all the a's
to the output string, then all the b's, then all the c's, and so
forth. This is a correct sort because the output string has the same
number of copies of each letter as the input string.
A somewhat faster version of the same idea is called counting
sort -- we make one pass over the input counting the number of
each letter, using an array of 26 We showed in lecture that no comparison-based sorting
algorithm can be faster than O(n log n), which means that this O(n)
solution is not a comparison-based sorting algorithm. Its running
time depends crucially on the array being of O(1) size, which it only
is because the type of elements that we are sorting has only O(1)
members. This could not be used to sort objects of a generic type,
for example.
int
values. Then we go
through this array, adding the correct number of each letter to the
output string. Code for this is given below.
public static String sortedString (String str) {
int [ ] freq = new int[26];
for (int i = 0; i < 26, i++)
freq[i] = 0;
for (int j = 0; k < str.length( ) j++)
freq[str.charAt(j) - 'a']++;
String out = "";
for (char cur = 'a'; cur <= 'z'; cur++)
for (int k = 0; k < freq[cur - 'a']; k++)
out += cur;
return out;}
DogTree
which will hold an
ordered collection of Dog
objects as a binary search
tree. The class should not be generic and should not use the
class BinarySearchTree<T>
, though it will use the
generic class BSTNode<T>
for its nodes. The
ordering we want on Dog
objects uses the
compareTo
operation on the String
objects
for their names -- note that the Dog
class has no
compareTo
method and you may not add one.
DogTree
, such that
the instance method int size( )
will return the
number of dogs in the tree and will take O(1) time.
public class DogTree {
private BSTNode<Dog> root;
private int size;
public DogTree( ) {
size = 0;
root = null;}
int size( )
that takes
O(1) time (no matter how many dogs are in the tree).
public int size( ) {return size;}
boolean add (Dog
d)
that will insert the new dog d
into the
tree if there is no dog already in the tree with the same
name. Your method should return true
if the new dog
is added and false
if there was already a dog there
with that name. (Hint: You may want to use a helper method
boolean recAdd (Dog d, BSTNode<Dpg> n)
but
you are not required to do so.)
public boolean add (Dog d) {
if (root = null) {
root = new BSTNode<Dog> (d);
return true;}
else return recAdd (d, root);}
public boolean recAdd (Dog d, BSTNode<Dog> node) {
// assumes that "node" is not null, adds node for d in right place
Dog thisDog = node.getInfo( );
int cv = d.getName( ).compareTo(thisDog.getName( ));
if (cv < 0)
if (node.getLeft( ) == null) {
node.setLeft(new BSTNode<Dog>(d));
return true;}
else return recAdd (d, node.getLeft( ));
else if (cv == 0) return false;
else if (node.getRight( ) == null) {
node.setRight(new BSTNode<Dog>(d));
return true;}
else return recAdd (d, node.getRight( ));}
Dog youngest( )
for DogTree
that will return a Dog
(not
a node) in the calling tree that has an age no larger than that
of any other Dog
in the tree. The method should
return null
if the tree is empty. This method
must make use of recursion -- I suggest making a helper
method Dog recYoungest (BSTNode<Dog> node)
that
returns the youngest dog in the subtree under node
.
public Dog youngest ( ) {
return recYoungest (root);}
public Dog recYoungest (BSTNode<Dog> node) {
if (node == null) return null;
Dog candidate = node.getInfo( );
Dog leftVal = recYoungest (node.getLeft( ));
Dog rightVal = recYoungest (node.getRight( ));
if (leftVal != null)
if (leftVal.getAge( ) < candidate.getAge( ))
candidate = leftVal;
if (rightVal != null)
if (rightVal.getAge( ) < candidate.getAge( ))
candidate = rightVal;
return candidate;}
Last modified 9 January 2013