Computer Science 3510
Data Structures
Fall 2002
Solutions to practice questions for midterm Exam 2

  1. When doing object-oriented programming, you typically find that functions have one less parameter than do corresponding functions that are used in procedural (non-object-oriented) computing. For example, a classical function to test whether a value x is present in a tree t has two parameters, x and t, but an object-oriented function has only one parameter, x. Explain why there is one less parameter.

    In object-oriented programming, each function is part of an object. The function can refer to the object in which it resides implicitly, so that object is not an explicit parameter. It is an implicit parameter.

  2. What is the purpose of a destructor for a class? What is the destructor for class River called? Under what circumstances is the destructor called?

    A destructor recovers resources owned by an object. Typically, it deletes memory that the object owns, but that is physically outside the object.

    The destructor for class River is called ~River.

    The destructor is called any time an object is destroyed, for any reason.

  3. Object-oriented programming in C++ can be used to make it impossible to violate the abstraction of an abstract data type.

    1. What feature of C++ makes this possible?

      The class, and private members of classes.

    2. Programmers rarely deliberately sabotage their own work. Since programmers are not supposed to violate abstractions, why is it so important that a compiler prevent them from doing so? Can't programmers police themselves?

      Even good programmers make mistakes, and the compiler should catch those mistakes wherever possible.

      Programming teams often have some less experienced programmers who are likely to violate abstractions if permitted to do so. A compiler that disallows such violations gives the better programmers confidence that the less experienced programmers are not violating abstractions.

  4. Suppose that structure Polynomial is defined as follows.

        struct Polynomial {
          int degree;
          double* coef;
          Polynomial(int n)
          {
            degree = n;
            coef = new double[d+1];
          }
        };
    
    Which of the following will create a polynomial with degree 5?

    1. Polynomial P(5);
    2. Polynomial(5) P;
    3. Polynomial P; Polynomial(5);
    4. Polynomial P; P(5);

  5. Suppose that structure Polynomial and variable P are defined as in the preceding problem. Which of the following will set the coefficient of P at index 1 to hold 3.5?

    1. P[1].coef = 3.5;
    2. P.coef[1] = 3.5;
    3. P.(*coef)[1] = 3.5;
    4. (*P).coef[1] = 3.5;

  6. Suppose that you insert 33 into the following heap. What does the heap look like after the insertion, and after restoring it to a heap using reheapUp?

     
                          25
                         /  \
                        /    \
                       /      \
                     60        40
                    /  \      /  \
                   65  75    42  46
                  /
                 80
      

                          25
                         /  \
                        /    \
                       /      \
                     33        40
                    /  \      /  \
                   60  75    42  46
                  /  \
                 80  65
      

  7. How long does it take, in terms of n, to remove the smallest thing from a heap? The answer only needs to be correct to within a constant factor.

    log2(n)

  8. Dijkstra's shortest distance algorithm performs a sequence of basic update operations, where one basic update consists of finding a vertex u, marking u done, and then updating the status and other information of all vertices that are adjacent to u. If there are n vertices, about how many basic update operations does Dijkstra's algorithm do, in the worst case?

    It does one step to initialize (marking the start vertex done), and then does one update for every other vertex. So, in the worst case Dijkstra's algorithm does one basic step for each vertex. That is, it does n basic update steps.

  9. Suppose that you use the connection manager discussed in class to manage connections in a graph. Show how the superior array (called sup here) changes as each of the following operations are done. Use the algorithm that does not do either of the improvements -- the basic algorithm.

    In the diagram, the connect function is abbreviated c. So, for example c(2,4) indicates that we connect 2 and 4.

    I presume that connect(A,B) tries to make A' point to B', rather than making B' point to A', where A' is the representative of A and B' is the representative of B.

      i  sup[i]         i  sup[i]           i  sup[i]         i  sup[i]
         
      1   1             1  _5__              1  _5__            1  _5__
         
      2   2             2  _2__              2  _2__            2  _4__
               c(1,5)              c(5,2)              c(5,4)
      3   3   --------> 3  _3__   -------->  3  _3__  --------> 3  _3__
         
      4   4             4  _4__              4  _4__            4  _4__
         
      5   5             5  _5__              5  _2__            5  _2__
      

  10. Using the type Node shown, write a function called PrintEvens that prints the even numbers in a binary search tree in descending order, one number per line, skipping the odd numbers in the tree. (n is even if n%2 == 0.) For example, if t is the tree shown in problem 5, then PrintEvens(t) would print the numbers 100, 20 and 16, one per line, in that order. The function should not destroy the tree.

          struct Node {
            int key;
            Node* left;
            Node* right;
            Node(Node* l, int k, Node* r)
            {
              left = l;
              key  = k;
              right = r;
            }
          };   
      

        void PrintEvens(Node* T)
        {
          if(T != NULL) { 
             PrintEvens(T->right);
             if(T->key % 2 == 0) cout << T->key << endl;
             PrintEvens(T->left);
          }
        }
      

  11. The binary search tree implementation that was discussed in class was nonpersistent; the insert operation, for example, changed the tree. It is possible to implement binary search trees in a persistent way also, so that they compute new trees from old ones.

    Using the type Node of the previous problem, write a function removeMin(t) that returns the tree that would result from removing the smallest value from tree t, but that does not alter tree t. If t is empty, then removeMin(t) should return an empty tree. For example, if t is the tree

                          81
                        /    \
                      20      100
                        \
                         65 
                       /    \
                     50      70
      
    then removeMin(t) should return the following tree, without altering tree t.
                           81
                         /    \
                       65      100
                     /   \
                   50     70 
      
    The new tree that you construct can share subtrees with t, as long as it does not change the subtrees.

          Node* removeMin(Node* t)
          {
            if(t == NULL) return NULL;
            else if(t->left == NULL) return t->right;
            else return new Node(removeMin(t->left), t->key, t->right);
          }