CS 225 Second Exam—Sample Exam 2: Data Structures and Software Principles - Prof. John Car, Exams of Data Structures and Algorithms

A sample exam for the university of illinois at urbana-champaign's cs 225 data structures and software principles course. The exam includes multiple-choice questions and problem-solving tasks related to data structures such as binary search trees, avl trees, b-trees, and red-black trees.

Typology: Exams

2010/2011

Uploaded on 06/14/2011

koofers-user-kuc
koofers-user-kuc 🇺🇸

4.7

(3)

7 documents

1 / 13

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
University of Illinois at Urbana-Champaign
Department of Computer Science
Second Examination
CS 225 Data Structures and Software Principles
Sample Exam 2
75 minutes permitted
Print your name, netID, and lab section day/time neatly in the space provided below; print
your name at the upper right corner of every page.
Name: SOLUTIONS
NetID:
Lab Section (Day/Time):
This is a closed book and closed notes exam. In addition, you are not allowed to use any
electronic aides of any kind.
Do all 5 problems in this booklet. Read each question very carefully.
You should have 7 sheets total (the cover sheet, plus numbered pages 1-12). The last sheet is
scratch paper; you may detach it while taking the exam, but must turn it in with the exam
when you leave.
Unless otherwise stated in a problem, assume the best possible design of a particular imple-
mentation is being used.
Unless the problem specifically says otherwise, (1) assume the code compiles, and thus any
compiler error is an exam typo (though hopefully there are not any typos), and (2) assume
you are NOT allowed to write any helper methods to help solve the problem, nor are you
allowed to use additional arrays, lists, or other collection data structures unless we have said
you can.
Problem Points Score Grader
1 12
2 30
3 18
4 15
5 15
Total 90
pf3
pf4
pf5
pf8
pf9
pfa
pfd

Partial preview of the text

Download CS 225 Second Exam—Sample Exam 2: Data Structures and Software Principles - Prof. John Car and more Exams Data Structures and Algorithms in PDF only on Docsity!

University of Illinois at Urbana-Champaign

Department of Computer Science

Second Examination

CS 225 Data Structures and Software Principles

Sample Exam 2

75 minutes permitted

Print your name, netID, and lab section day/time neatly in the space provided below; print your name at the upper right corner of every page.

Name: SOLUTIONS

NetID:

Lab Section (Day/Time):

  • This is a closed book and closed notes exam. In addition, you are not allowed to use any electronic aides of any kind.
  • Do all 5 problems in this booklet. Read each question very carefully.
  • You should have 7 sheets total (the cover sheet, plus numbered pages 1-12). The last sheet is scratch paper; you may detach it while taking the exam, but must turn it in with the exam when you leave.
  • Unless otherwise stated in a problem, assume the best possible design of a particular imple- mentation is being used.
  • Unless the problem specifically says otherwise, (1) assume the code compiles, and thus any compiler error is an exam typo (though hopefully there are not any typos), and (2) assume you are NOT allowed to write any helper methods to help solve the problem, nor are you allowed to use additional arrays, lists, or other collection data structures unless we have said you can.

Problem Points Score Grader

Total 90

  1. [Short Answer – 12 points (4 points each)].

(a) What was the “problem” with using path compression and union-by-height together? That is, what difficulty does using the two techniques together present? Please be specific. (The word “problem” is in quotes because we said it turned out that this “problem” didn’t actually affect things too badly, even if it seems like it would.)

Path compression (potentially) changes the height of the tree, due to (potentially) short- ening a path that was (potentially) the deepest path in the tree. In that case, the height stored at the root would become incorrect, as neither path compression nor union-by- height has any provision for recalculating the correct height in such a circumstance (and adding one would make either algorithm far too expensive, time-wise).

(b) If you have a complete tree of 17 nodes, how many nodes are on the deepest level?

(c) Insert the integers 1 through 6 , in that order, into an AVL tree. Draw the resulting tree. How many rotation operations, total, did you perform? Count a “double rotation” operation as one rotation operation.

3 rotations total 4 /
2 5 / \
1 3 6

(c) After performing a combine operation during B-Tree removal, why is it that we need to check the parent for underflow? i.e. justify that such a combine operation could have caused the parent to underflow.

Combining a node merges two nodes into one...so where the parent used to have two nodes, it now has just one. So if the parent had the minumum number of children before, it now has one less than the minimum number of children, because two of those children have been combined into one.

(d) Explain why we can implement a complete tree using an array – that is, explain why we don’t lose information when we get rid of the pointers, i.e. explain why it is that, given an array, we can always produce the corresponding complete tree.

