CSCI 3510 Programming Assignment 4
First solution due: Nov. 23, 5:00
Second solution due: Dec. 6, 5:00
This assignment has you implement an abstract data type
and a tester for that abstract data type. Follow a refinement
plan such as the one at the end of the assignment.
DO NOT ATTEMPT TO WRITE ALL OF THE PROGRAM BEFORE DOING ANY TESTING.
START RIGHT AWAY. DO NOT WAIT.
An abstract data type
Write an implementation of height-balanced binary search
trees as an abstract data type. Only implement
- the lookup operation (which is exactly the same as the
naive algorithm, since it does not rebalance);
- the insertion operation;
- the extractMin and extractMax operations;
- the deletion operation;
- an operation to print a tree, showing its structure. See below.
Implement the abstract data type as a class. However, the class
can be designed as a wrapper of a classical implementation. See
below.
Hints: Check what you write for these common errors.
- Vague or incorrect contracts.
- Failure to rebalance. Check all of the tree
operations that make any change to the tree.
- Failure to delete memory that becomes inaccessible.
Again, check all of the operations that make any
change to the tree.
- Advertising functions in the header file that should
be private.
- Trying to do something after the function returns.
An application
Write an application that reads four numbers a, b, c and d from the user,
where it is required that a < b and c < d.
It should then do the following.
- Insert the numbers a, a+1, ..., b
into an initially empty tree, in ascending order.
- Insert d, d-1, ..., c into that same tree,
in descending order.
- Print the tree.
- For each of a, a+1, ..., b and each of c, c+1, ..., d, ask
whether the number is in the tree. If any are not, print a
complaint.
- Remove the numbers c, c+1, ..., d, in that order, from the tree.
- Print the tree again.
Hints
Be sure that the application does what is requested. Do not
try to be fancy and do something else.
Run some tests. Do not stick only with very small tests.
Put some stress on your program. Look a the results.
Are they right? Are the trees balanced?
Printing a tree
A reasonable way to print a tree
is to use indentation to show the structure of the tree. Tree
300
/ \
80 500
would be printed as follows.
node: 300
leaf: 80
leaf: 500
Tree
20
/ \
10 55
/ \ \
4 17 75
/
12
would be as follows.
node: 20
node: 10
leaf: 4
node: 17
leaf: 12
null
node: 55
null
leaf: 75
This way, you can see the tree in the indentation. Be sure
to include the null subtrees at nodes with just one child,
since otherwise it is impossible to know whether the single child
is a left child or a right child.
You will want a function printtree(t,n)
that prints tree t with its first line indented n "units"
(where a unit is some number of spaces)
and remaining lines indented appropriate amounts from there.
I will use two spaces for a unit.
For example, if t is the tree
300
/ \
80 500
then printtree(t,6) would print
node: 300
leaf: 80
leaf: 500
where the entire thing has been indented 6 units (12 spaces).
Wrapper classes
Often, a class is created by writing a classical implementation of
an abstract data type (without classes or any attempts to hide
data), and then writing a class that acts as
an object-oriented interface to that classical data type. That is what you
should do here. Your tree class will use a tree type that is
implemented in the way discussed in class. Since a tree represents
a set of numbers, call the class Set. The set is supposed to hide
the way that it implements sets.
A set object will hold a binary search tree. Here is what your
set class will look like.
class Set {
private:
Tree t;
public:
Set(); // Initially, a set is empty.
~Set(); // Destructor: recovers memory.
void Insert(int x);
void Remove(int x);
bool Member(int x);
bool IsEmpty();
void MakeEmpty();
void PrintTree(); // For testing
};
Notice that the extractMin and extractMax operations are not part
of this class. They are implemented in the classical data type
because they are needed to do deletions.
The class implementation uses the classical data structure to do
the job. For example, if you have an insert operation in your
binary search tree data structure, then your Insert operation for class
Set will look like this.
void Set::Insert(int x)
{
insert(t,x);
}
Be careful. If you use exactly the same name in the class as you use
in the tree data structure, you might need to put :: in front of
it to ensure that the compiler understands that you mean the one from
outside the class, not the one inside the class. For example, if
both insert operations have name Insert, you write
void Set::Insert(int x)
{
::Insert(t,x);
}
Refinement
Here is a suggested plan for implementing this program.
- Implement the tree classical data structure with just
the lookup and insert operations, without rebalancing.
Implement the print operation.
Write the first four steps of the tester, but make them refer
directly to the classical data structure. Do not use the
class yet. Test your program.
- Modify the classical data structure implementation
of the insert operation so that it rebalances. This will involve
implementing the rotation operations. Test the program again.
If there are problems, fix them. Be careful when writing the
rotation operations to avoid mistakes. Bugs in rotations are
difficult to find by debugging along.
One way to deal with this is
to comment out all but one of the rotation operations, and instead
implement it as a "stub" that has no effect. For example,
your implementation of a function called DoubleRotateRightwards
might look like this when it is a stub.
void DoubleRotateRightwards(Tree& t)
{}
That way, you can introduce and test one rotation operation at a time.
When testing the rotations, put a print in each rotation that
says that it is being performed. Make sure your tests do all of
the rotations. If they don't, try some other tests.
- Add the ExtractMin and ExtractMax operations, without
rebalancing. Write a tester to call them on a few inputs.
Check your results.
- Now make the ExtractMin and ExtractMax operations rebalance.
Test them with the same tester. You should get different results
for the trees.
- Add the remove function. It uses ExtractMin and ExtractMax.
Add the rest of the test to the program, still using the classical
data structure directly.
- Now write the Set class. Modify your tester to use the
set class instead of using the classical data structure directly.
Test your program.
Pay attention to the trees that you get. If your program is supposed
to be balancing, but the trees are unbalanced, something is wrong.
You might want to write a function, just for testing, that tells whether
a tree is balanced by checking all of the nodes. If it detects an
unbalanced tree, you might make your program print a warning.
Things to watch for and debugging hints
Pay attention to your tests. Don't presume that, just because your
program passes a few very small tests that it will work on larger trees.
Also don't presume that a test has produced the correct answer without
looking a the answer to see whether it is correct.
Look for memory leaks. Be sure to delete memory that is no longer
needed. However, be wary of deleting memory that you still need.
A good idea is to enclose all of your deletes in ifdefs. For example,
if you are going to delete a node pointed to by pointer r, write
# ifdef DO_DELETIONS
delete r;
# endif
Normally, you write
#define DO_DELETIONS
at the top of the program. That way, the delete instructions are
compiled. If your program is not working, however, and you suspect
you might be creating dangling pointers, comment out that line, as
//#define DO_DELETIONS
and compile again. If the problem goes away, you know what the
source of the problem is.
Program structure
The final program MUST be written as a separate application and
abstract data type. The abstract data type can be put into one
module (holding the class Set and the binary search tree
implementation) or in two modules (one holding the class Set and
another holding the binary search tree implementation). The testing
application must be in a separate file.
The appliation MUST only include the header file of the set class.
It must not include the implemenation (.cc or .cpp) file. Use
a linker to link the parts together.