CS 311 Fall 2024  >  Exam Review Problems, Set E


CS 311 Fall 2024
Exam Review Problems, Set E

This is the fifth of seven sets of exam review problems. A complete review for the Final Exam includes all seven problem sets.

Problems

Review problems are given below. Answers are in the Answers section of this document. Do not turn these in.

    1. Compare & contrast Singly Linked Lists and Doubly Linked Lists, from the point of view of efficiency and order of operations.
    2. What does all this mean for the design of generic container types?

     
  1. List the STL sequence containers, and very briefly indicate how each is typically implemented. Hint. There are six of them.
     
    1. Briefly explain how an STL std::deque is generally implemented.
    2. What are the major advantages of a std::deque over other sequence types?
    3. The C++ standard says that insert at either end of a std::deque is “constant time”. We would not say that. Nonetheless, the C++ Standard is correct—and so are we. Explain.

     
    1. What is mean by iterator (and reference) validity?
    2. Briefly summarize the iterator validity rules for std::vector, std::list, and std::set.

     
  2. “An iterator is often a wrapper around a pointer.”
    1. What does this statement mean?
    2. Why a “wrapper”, and not just a pointer?

     
  3. Suppose the following operations are performed, in the order given, beginning with an empty structure:
    1. push(2)
    2. push(1)
    3. getTop
    4. push(3)
    5. getTop
    6. pop
    7. push(4)
    8. getTop
    9. pop
    10. getTop
    11. pop
    12. getTop
    13. pop
    Answer the following questions:
    1. If these operations are performed on a Stack, what values are returned by the five getTop operations (give them in order)? Remember that getTop does not alter the Stack.
    2. Suppose we replace “push” with “enqueue”, “pop” with “dequeue”, and “getTop” with “getFront”. We perform the operations on a Queue. What values are returned by the five getFront operations (give them in order)?
    3. Do a similar replacement, and answer the same question for a Priority Queue in which higher values come out first.

     
    1. Give three applications of Stacks.
    2. Give two applications of Queues.

     
  4. Suppose we implement a Queue (not using a circular buffer), based on each of the data structures below. For each, indicate the order of the three main queue operations (enqueue, dequeue, getFront).
    1. A Doubly Linked List.
    2. A smart array (front of the queue at the beginning of the array).
    3. std::deque.

     
    1. Explain the circular buffer method of implementing a queue.
    2. Suppose we have a circular buffer stored in a 10-element array (with indices 0..9). The first item in the queue is stored at index 7. If later items are stored at increasing indices, where is the 5th item?

     
    1. What is a rooted tree?
    2. Is there such a thing as an “unrooted tree”?

     
  5. A tree is shown below.
    Image not displayed: Tree for rooted tree terminology questions

    The following questions concern the bold node. One of these questions is nonsensical.

    1. How many children does this node have?
    2. How many parents does this node have?
    3. How many descendants does this node have?
    4. How many ancestors does this node have?
    5. How many leaves does this node have?
    6. How many siblings does this node have?
    7. How many nodes does this node’s level contain?

     
    1. What is a “Binary Tree”?
    2. We can define this concept using a “recursive definition”. What does that mean?

     
    1. What is a “full Binary Tree”?
    2. What is a “complete Binary Tree”? Why are they important?
    3. What is a “strongly balanced Binary Tree”? Why are they important?

     
    1. Briefly describe two ways of implementing a Binary Tree. (It is possibly that one of these may not be usable for all Binary Trees.)
    2. One of the Binary Tree implementations that we covered is only good for Binary Trees with certain properties. What ADTs/data structures is this method typically used as the basis of?

     
  6. A Binary Tree is shown below.
    Image not displayed: Binary Tree for traversal questions
    1. Give a preorder traversal of this tree.
    2. Give an inorder traversal of this tree.
    3. Give a postorder traversal of this tree.
    4. This is not a strongly balanced Binary Search Tree. What two properties of a strongly balanced Binary Search Tree does it not have?

     
    1. Explain how to use a Binary Tree to implement an arbitrary (“general”) tree.
    2. Give advantages/disadvantages of representing a general tree as a Binary Tree.

     
    1. What is a “Binary Search Tree”?
    2. Which of the three standard traversals of a Binary Tree is most important for a Binary Search Tree? Why?

     
    1. What are the orders of insert, delete, and retrieve for a Binary Search Tree, using the Binary Search Tree algorithms covered in class?
    2. Why is this a problem?
    3. What can we do about this problem?

     
  7. Suppose the following items are inserted, in the order given, into an empty Binary Search Tree. Draw the resulting tree.
    Items: 3, 2, 6, 7, 1, 4, 5.
     
    1. Describe the Treesort algorithm.
    2. What is wrong with this algorithm?
    3. This algorithm is closely related to another algorithm we studied. Explain.

     