Because of the way a complete tree is defined, the number of nodes defines the structure

  • that is, every complete tree of N nodes, has those nodes in the same place as every other complete tree of N nodes. And the array is just the level order traversal of the complete tree. So, given an array with N elements, we know exactly what structure the tree is supposed to have, and we know how that tree was traversed to produce the array, meaning we know what array cells correspond to what nodes in the tree.

(e) Explain the “repair case” of the Red-Black Tree removal algorithm (the “repair case” was case 2b, where the node we labelled “x” had a black sibling and that black sibling had a red child in the child position further from “x’). That is, explain what we do in this case and justify that it fixes the problems we have without causing new ones.

If the parent of X is black, and we perform a single rotation on the parent toward X, and color the new subtree root (S below) and its two children (P and C below) black: P S / \ /
X S P C / \ / \
L C X L R
R Then we used to have one black node on the way to X, namely, P, but now we encounter both S and P on the way to X. On the other hand, there are still two black nodes on the way to L, two black nodes on the way to C’s left child, and two black nodes on the way to R, since previously, P and S were black and C was red, and now, S and C are both black. In other words, we add one black node to all paths containing X, but the paths that do not contain X have the same number of black nodes as before. Similarly if P is red - same rotation, but S becomes red and P and C (as before) become black. The same analysis as above is true, but since S is red instead of black, all paths have one fewer black node than they did in the above paragraph. Nevertheless, we have added one black node to the paths through X, while not changing the black heights of the paths that do not travel through X.

(b) Explain why it is that the rebalancing work performed by the AVL tree insert or remove is at most O(lg n) on a tree of height O(lg n). Your answer should be detailed enough to convince us you know what you are talking about. You don’t need to justify the steps of the algorithm here – simply indicate what those steps are and their running times – and indicate that those running times add up to what we claim they add up to.

As you return from the BST recursive calls (after doing BST insert or BST remove), at each node there are three things that need doing: i. Recalculate the height of the node, by reading the heights of the two children, choosing the maximum of those two heights, and adding 1. Given a pointer to a node, you can access the node’s children in constant time (ptr->left and ptr->right), and since the height is stored in the node itself, once you have a pointer to a node you can retrieve its height in constant time (ptr->left->height, for example). So reading the heights of the two children is constant time, and the rest is just arithmetic, which is also constant time. ii. Recalculate the balance of the node – that, again, is just arithmetic on the heights of the child nodes, and we’ve already established that arithmetic on the heights of the child nodes can be done in constant time. iii. If the balance is illegal, perform the appropriate rotation. Comparing the balance from (2) to +2 or -2 is constant, decding what rotation to perform will be constant (because you are just reading the heights of the children and grandchildren, all of which are reachable in constant time), and each rotation is a constant time operation, so no matter which one you do, rotation takes constant time. All three of those steps are constant time, so we spend a total of constant time at this node. Since we have O(lg n) levels to move upward through as we return from recursive calls, the total work will be the number of levels multiplied by the time spend on each level, which will be O(lg n) times O(1), or O(lg n).

  1. [List to tree – 15 points].

You have the following two standard node classes (which are publicly accessible and not encapsulated in another class):

class ListNode { public: int element; ListNode* next; };

class TreeNode { public: int element; TreeNode* left; TreeNode* right; };

Write a function LevelOrderToTree. The function should take as parameter a pointer to a ListNode, which is the first element of a list that represents the level-order traversal of a perfect binary tree. This function should reproduce the binary tree from the level-order listing received as a parameter. That is, LevelOrderToTree should return a TreeNode pointer which will be the root of a perfect binary tree such that, if a level-order traversal is run on it, it will yield the same listing as the one received as parameter. If the parameter ListNode pointer is NULL, the the returned TreeNode pointer should also be NULL. You have one Queue available to you to use as a local variable, if you wish.

TreeNode* LevelOrderToTree(ListNode* head) { // your code goes here

SOLUTION ON NEXT PAGE – essentially a modification of level-order traversal.

  1. [Counting Leaves – 15 points].

You have the following node class available to you, which is publicly accessible and not encapsulated in another class:

class TreeNode { public: int element; TreeNode* left; TreeNode* right; };

Write a function CountLeaves that takes as a parameter, a pointer to a TreeNode, and returns the number of leaves in the tree whose root is that TreeNode. (Hint: Use recursion)

int CountLeaves(TreeNode* ptr) { // your code goes here

if (ptr == NULL) return 0; else if ((ptr->left == NULL) && (ptr->right == NULL)) return 1; else return CountLeaves(ptr->left) + CountLeaves(ptr->right); }

(Counting Leaves, continued)

(scratch paper)