







Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
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
1 / 13
This page cannot be seen from the preview
Don't miss anything!








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.
(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
(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).
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.
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)