[20] Question 1:
Please answer the following questions by circling either true or false.
 
 
1.
 
True False
 

 

C++ uses a special internal mechanism to allow the use of << and >> with streams; it is not possible for a programmer to use this mechanism with other classes.
False.  C++ just uses operator overloading on the << and >> operators.  A programmer can use operator overloading at any time.
 
2.
 
True False
 

 

A precondition on a function can document what inputs are legal to a function, but it can't prevent the program from attempting to call the function with illegal inputs.
True.  Preconditions can stop a function from executing with bad inputs, but it can't stop you from trying to call the function illegally.
 
3.
 
True False
 

 

Given an arbitrary binary tree, a postorder traversal of that tree will visit the nodes in the exact reverse order of a preorder traversal.
False.  For most binary trees, the preorder and postorder traversals will not be reverses of each other.  Consider, for example, the tree with 2 as the root, 1 as the left child, and 3 as the right child.  Preorder = 213, postorder = 132.
 
4.
 
True False
 

 

For a stack, "push" and "pop" cancel each other out: a push of any value followed by a pop leaves the stack in exactly the state it was in initially.
True. 
 
5.
 
True False
 

 

For a queue, "enqueue" and "dequeue" cancel each other out: an enqueue of any value followed by a dequeue leaves the queue in exactly the state it was in initially.
False.  The dequeue operation won't remove the item just enqueued.  So if the queue contains '1', and you enqueue 2 and dequeue, the queue will contain '2'.
 
6.
 
True False
 

 

The "new" operator in C++ returns a fresh, statically-allocated instance of a class.
False.  The fresh instance is dynamically-allocated, not statically allocated.
 
7.
 
True False
 

 

If a function f(n) is always less than another function g(n), then f(n) = O(g(n))
True, by definition of big-O.  f(n) = O(g(n)) when g is 'eventually always bigger'.  In this case, g is bigger right from the start.  Just because a question has the word 'always' in it, the answer isn't necessarily false.
 
8.
 
True False
 
  Different binary search trees can have the same inorder traversal.
True.  The inorder traversal is just the elements of the tree in sorted order.  Different BSTs can have the same set of elements, so they can have the same traversals.
 
9.
 
True False
 
  Although binary searching a sorted array has logarithmic runtime on average, it can take as much as linear time in the worst case.
False.  Binary search of a sorted array always takes logarithmic time, no matter what.
 
10.
 
True False
 
  If a class overloads the + operator, the compiler will check that the associated function really does perform an addition-like operation.
False!! The programmer is responsible for providing the 'meaning' of operator + on a class.  If the 'meaning' is not addition, too bad.
 
11.
 
True False
 
  Every binary tree with more than three nodes must have an interior node that is not the root.
True.  
 
12.
 
True False
 
  C++ references are a purely syntactic convenience that do not make the language inherently more powerful than C.
True.  References are just a nice way to say something.  There's nothing that you couldn't say before.
 
13.
 
True False
 
  Since a derived class does not have access to its base class's private data, instances of the derived class do not set aside space for the base class's data members.
False.  Even though they're private, the hidden data of the base class is still part of the derived class.
 
14.
 
True False
 
  The C++ inheritance mechanism allows us to rewrite any recursive function non-recursively.
False.  You can rewrite any function non-recursively, but inheritance has nothing to do with it!
 
15.
 
True False
 
  Activation records are allocated in a special region of memory that is managed automatically by the language.
True.  They contain the 'automatic' variables.
 
16.
 
True False
 
  According to the definition of the Set ADT, if I have a set of 10 integers, and I add an integer to the set, I will always end up with a set of 11 integers.
False.   Consider the case where the set already contains the item you want to add.  The size doesn't change.
 
17.
 
True False
 
  assert( false ) at any point in a program will cause that program to immediately abort.
True, since the 'false' condition always fails.
 
18.
 
True False
 
  It is impossible to write a binary search function for a sorted linked list of integers that runs in O(log n) time for a list of length n.
True.  Binary search of an array works because we have direct access.  Direct access is impossible with a linked list.
 
19.
 
True False
 
  The this pointer is a global variable in C++ programs that points to a single, statically-allocated object.
False.  this is a local variable to member functions that points to the receiver of the call.
 
20.
 
True False
 
  The bool type with the values true and false, and the int type with the values 1 and 0 can both be used to represent boolean values, but 1 and 0 should be used when possible because it makes the program faster.
