CS302 -- Lab 4


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 turn in an ascii text file that contains your solutions.

Problems

  1. (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., proportional to the number of nodes in the tree). Assume you have the class definition:
       class treenode {
          public:
    	int value;
            treenode *left_child;
            treenode *right_child;
       };
    

    1. Hint: You will not be able to use recursion for your solution.
    2. You may write pseudo-code for this problem. All I am interested in seeing is a function that takes a pointer to a node as an argument. For example:
      	void print_levels(treenode *root)
      	
    3. You may assume that the left_child pointer will be 0 if a node does not have a left child and you may make the same assumption for right_child.
    4. If you want to test your solution I have created a file called /home/bvz/courses/302/labs/lab4/printLevels.cpp. It has a comment that shows you where you should insert your code. printLevels generates test trees based on several command line arguments described below. 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
      	          ----
                        ----
                    ----
      	

  2. (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 I left shift 10 by 2 bits I 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. It 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.

    Your first reaction might be that you can do multiplication in O(1) time using a machine-level instruction so why bother. Technically this is wrong. Machine-level instructions cannot accomplish their task in O(1) time. They only appear to do so by limiting integers to 32 or 64 bits. Further, they may well use a hard-wired version of the algorithm that you will be developing. Another reason to care is that some scripting languages implement arbitrarily large-sized integer multiplication and hence cannot complete integer multiplication in one step using 32 or 64 bit multiplication instructions.

    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)). It is a linear time algorithm (actually it is considered an exponential time algorithm because numbers can be represented with log n bits and hence "n" is considered to be the number of bits--thus a linear time algorithm must require no more than a constant factor of log n steps). You can obtain an O(min(log factor1, log factor2)) algorithm if you use addition and bit shifts (as just discussed, this algorithm is actually considered linear). Write such an algorithm using C-code.

    Hints

    1. 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.

  3. (10 points) In class we discussed how the insertion code in a heap can be simplified by placing a sentinel value in array location 0. In class we knew that -1 was always less than any value that would be inserted into the heap so we made -1 the sentinel value. In general we do not know what is 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.

  4. (10 points) The leaves of the binary search trees you have seen thus far have null pointers for the left and right children. A find function will then be written as follows:
    	node *find(node *current_node, int key) {
    	    if (current_node == 0)
    		return 0;
    	    else if (current_node->key == key)
    		return current_node;
    	    else if (current_node->key < key)
    		return find(current_node->left_child, key);
    	    else
    		return find(current_node->right_child, key);
    	}
    	
    If we instead make the left and right children point to sentinel nodes then the first two lines of the find function could be eliminated, since we could ensure that even if the key is not in the tree, the search will terminate when a sentinel node is reached. The problem is that if each child points to its own sentinel node we would have to initialize all the sentinal nodes before performing the search and that would be expensive. How can we solve this problem and make it feasible to use sentinel nodes?

  5. (20 points) 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 matrix.cpp program found in /home/bvz/courses/302/labs/lab4/. matrix generates test matrices for your function. You can invoke the testprogram by typing:
    	matrix n seed
    	
    where seed is a seed for the random number generator that creates entries in the matrix.

    Hints

    1. You may find it helpful to allocate an (N+2)X(N+2) matrix and use sentinel values.
    2. You can determine whether or not a number is in the matrix with roughly 5N comparisons with matrix elements. If you do not use sentinels you may have to have additional comparisons to make sure that you stay within the matrix. My initial solution had roughly 5N comparisons and then I was able to slightly simplify it so do not worry if you require 6N comparisons instead of 5N comparisons.
    3. 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 forward and then crawl backwards.


What to Hand In

Submit an ascii text file named lab5-solutions. Do not submit an MS Word document. We are not a Windows shop and the TAs may not have access to Windows tools.