Grading
This assignment will be tested and graded by a computer program (and you can submit as many times as you like). For this to work, there are some important rules you must follow:
- Keep the directory structure of the provided zip file. If you find a file in the subdirectory
comp2402a3
leave it there. - Keep the package structure of the provided zip file. If you find a
package comp2402a2;
directive at the top of a file, leave it there. - Do not rename or change the visibility of any methods already present. If a method or class is
public
leave it that way. - Submit early and often. The submission server compiles and runs your code, and gives you a mark. You can submit as often as you like and only your best submission will count. There is no excuse for submitting code that does not compile or does not pass tests.
- Write efficient code. The submission server places a limit on how much time it will spend executing your code even when performing millions of operations. For some questions it also places a limit on the stack size. These limits will easily be obtained by data structures and algorithms that are asymptotically optimal: This means $O(\log n)$ time operations for question 1 and $O(n)$ time $O(1)$-stack-size algorithms for question 2.
Submitting and Testing
The submission server is now accepting submissions. Please try it out.
Warning: Do not wait until the last minute to submit your assignment. There is a hard 5 second limit on the time each test has to complete. For the largest tests, even an optimal implementation takes 3 seconds, and may take longer if the server is heavily loaded.
The Assignment
Start by downloading the Assignment 3 Zip File, which contains a skeleton of the code you need to write.
1. Sorted sets with access by rank
For this assignment, you will have to test your own code thoroughly. The submission server will consume anything that you try to print to System.out or System.err so the testing done on the server will be almost completely opaque. It will only give you a generic error of the form "get(i)/rank(i)/find(x)
returns incorrect value", "an exception occurred", or "test failed after 5 seconds". On the positive side, testing is something you can do easily because SlowSkiplistRankedSSet
is a correct implementation that you can use to test against. Be sure to test a good mix of operations that includes and interleaves add(x)
, remove(x)
, get(i)
and rank(x)
.
The RankedSSet
interface is an extension of the SSet
interface described in the textbook that supports two extra operations
get(i)
: return the valuex
in the set that is larger than exactlyi
other elements in the set.rank(x)
: return the number, $i$, of elements in the set that are less thanx
(note thatx
is not necessarily in the set).
Currently there are two identical implementations of the RankedSSet
interface included in the assignment called SlowSkiplistRankedSSet
and FastSkiplistRankedSSet
. Both of them are slow: each of them has an implementation of get(i)
and rank(x)
that runs in $\Omega(n)$ time, in the worst case.
Modify the FastSkiplistRankedSSet
implementation so that the get(i)
and rank(x)
implementations each run in $O(\log n)$ expected time and none of the other operations (add(x)
, remove(x)
, find(x)
, etc.) have their running-time increased by more than a small constant factor. Looked to the SkiplistList
implementation discussed in class for inspiration on how to achieve this.
Suggestion: Once you've decided on your representation, implement a display()
method for your FastSkiplistRankedSSet
that outputs an ASCII-graphics representation of your data structure on System.out
. Here's example output from one that I created while making sample solutions:
*---------------------------------------* *---------------------------------------* *---------------------------------------* *---------------------------------------* *---------------------------------------*---------------* *---------------------*-----*-----------*---------------* *---------------------*-----*-----*-----*---*-----*-----* *---------------------*-----*-----*-*---*---*---*-*-----* *-------------*---*---*-*---*-----*-*---*---*---*-*-----* *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
Doing this will force you to think about your representation and will also help you debug your implementation. With the right representation, the display()
method is actually easy to implement, requiring only a pair of nested for
loops.
2. Tree Traversal Tricks
For this part of the assignment, you will be working with the BinaryTree
class provided in the zip file. It contains four uncompleted functions that you are supposed to complete. For full marks, each of these functions should run in $O(n)$ time and should not use recursion. See the function traverse2()
in BinaryTree.java
that we also discussed in class for an example of how to do tree traversal without recursion.
The BinaryTree
class implements a static method called randomBST(n)
that returns a random looking binary tree. BinaryTree
also implements the toString()
method so you can use System.out.println(t)
to view a representation of a BinaryTree
that is closely related to question 3. You can use this for testing your functions. Again, you should test your own code thoroughly since the testing done on the server will be mostly opaque. On the server, your code will be tested on a Java virtual machine with a limited stack size. You can do similar testing yourself by using the java -Xss argument (although you won't have to worry about this if you don't use recursion.).
- The method
totalDepth()
should return the sum of the depths of all nodes in the tree, or-1
if the tree has no nodes at all. - The method
totalLeafDepth()
should return the sum of the depths of all the leaves in the tree, of-1
if the tree has no leaves. (A leaf is a node that has no left child and no right child.) - The method
bracketSequence()
should return a string that gives the dot-bracket representation of the binary tree. The dot-bracket representation of a binary tree can be defined as follows:- the dot-bracket representation of a tree with no nodes is the string
"."
- the dot-bracket representation of a binary tree with root node
r
consists of an open bracket(
followed by the dot-bracket representation ofr.left
followed by the dot-bracket representation ofr.right
followed by a closing bracket)
Some examples: - the dot-bracket representation of the binary tree with only one node is
(..)
- the dot-bracket representation of a 2-node binary tree is either
((..).)
or(.(..))
depending on whether the root has no right child or no left child - the dot-bracket representation of a the height-1 binary tree with two leaves is
((..)(..))
- the dot-bracket representation of a tree with no nodes is the string
- The method
prettyPrint(w)
prints an ASCII drawing of the binary tree on thePrintWriter
w
(usingw.print(s)
andw.println(s)
, like in Assignment 1). In this representation,- each node is represented by an asterisk
*
- an edge from a node to its left child is represented by a vertical bar
|
- an edge from a node to its right child is represented by a sequence of one or more hyphens
-
For each edge from a nodeu
to its right childv
, the number of hyphens used to represent the edgeuv
should be just enough so that the drawing of the subtree rooted atv
is separated from the drawing of the subtree rooted atu.left
by exactly one column. See the example here:
- each node is represented by an asterisk
*-------*-*-----*-* | | | | *---*-* * *-*-* * | | | *-* * * | *