False!  As I've tried to say, you should always use true and false, since they preserve the 'boolean' abstraction.  And 1 and 0 will NOT be faster.
 

[12] Question 2:
Here is a C++ function that implements a new kind of searching algorithm:
 

int deluxeSearch( int data[], int size, int item )
{
    return deluxeSearchInRange( data, item, 0, size - 1 );
}

int deluxeSearchInRange( int data[], int item, int lo, int hi )
{
    if( lo hi ) {
        return -1;
    }

    int mid1 = lo + (hi-lo)/3;
    int mid2 = lo + 2 * (hi - lo)/3;
    if( item < data[ mid1 ] ) {
        return deluxeSearchInRange( data, item, lo, mid1 - 1 );
    } else if( item == data[ mid1 ] ) {
        return mid1;
    } else if( item data[ mid1 ] && item < data[ mid2 ] ) {
        return deluxeSearchInRange( data, item, mid1 + 1, mid2 - 1 );
    } else if( item == data[ mid2 ] ) {
        return mid2;
    } else {
        return deluxeSearchInRange( data, item, mid2 + 1, hi );
    }
}
Answer the following questions: 
[[ Note that this searching function is just like binary search, except that instead of dividing the array into two chunks, we divide it into three chunks.  Check the two midpoints at every step.  If you find the item you're looking for, stop.  Otherwise recurse into one of the thirds that remain. ]]
 
  1. In terms of the way it searches through a sorted array, is deluxe search more like linear search or binary search?
    Binary search.  It chops off whole chunks of the array at every step.

  2.  
  3. For an array of size n, what's the maximum number of times deluxeSearchInRange will make a recursive call to itself while searching?
    log3(n)

  4.  
  5. Give a tight Big-O bound for deluxeSearch in terms of the size of the passed-in array.  Make the bound as simple as possible by removing any constant factors or lower-order terms.

  6. O(log(n))
     
[8] Question 3:
You've been asked to design and build an ADT that manages information about a collection of students enrolled in a class.  When you sit down to start writing the C++ code, you realize that there are many unspecified details for this task!

Give two questions that you would like to ask about the specification of this ADT before you start programming.  For each question, write a sentence or two explaining why the answer to this question would affect your approach to the task.

There are many possible answers to this question.  Here are some samples:

Will the number of students remain fixed, or change a lot over time?
    (If the number is fixed, I might want to implement my ADT using an array.  If it's going to change a lot, arrays can get inefficient and I'll want to use something more like a linked list.)

Is there a fixed upper bound on class size?
    (This can have a serious effect on my implementation.  If there is a small fixed upper limit, I'll definitely use an array.  If the upper limit is really big, arrays will be no good.  If there's no upper limit, obviously I can't use an array.  I can also use an upper limit for debugging, to make sure nobody tries to overfill the class.)

What information will be stored with each student?
    (This will allow me to write the struct of info associated with every student. )

How will this information be searched/accessed?
    (This will influence whether I should implement a simple collection or a dictionary.  If I'm implementing a dictionary, I'll need to figure out which piece of student information will act as the key.)

Will the information be searched frequently?
    (If there will be frequent searching, then I might want to choose an ADT that makes this fast.  For instance, I might use an array and keep the students sorted by name, which will greatly speed up searching for students by name.)

Note that questions like "should I use a linked list or array" and not valid, because they are questions about the implementation and not the specification.  Your job in this question was to ask questions of someone that will allow YOU to choose a good implementation.  You were not supposed to ask for an implementation.  Assume the person you're asking doesn't know anything about computers; they just know what information they'll need.

[14] Question 4:
Here is a portion of a program:
 

#include <iostream.h>

class Simple
{
public:
    virtual int goAhead();
};

int Simple::goAhead() {
    return 17;
}

class Complicated
{
public:
    virtual int goAhead();
};

int Complicated::goAhead() {
    return 51;
}

Simple *getsimpleObject()
{
    return new Complicated();
}
int main( void )
{

    Simple *obj = getSimpleObject();
    cout << (obj-goAhead()) << endl;
    return 0;
}
This is obviously not a complete program, because it's missing at least the definition of the function getSimpleObject().  Surprisingly, when I compile and run the whole program, it prints out not 17, but 51!  It's possible to finish writing this program in such a way that it prints out 51.  Provide the rest of the program so that this happens.  You may not modify the code that's already written down; you should instead add a definition for the getSimpleObject function (and probably add some other code too).  You are also not permitted to make any use of cout in the code you add. (Hint: what kind of method is goAhead)?

goAhead is a virtual function.  That should suggest that the answer lies somehow in the construction of a new virtual function that overrides the base version of goAhead.  To override a method, you'll need a derived class.  The extra code appears above.

[10] Question 5:
Suppose you are given the following definition of a Dir type:
 

enum Dir {
    NORTH,
    EAST,
    SOUTH,
    WEST
};
 
  • Write a function printDir that takes a direction and prints (using cout) the textual representation of that direction.


  • void printDir( Dir d )
    {
        switch( d ) {
        case NORTH:
            cout << "north";
            return;
        case EAST:
            cout << "east";
            return;
        case SOUTH:
            cout << "south";
            return;
        case WEST:
            cout << "west";
            return;
        }
    }

    Using the numbers 0, 1, 2 and 3 instead of the contants NORTH, EAST, SOUTH and WEST is a serious violation of the abstraction barrier!!
     

  • A malicious programmer could potentially cause an error in your printDir function using a cast, as follows:
  •  


        for( int idx = 0; idx < 10; ++idx ) {
            Dir sneakyDir = (Dir)(idx);
            printDir( sneakyDir );
        }

    Choose a graceful method of error handling for printDir so that this malicious code doesn't cause unpredictable behaviour in the program.  How would you rewrite printDir to use this error handling mechanism?

    Graceful means that the program should not abort.  I would add a default: case to the switch statement in printDir that prints out a special direction name, like "invalid-direction".  That indicates that something was wrong, but allows the program to continue functioning.  After all, the printDir function is harmless.  A faulty direction won't break the program.  If some other function really relies on a correct direction, do the abort there.

    [12] Question 6:
     
    1. Draw the binary search tree that results from inserting the numbers 16, 11, 2, 19, 5, 29, 18, 24 (in that order) into an initially empty tree.
                                  16
                                 /  \
                               11    19
                              /     /  \
                             2    18    29
                              \        /
                               5     24

    2.  
    3. Write out the values in the tree from part (a) as they would be encountered in a preorder, inorder and postorder traversal of the tree

    4. Preoder: 16 11 2 5 19 18 29 24
      Inorder: 2 5 11 16 18 19 24 29 (should just be in sorted order)
      Postorder: 5 2 11 18 24 29 19 16

       
    5. Now suppose I search the tree for the value 6, using the recursive method for finding an item in a binary search tree (the one that doesn't examine the whole tree).  Write down the nodes that I would look at while performing the search.

    6. 16 11 2 5 (and NULL, if you want to absolutely specific).
    [24] Question 7:
    You and some friends decide to write a program to store the names of current movies along with their ratings, so that you can collectively decide what  movies to bet on for the academy awards.  You and your friends rate individual movies on an integer scale of 0 to 10.  A movie's overall rating is a floating-point number from 0.0 to 10.0, which is computed by taking the average of all the votes you and your friends have cast for that movie.

    To store movie ratings, you decide to create a table-style ADT called MovieDict. An instance of the MovieDict ADT should be able to record votes, return the ratings for movies by name, and provide the name of the highest-rated movie.  Although the final version of the program will be used from a WWW turnin-like form, you are provided with the following sample client program to get an idea of how MovieDict will be used:
     

    #include "moviedict.h"
    int main( void )
    {
        MovieDict dict;
        dict.vote( "Citizen Kane", 9 );   // Bob votes 9 on "Citizen Kane"
        dict.vote( "The Avengers", 1 );   // Fred votes 1 on "The Avengers"
        dict.vote( "Citizen Kane", 10 ); // Fred votes 10 on "Citizen Kane"
        dict.vote( "Fargo", 10 );         // Alice votes 10 on "Fargo"
        // Prints out 1.0, the average of all votes for "The Avengers"
        cout << dict.getRating( "The Avengers" ) << endl;
        // Prints out 9.5, the average of all votes for "Citizen Kane"
        cout << dict.getRating( "Citizen Kane" ) << endl;
        char buf[ 100 ];
        // Copies "Fargo" into the buffer buf, because it has
    // the highest overall rating, 10.
        dict.findBest( buf );
    cout << "The movie to bet on is " << buf << "." << endl;
        return 0;
    }
    You've already completed some of the design of the ADT.  You've decided to use a linked-list representation to store the dictionary entries.  You've even created a declaration for a Node struct (that goes into the header file "moviedict.h"), which the ADT will use to store information for each movie:

    #ifndef __MOVIEDICT_H__
    #define __MOVIEDICT_H__
     

    const int MAX_NAME_LENGTH = 100;

    struct Node
    {
        char name[ MAX_NAME_LENGTH ]; // The name of this movie
        int sum_of_votes; // The sum of all votes cast so far
        int number_of_votes; // The number of votes cast, needed
    // to compute the average

        Node *next; // Linked-list pointer
    };

    #endif // __MOVIEDICT_H__

    1. Complete the header file above by adding a declaration for the MovieDict class.  The methods that need to go into the class are precisely those that are implied by the sample client above.  The private data should be chosen to allow a linked-list representation based on the Node struct.  All the information you need to create the header file should be given above.  But if you need to make additional assumptions about the class in order to answer this question, simply state your assumptions below.


    2. class MovieDict
      {
      public:
          MovieDict();

          void vote( const char *name, int value );
          double getRating( const char *name );
          void findBest( char *buf );

      private:
          Node *front;
      };

       
    3. Give the implementation of the getRating method of MovieDict.  You may find the function strcmp helpful.  strcmp takes two strings as arguments.  It returns 0 when the strings are equal and nonzero when they are unequal.  Your function should attempt to deal gracefully with the case of trying to get the rating of a movie that isn't in the database.

    4. double MovieDict::getRating( const char *name )
      {
          for( Node *cur = front; cur != NULL; cur = cur->next ) {
              if( strcmp( cur->name, name ) == 0 ) {
                  return (double)( cur->sum_of_votes ) / (double)( cur->number_of_votes );
              }
          }
          return -1.0; // a safe value.
      }

       

    5. Suppose that somewhere in my client program, I have the following:

    6.     MovieDict m1;
          m1.vote( "Pi the Movie", 9 );
          // ... other stuff here
          MovieDict m2 = m1;

      What potential dynamic memory problem can arise by doing this assignment?

      You could get a dangling pointer if you did some operation that destroyed m2.front while not updating m1.front.  This is similar to the example presented in class about the need for a deep copy when assigning stacks.

    7. What methods should I add to the MovieDict class to prevent it from potentially causing memory errors?

      To prevent all possible errors, the methods that should be added are:
          - An assignment operator
          - A copy constructor
          - A destructor
      In other words, we should make sure that MovieDict conforms to the Canonical Form.
    [4] Bonus Question 1:
    Give at least four of the metasyntactic variable names mentioned in lecture this quarter.
    foo, bar, baz, quux, fred, bob, waldo, blah, blarg, some others were accepted.

    [10] Bonus Question 2:
    Suppose that we redefine the nodes we use in binary search trees as follows:
     

    struct FancyNode
    {
        int data;

        FancyNode *parent;
        FancyNode *left;
        FancyNode *right;
    };

    In this representation, every node points to its left and right children, and also to its immediate parent (through the parent pointer).  It turns out that given a pointer to a FancyNode which is the root of a binary search tree, it's possible to do an inorder traversal of the tree with O(1) additional space (that is, the amount of extra memory you use is constant: no recursion, no stacks, no extra dynamic memory of any kind!)  Write a function cheapInOrder( FancyNode *root ) that does this.

    // This is a pretty hard question.  Here's a piece of code that probably does the trick.
    // The idea is to remember what direction you're traveling in and use that information 
    // to decide what to do next.  I don't guarantee that this works, and I welcome comments
    // about potential bugs.

    enum Direction { NW, NE, SW, SE };

    Direction upFrom( FancyNode *node )
    {
        if( node->parent != NULL && node->parent->left == node ) {
            return NE;
        } else {
            return NW;
        }
    }

    void cheapInOrder( FancyNode *root ) 
    {
        if( root == NULL ) {
            return;
        }

        FancyNode *cur = root;
        Direction dir = SW;

        while( cur != NULL ) {
            if( dir == NW ) {
                dir = upFrom( cur );
                cur = cur->parent;
            } else if( dir == NE ) {
                cout << cur->data << " ";
                if( cur->right != NULL ) {
                    cur = cur->right;
                    dir = SE;
                } else {
                    dir = upFrom( dir );
                    cur = cur->parent;
                }
            } else if( cur->left != NULL ) {
                cur = cur->left;
                dir = SW;

            } else if( cur->right != NULL ) {
                // There's a right child, and we're not coming from it.
                // Print out the current node and go there.
                cout << cur->data << " ";
                cur = cur->right;
                dir = SE;
            } else {
                dir = upFrom( cur );
            }
        }

    }