Answers

    1. Singly Linked Lists cannot do efficient insert/remove at end, insert before a given node, remove a given node, or backwards iteration. (Efficient insert at end is possible if removal only happens at the beginning, that is, in the special case when we use a Singly Linked List to implement a queue.) Doubly Linked Lists can do all these efficiently at the expense of a little more memory, time usage, and code complexity.
    2. Because of the above restrictions, and others, we cannot implement a convenient full-featured generic container using a Singly Linked List, in an all-purpose programming language like C++. Thus, full-featured Linked-List-style containers, like the STL’s std::list, are implemented using a Doubly Linked List. (The STL does have a Singly Linked List: std::forward_list, but it is not nearly so convenient to use as std::list.)
    • std::vector. Resizable (“smart”) array.
    • std::basic_string. Much like std::vector, but also has string-related functionality.
    • std::array. An array container, like std::vector and std::basic_string, but not resizable. The array itself, rather than a pointer to it, is contained in the std::array object.
    • std::list. Doubly Linked List.
    • std::forward_list. Singly Linked List.
    • std::deque. Array of pointers to (small) arrays, with reallocate-and-copy placing the data in the middle of the new space. (Thus, essentially a slow-ish smart array in which insertion at either end is amortized constant time, and deletion at either end is constant time.)
    1. A std::deque is implemented as an array of pointers to arrays. Thinking of this as a giant list, we try to keep the data in the middle, so that we can efficiently insert at either end. When the master array fills up, we reallocate and copy, but we do not need to copy the data items themselves, only the array of pointers.
    2. A std::deque has amortized constant time insert/remove at both ends, as well as constant-time look-up by index. No other STL container has both of these properties.
    3. In determining the order of operations, the C++ standard counts only value-type operations as basic operations. Insert at either end of a deque is constant time when you count only value-type operations, and amortized constant time when you count all operations, as we usually do.
    1. Iterator validity refers to when iterators can be safely used (dereferenced, etc.). Reference validity is the same thing for pointers and references. Iterators, pointers, and references can become invalid when the internal workings of data structures are shuffled around (for example, reallocate-and-copy of a smart array).
    2. Essentially, a vector iterator remains valid as long as no item at or before the one it references is inserted or removed, no reallocate-and-copy happens, and the item itself is not destroyed. An Iterator for list or set remains valid until the item it references is destroyed. (Note that destroying a container destroys all the items in it.)
    1. An iterator class often has a single data member: a pointer to a data item or to a node. Iterators differ from pointers in the operations they allow, and how these operations are implemented.
    2. Sometimes we can use a pointer as an iterator. However, for node-based data structures, raw pointers are not appropriate, because the built-in operators do not do what we want them to. In particular, operator++ takes a pointer to the next spot in memory. We want it to take our iterator to the next item in our sequence, which may not be immediately after the current item in memory.
    1. 1, 3, 4, 1, 2.
    2. 2, 2, 1, 3, 4.
    3. 2, 3, 4, 2, 1.
    1. Here are four applications of Stacks:
      • Eliminating recursion.
      • Parsing expressions (or programs or …).
      • Storing state in a program in which temporary state changes are made, which need to be undone later.
      • Executing a program (put return addresses and local variables on the Stack).
    2. Here are four applications of Queues:
      • Storing jobs waiting for an I/O device (e.g., a printer).
      • Storing user-input events for a GUI-based application.
      • Storing requests for a lock on shared data, in a multi-threaded application.
      • Doing a breadth-first search of a graph.
    1. Doubly Linked List. enqueue: constant. dequeue: constant. getFront: constant.
    2. Smart array. enqueue: linear (amortized constant). dequeue: linear. getFront: constant.
    3. std::deque. enqueue: linear (amortized constant—constant, by C++ Standard Library defintions). dequeue: constant. getFront: constant.
    1. In a circular buffer, we use an array, and we imagine the beginning and end being joined, so that it forms a circle. Then we can insert and delete at either end of the data without moving any items around, as long as the size of the queue does not exceed the size of the array.
    2. The 1st through 5th items are stored at indices 7, 8, 9, 0, 1, respectively. So the 5th item is at index 1.
    1. A rooted tree is a tree in which a node is designated as the root. All other nodes hang from it, or from previously specified nodes (which hang from nodes, which hang from nodes—and it all eventually gets back to the root).
    2. Yes, there are trees with no root and no parent-child relationships. However, they are less important in the study of data structures.
    1. 3 children.
    2. 1 parent.
    3. 7 descendants.
    4. 2 ancestors.
    5. This is nonsense. A node does not have leaves.
    6. 1 sibling.
    7. There are 3 nodes in this node’s level—including the bold node itself.
    1. A Binary Tree consists of a set \(T\) of nodes so that either:
      • \(T\) is empty (no nodes), or
      • \(T\) consists of a node \(r\), the root, and two subtrees of \(r\), each of which is a Binary Tree:
        • the left subtree, and
        • the right subtree.
    2. To define a term recursively is to write the definition in terms of itself. For example, the above is a recursive definition, since the term “Binary Tree” is used in the statement of the definition.
    1. A full Binary Tree is a Binary Tree in which every node either has two children or no children, and every leaf is at the same level. That is, every level is filled, up to the highest level.
    2. A complete Binary Tree is a Binary Tree in which every level except the highest is filled, and the highest level is full from the left side to somewhere in the middle (or the far right side). Complete Binary Trees are important because they have a nice array representation, since elements are always added in a specific order. A Binary Heap is a special kind of complete Binary Tree.
    3. A strongly balanced Binary Tree is a Binary Tree in which, for each node, the heights of its two subtrees differ by at most 1. Strongly balanced Binary Trees are important because they allow for efficient algorithms (often \(O(\log n)\), when combined with the search-tree idea). An AVL Tree is a special kind of strongly balanced Binary Tree.
    1. Here are the two implementations we discussed:
      • Node/pointer-based. Each node in the tree is a separate block of memory. Nodes refer to their children (and maybe their parents) via pointers. The main data structure includes a pointer to the root node.
      • Complete Binary Tree in an array. We can implement complete Binary Trees by putting their nodes into an array, in order by level, and then left-to-right within each level. We store the array of nodes and the number of nodes. Locations of children and parents are computed via simple formulae. No pointers of any sort (including array indices) need to be stored in the data structure.
    2. The array implementation of a complete Binary Tree is used to implement a Binary Heap, which is used to implement a Priority Queue, and also to do Heap Sort, and some other Heap-based algorithms.
    1. Preorder: 1, 2, 4, 7, 5, 3, 6, 8.
    2. Inorder: 4, 7, 2, 5, 1, 3, 8, 6.
    3. Postorder: 7, 4, 5, 2, 8, 6, 3, 1.
    4. This is not a strongly balanced Binary Search Tree, since:
      • It is not strongly balanced. The two subtrees of the “3” node have heights 0 and 2, respectively, and these differ by more than 1.
      • While it is a Binary Tree, it is not a Binary Search Tree. An inorder traversal (see above) does not give the items in sorted order.
    1. We represent a general tree using a Binary Tree by giving the left & right child pointers special meanings. The left-child pointer points to a node’s first child in the general tree. The right-child pointer points to a node’s next sibling in the general tree.
    2. Representing a general tree as a Binary Tree is very simple and avoids the data structures that may be required to hold pointers to an arbitrary number of children. Further, it is no less efficient than any other implementation for things like tree traversals. However, if random access to large numbers of children is required, this method is poor, since it requires linear time (in the number of children) to access a child pointer. Also, some trees with a fixed limit on the number of children (quadtrees, etc.) need very fast access to child pointers, making this possibly an inappropriate implementation in those situations as well.
    1. A Binary Search Tree is a Binary Tree with one item in each node, such that, for each node, the items in its left subtree are all less-than-or-equal-to its item, and the items in its right subtree are all greater-than-or-equal-to its item.
    2. The inorder traversal is by far the most important, since it gives the items in sorted order.
    1. For a Binary Search Tree, the insert, delete, and retrieve operations are all linear time. (They are logarithmic time if the tree is strongly balanced; however, it might not be strongly balanced. Even if it is, it might not stay strongly balanced.)
    2. This is a problem, because we would like to use a Binary Search Tree to implement a Table ADT, but its single-item operations are too slow to be the basis for an efficient Table.
    3. Here are two possible solutions:
      • Keep the tree balanced during insertion and deletion (for example, by using the AVL Tree idea).
      • Or, even better, use some other Table implementation, like a Red-Black Tree or a Hash Table.
  1.  
    Image not displayed: Binary Heap resulting from given insertions
    1. The Treesort algorithm is a general-purpose comparison sort. It uses a Binary Search Tree: begin with an empty tree, insert each item into the tree, then do an inorder traversal of the tree to recover the items in sorted order.
    2. First of all, Treesort has the same problem as Quicksort: it is \(O(n^2)\), even though it has decent performance for randomly ordered data. Second, like unoptimized Quicksort, Treesort has particularly bad performance for already sorted data. Third, Treesort is not in-place, as Quicksort is. Because of these problems, Treesort is never used in practice.
    3. Treesort is very much like Quicksort, disguised and made a little worse. In both, we pick a single item (the root in Treesort; the pivot in Quicksort), then we (recursively) sort the remaining items in two bunches: those less that come before the chosen item and those that come after.