This lab will consist of two parts. The first part focuses on implementing a node-based stack. The second part uses your stack and the queue from Lab 9 to convert a recusive method implementation to an iterative implemenation.
To begin this lab, you should also review your notes and lecture material for information about linked lists and stacks.
lab.util
or re-use the package
from Lab 9.
lab.util
.
You will already have the node classes if you are re-using your
package from Lab 9.
APIs can be found here.Stack
class:
Node
class to implement
the linked sequence. All of the required fields are already provided
for you in the starter code.
push
method should add a new element to the top
of the stack, increasing the size of stack by one. It does so by adding a
new node to the beginning of the sequence
of nodes.
pop
method should remove and return the element
at the top of the stack, decreasing the size of the stack by one.
It does so by removing the node at the front of the sequence of nodes.
peek
method should return the element
at the top of the stack without removing it.
It does so by returning the element stored in the node at the front of the
sequence of nodes.
size
method should return the size of the stack.
isEmpty
method should return true if the stack is
empty and false otherwise.
equals
method should return true if both stacks
contain the same sequence of elements (i.e., both stacks have the
same size, and the elements in this
stack are equal
to the elements in the other stack when compared in iteration order).
You should use the NodeIterator
class to iterate over
the elements of both stacks.
Consider the following recursive implementation for computing the Fibonacci number F(n):
01 public class Fibonacci { 02 private static Map<Integer, BigInteger> cache = new HashMap<Integer, BigInteger>(); 03 04 public static BigInteger fib(int n) { 05 BigInteger sum = null; 06 if (n == 0) { 07 sum = BigInteger.ZERO; 08 } 09 else if (n == 1) { 10 sum = BigInteger.ONE; 11 } 12 else if (Fibonacci.cache.containsKey(n)) { 13 sum = cache.get(n); 14 } 15 else { 16 BigInteger fMinus1 = Fibonacci.fib(n - 1); 17 BigInteger fMinus2 = Fibonacci.fib(n - 2); 18 sum = fMinus1.add(fMinus2); 19 Fibonacci.cache.put(n, sum); 20 } 21 return sum; 22 } 23 }
The implementation above uses a technique called memoization; it
stores the value of F(n) in the map named cache
so that the
value of F(n) can be reused (instead of recursively recomputed) when
some larger Fibonacci number is computed. For example, invoking
fib(5)
causes the values of F(5), F(4), F(3), and F(2) to
be stored in cache
. If fib(6)
is then invoked,
the values of F(5) and F(4) can be retrieved from the cache
to efficiently compute the value of F(6). It is possible to show that
the method above has complexity O(n) in contrast to the naive implementation
that does not use memoization which has complexity O(2n).
One problem with the above implementation, and with recursive implementations
in general, is that there is a limit to the amount of memory that can
be used to store the active method invocations, and this limit is much
smaller than the total amount of memory available. For example, on my
computer I can successfully invoke fib(5411)
, but
invoking fib(5412)
causes a StackOverflowError
exception to be thrown. To understand why a StackOverflowError
exception is thrown, consider the following illustration of what occurs
when fib(3)
is invoked:
fib
begins execution to compute F(3).
A block of memory called a frame that contains storage for the
method parameters, references to static variables, local variables, and the return
value is allocated from a part of memory called the stack (not
to be confused with the data structure you implemented in Part I that
is also called a stack). Because
the frame is allocated from the stack, the frame is often called a stack frame.
fib
begins
execution to compute F(2). Another block of memory is allocated for
the stack frame for F(2).
fib
begins
execution to compute F(1). Another block of memory is allocated for
the stack frame for F(1).
fib
begins
execution to compute F(0). Another block of memory is allocated for
the stack frame for F(0).
fib
begins
execution to compute F(1). Another block of memory is allocated for
the stack frame for F(1).
Notice that in the picture above, the stack of frames grows because the bottom-most frame (which is trying to compute F(3)) must wait for all of the frames above it to finish executing (to compute F(2) and then F(1)) before it can finish executing. This can become problematic because each stack frame requires enough memory to store:
n
cache
result
a
(used to store F(n - 1))
b
(used to store F(n - 2))
When we try to invoke fib(5412)
we end up with a stack of
5412 frames, each requiring memory as described above. This turns out to
exceed the availabe stack space on my JVM. Stack memory is special because
it can be allocated and deallocated very quickly; this is important for
efficient method execution. However, this speed is obtained by restricting the
amount of stack memory.
A larger pool of memory called the heap is available to your
program. Heap memory is used to store objects (i.e., heap memory
is allocated whenever the new
operator is used). Allocating
and deallocating heap memory is slower than for stack memory, but the size of
the heap is much larger.
If we can convert our recursive implementation into an iterative implementation where
we replace stack frames with objects, then we might be able to compute
values such as fib(5412)
and larger. To do so, we have to mimic
how the JVM mimics the stack by creating and managing our own stack
of frames.
Implement the class FibonacciStackFrame
:
lab.util
.
FibonacciStackFrame
can be found
here. Note that the API is unusual
in that it describes many implementation details (which are not normally
part of an API) to help you complete the implementation.
FibonacciStackFrame
has the following fields:
cache
that corresponds to the cache
in the recursive implementation
n
that stores which Fibonacci number
is being computed (the n in F(n)).
caller
that is a reference to the stack frame
that created this stack frame; this field is required so that this stack frame
knows where to return the value of F(n)
fMinus1
that stores the value of F(n - 1)
returned by another stack frame instance; this field corresponds to the
variable named fMinus1
in the recursive implementation
fMinus2
that stores the value of F(n - 2)
returned by another stack frame instance; this field corresponds to the
variable named fMinus2
in the recursive implementation
sum
that stores the value of F(n - 1) + F(n - 2)
returned by another stack frame instance; this field corresponds to the
variable named sum
in the recursive implementation
fMinus1
and fMinus2
that indicates that these values have not been computed yet. You can use
null
or a negative value.
receiveReturnValue
simulates lines 16 and 17 of the
recursive method. This method should be called exactly twice by two other
FibonacciStackFrame
instances (once to set the value of
fMinus1
and once to set the value of fMinus2
).
returnValueToCaller
simulates line 21 of
the recursive method. This method should invoke receiveReturnValue
on the creator of this stack frame (if the creator is not null
)
and then pop the call stack.
getSum
allows the client that created the initial
FibonacciStackFrame
instance to retrieve the final value
of F(n)
execute
simulates lines 6 through 21 of the
recursive method. To simulate a recursive invocation, you should create
a new FibonacciStackFrame
instance and push it onto the
call stack.
fib2
in the Fibonacci
class
to compute the value of F(10000).
submit 1030 lab10 group.txt lab/util/Stack.javaIf something fails, you may see something like
submitted: group.txt (17 bytes) submitted: Stack.java (5144 bytes) All files successfully submitted. Note: lab/util/Stack.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. Your Stack class compiled successfully. Your Stack class passed all the tests. Your code contains the following 1 style errors /eecs/home/franck/lab/util/Stack.java:3:8: Unused import - java.util.EmptyStackException. SUBMITTED FILE Stack HAS BEEN REMOVED. PLEASE SUBMIT AGAIN.Fix your code and submit again. If you successfully submitted your code, you will see something like this
submitted: group.txt (17 bytes) submitted: Stack.java (5144 bytes) All files successfully submitted. Note: lab/util/Stack.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. Your Stack class compiled successfully. Your Stack class passed all the tests. Your code contains no style errors. Your group.txt file has been successfully validated.
submit -l 1030 lab10After submitting your classes, you should see something like this
The following files have been submitted: group.txt.submitted 17 bytes Stack.java.submitted 5144 bytes FibonacciStackFrame.java.submitted 7341 bytes