CS302 -- Lab 5 -- Algorithm Analysis
Lab Objective
This lab is designed to give you experience with thinking about how to
solve a problem in the most efficient way possible. For this lab you will
not have to turn in programs. Instead you will submit a file in either
plain text or pdf format
that contains your solutions. (Note: we must require that your file
be a plain text file or a pdf file,
for the purposes of standardizing the grading
process.)
Problems
- (20 points)
Write a routine to list out the nodes of a binary tree in
level-order. List the root, then nodes at depth 1, followed
by nodes at depth 2, and so on. Each node should be prefixed by
its level number. For example, given the tree:
10
/ \
/ \
5 15
/ \
/ \
3 20
\
25
/
22
your routine should print:
0 10
1 5
1 15
2 3
2 20
3 25
4 22
For full credit your routine must run in linear time (i.e., O(n), where
n is the number of nodes in the tree). Assume you have the class
definition:
class treenode {
public:
int value;
treenode *left_child;
treenode *right_child;
};
- Hint: You will not be able to use recursion for your solution.
- You may write pseudo-code for this problem (i.e, your answer does not
have to be implemented C++ code.) All we are interested in
seeing
is a function that takes a pointer to a node as an argument. For example:
void print_levels(treenode *root)
- You may assume that the left_child pointer will be 0 if a node
does not have a left child; similarly, you may make the same assumption for
right_child.
- If you want to test your solution there is program called
printLevels.cpp found in
/home/parker/courses/cs302/labs/lab5/. It has a comment that
shows you where you should insert your code. You can invoke the
test program by typing:
printLevels seed child_probability max_depth
seed is a seed for the random number generator that will be
used to generate children and values for the children.
child_probability gives the probability that a left child or
right child will exist. It must be a real number between 0 and 1.
max_depth is the maximum depth of the tree. Without max_depth
the tree generator routine may get into an infinite recursion because
it will keep trying to generate children.
The test program first prints out the generated tree and then calls
your print levels function. The test program prints one node per line
and indents each level by two spaces. For example, the above tree would
be printed as:
10
5
3
----
----
----
15
----
20
----
25
22
----
----
----
- (20 points)
Write a function that takes two integer arguments and returns their
product using only addition and bit shifts. A bit shift is
written as either "var = var << n" or
"var = var >> n". The << operator left
shifts the bits in var by n bits and fills in the opened
positions with 0's. It corresponds to multiplying the number by
2n. For example, 10 is represented internally by the
bit string 1010. If we left shift 10 by 2 bits we obtain 101000 which
you can verify is 10 * 22 or 40. The >> operator right
shifts the bits in var by n bits. The low order n bits are
thrown away and the high order bits are replaced by 0's. This
corresponds to dividing a number by 2n. For example,
right shifting 1010 by one bit results in the string 0101 or 5.
For this problem you will need to use both the left shift and right
shift operators.
The obvious algorithm for doing multiplication using only addition
is:
int multiply(int factor1, int factor2) {
int multiplier = (factor1 < factor2) ? factor1 : factor2;
int multiplicand = (factor1 > factor2) ? factor1 : factor2:
int product = 0;
for (int i = 0; i < multiplier; i++)
product += multiplicand;
return product;
}
This algorithm runs in O(min(factor1, factor2)), which is a linear time
algorithm. You can obtain an O(min(log factor1, log factor2)) algorithm
if you use addition and bit shifts. Write such an algorithm using
C-code.
Hints
- C++ has a bitwise & operator that will do a bit-wise 'and' of
two integers. For example:
a = 14;
b = 3;
a & b = (1110 & 0011) = 0010 = 2
Bit-wise 'and' can prove helpful for inspecting the bits of an
integer. In the above example 3 was used to extract the first two
bits of 14.
- (10 points)
Insertion code in a heap can be simplified
by placing a sentinel value in array location 0 (see discussion
of this on page 216 of Weiss). For example, if we knew
that -1 would always be less than any value that would be inserted into the
heap, we could make -1 the sentinel value. In general we do not know
the minimum value that will be inserted into the heap. What value
can we use as a sentinel value in that case? Hint: you may need to
use a different sentinel value for each insertion.
- (10 points) The graphs below show the results of programs that perform
search on various sets of
random and ordered data. The search techniques include linear search,
red black trees, binary search trees, and hash tables.
The "insert" graphs show the total time
to insert n elements for different values of n.
The "find" (or "search") graphs
show the total time to perform n successful searches
and n unsuccessful searches (2n total searches)
for specified values of n.
Answer each of the following questions and explain why you answered
it the way you did.
- Red black trees perform better than binary search trees when
performing finds on randomly ordered data. Does this result
surprise you? Why or why not?
- Binary search trees perform better than red black trees when
performing inserts on randomly ordered data. Does this result
surprise you? Why or why not?
- The running time for finding elements in a hash table is O(1)
whereas the running time for finding elements in a red
black tree is O(log n). However, for both ordered and
unordered data, it takes a very large
number of elements (i.e., a large n) before hash tables become
more efficient at finding elements. Explain why red-black
trees can be faster for smaller numbers of elements. All
you need to know about a red-black tree is that it creates
a balanced binary tree for the purposes of searching.
- If you had to create a program that would be used to insert
and find elements in a set of data, and you did not know whether
the data was ordered or unordered, and you did not know how
many elements would be in the set of data, which data structure
would you be most likely to choose? Why?
- Based on the observed data, would you ever prefer using a
binary search tree? Why or why not? If your answer is no, can
you suggest a reason other than performance for why you might
use a binary search tree in your program?
- (20 points) Work Exercise 2.27 in the Weiss text. If you want to write a C++
function to test your solution you can paste it into the testMatrix.cpp
program found in /home/parker/courses/cs302/labs/lab5/. You can invoke
the test program by typing:
matrix n seed
where seed is a seed for the random number generator that creates
entries in the matrix.
Hints
- You may find it helpful to allocate an (N+2)X(N+2) matrix and
use sentinel values.
- It is possible to determine whether or not a number is in the
matrix with no more than 4N comparisons.
- Any O(N) solution is adequate; don't worry if your solution uses
more than the minimum.
- You cannot determine whether or not a number is in the matrix
using a single pass that crawls forward through the matrix.
You may have to move both forward and backwards.
Extra Credit (5 points)
The integer function int sign(int) returns
- 1 if its argument is positive
- -1 if its argument is negative, and
- 0 if its argument is zero.
Give a solution that uses 2N calls to the sign function,
no comparisons, and places a minimum number of
sentinel values. Hint: You may use the C++ switch
statement.
What to Hand In
Submit an ascii text file or pdf file named lab5-solutions.
Do not submit an MS Word (or any other format) document.