The goal of this lab is to practice using structs, pointers, and dynamic allocation in C.
One advantage to object-oriented programming is how easy it is to write data structures. Data structures merely are a kind of object, and many aspects of managing the objects are built in to a language like Java. In this lab, we will transfer our knowledge of implementing data structures-- in this case, a binary tree-- to C, where we do not have object-oriented facilities. Many details handled by Java will need to be emulated or done by hand in C.
Copy the following files from course public directory.
cp /cslab.all/ubuntu/cs245/lab12/bintree.h . cp /cslab.all/ubuntu/cs245/lab12/bintreedriver.c .
Open the file bintree.h
in xemacs and inspect it.
One common use of a header file is to define a type.
Unlike in a class, the details of a struct type must be known by all modules
that use it at compile time.
This file defines the struct for a node in the tree
and renames the type struct Node_t
to Node
.
Similarly BinaryTree
is the typedef'd name
for a struct type that is a placeholder for the root node of a tree.
The rest of the file provides prototypes for the functions--- things that would ordinarily be instance methods, but in this case must be like static methods.
Make sure you understand the structure of these types, and then
look over bintreedriver.c
.
This program will test out the functions you write to implement
the various binary tree operations.
It repeatedly asks the user for values and inserts those
values into the tree in a way to maintain it as a binary
search tree.
It then twice looks for values supplied by the user
(I have this running two times so you that you can test it
out both on a value that is in the tree and a value that isn't.)
Then we print the tree as a depth-first in-order traversal.
Finally, we destroy the tree.
Open a new file, bintree.c
, in which you
will implement the functions prototyped in
bintree.h
.
Include bintree.h
for the types and function prototypes and
include stdlib.h
for malloc()
.
Since BinaryTree
is not a class, it does
not have a constructor.
While a constructor is written to initialize the instance variables
of a class, instantiating a class does a bit more than what
the constructor contains: it allocates memory for the
new object.
In lieu of a constructor, implement the function
makeTree()
.
This needs to allocate memory for the new tree
(the address of which it will eventually return)
and it needs to initialize the tree's field-- in this
case, we're starting it out as an empty tree, so we make the
root null.
You won't be able to test these functions very well until they are all written, but you should compile as you go along.
gcc -c bintree.c
Remember that the -c
flag means "compile only"
("don't link or make an executable").
Now we want to write a function which, given a value, will make a new node containing that value and place it in the right place in the tree, assuming that this tree is and will remain a binary search tree.
There is more than one way to do this; however, two observations: (1) You'll probably want to do it recursively (it's more "elegant" this way), and (2) you'll probably want some helper functions. Here are some pieces in the process:
First, at some point you will need to make a new node.
So, just as we had a makeTree()
function for
trees, you probably should write a makeNode()
function (or something similarly named) which will allocate
a node and initialize its fields.
Second, we can restate this problem in terms of nodes instead of trees: given a node and a value, make a new node of the value and place it in the proper place in the subtree rooted at the given node. This is nicely solved recursively.
Finally, if the two above pieces are in place, then
the implementation of add
is just a matter of
starting the recursion going on the root node-- if that node is not
null.
Implement add()
and any necessary helper functions.
Remember to put the helper functions first, since the C compiler
must see them before they are used in add()
.
Implementing contains()
is straightforward
once you have figured out how to add a value to a tree.
Implement this and compile.
Next, implement an in-order depth first traversal.
I've chosen this because in a binary search tree, that
will result in printing the numbers as a sorted list.
I recommend also implementing this recursively,
with a recursive helper function for visiting a node;
let the function printInOrder()
, then,
simply start the recursive process going, and
print a new line at the end.
(Don't print a new line for every node; let the list appear on one line.)
Also, you'll need to include stdio.h
.
Finally, we want to deallocate all the memory we are using in this tree. This is work that would be done by the garbage collector in Java. (In this case, since we've come to the end of the program, deallocating isn't much of an issue; all the memory the program is using gets returned to the available memory when the program exits anyway. I'm making you do the deallocation just for the practice.)
It might seem like a simple matter to write destroyTree()
.
Saying free(tree)
will return the chunk of code
for the tree to the list of available memory.
However, it's not that simple;
doing that would not return the memory for all the individual nodes
to the available portion.
Instead, we need to traverse the tree, deallocating nodes
from the leaves up.
Once again, a recursive helper function would be an appropriate
solution.
To conform to standard practice, after you deallocate
the memory stored in a pointer, you should set that pointer
to NULL
.
It would be better to get a segmentation fault (a traceable error)
if you were ever to reuse that variable
than to have two pointers refering to the same piece of memory
for completely different purposes (an error very difficult to trace).
Now compile the driver.
gcc -c bintreedriver.c gcc bintreedriver.o bintree.o -o bintreedriver
Test, and don't be surprised if you get a segmentation fault. Fix it up until you're sure it works.
Turn in a hard copy.
In your typescript, make sure you search for
one value that is in the tree and one value that isn't
(so you get both a positive and a negative response from
contains()
).