CS-141 Exam 2 Review April 4, 2015 Presented by the RIT Computer Science Community http://csc.cs.rit.edu Linked Lists 1. You are given the linked list: 1 → 2 → 3. You may assume that each node has one field called value and one called next. (a) 1 points to 2 and 2 points to 3. What does 3 point to? The implementer may cause the 3 node to point to either None or a sentinel node. (b) Draw out the linked list structure and add a 5 to the end. +----------+ +----------+ +----------+ +------------+ | value: 1 | | value: 2 | | value: 3 | | value: 5 | | next: ------->| next: ------->| next: ------->| next: None | +----------+ +----------+ +----------+ +------------+ OR +----------+ +----------+ +----------+ +----------+ +-----------------+ | value: 1 | | value: 2 | | value: 3 | | value: 5 | | value: SENTINEL | | next: ------->| next: ------->| next: ------->| next: ------->| | +----------+ +----------+ +----------+ +----------+ +-----------------+ (c) Write pseudocode to add an element onto the end of a linked list. 1 def append ( value , l i n k e d l i s t ) : 2 newend = new node 3 newend . v a l u e = v a l u e 4 newend . next = None # ( or SENTINEL) 5 curend = end node o f l i n k e d l i s t 6 curend . next = newend (d) Looking at the list from (b), you notice that we forgot to add in a 4. What is a procedure for doing this? (There are many possibilities.) Use some variation of this strategy: Find the preceding element’s node (in this case, 3), link its next node to the new node containing 4, then link that new node to what was the following node. 1 Stacks and Queues 2. If we wanted to implement a stack as a linked list, what linked-list operations would correspond to push() and pop()? push( stack, element ) → insertFront( linked-list, element ) pop( stack ) → removeFront( linked-list ) 3. It is possible to implement both stacks and queues using only simple Python lists. (a) Write the following functions that implement stack functionality atop a Python list. Your stack must provide the following functionality: push(lst, elm) — Push elm onto the top of the stack pop(lst) — Return the top element of the stack and remove it from the stack isEmpty(lst) — Return whether the stack is empty peek(lst) — Return the top element of the stack without modifying the stack 1 2 3 4 5 6 7 8 def push ( l s t , v a l ) : l s t . append ( v a l ) def pop ( l s t ) : return l s t . pop ( ) def peek ( l s t ) : return l s t [ −1] def isEmpty ( l s t ) : return ( l e n ( l s t ) == 0 ) (b) Write the following methods to create a queue in similar fashion to previous question (with a Python list as the data structure managing elements ”under the hood”): enqueue(lst, val) dequeue(lst) 1 2 3 4 def enqueue ( l s t , v a l ) : l s t . append ( v a l ) def dequeue ( l s t ) : l s t . pop ( 0 ) (c) Which of the data structures you implemented is more efficient and why? Give a better way to implement the slower structure, and discuss how this would change the time complexity of its operations. Because the queue must be able to modify both ends of the list, it pays an O(n) cost to remove the beginning element during each dequeue operation. This could be reduced to O(1) by using a linked list instead of a Python list. 2 Hashing and Hash Tables 4. Chris made a mistake in his hash table implementation! 1 c l a s s WonkyHashTable ( ) : 2 def i n i t ( s e l f , s i z e =4): 3 s e l f . t a b l e = [ [ ] f o r i in r a n g e ( s i z e ) ] 4 5 def a d d e l e m e n t ( s e l f , e l e m e n t ) : 6 s e l f . t a b l e [ s e l f . bad hash ( e l e m e n t ) ] = e l e m e n t 7 8 def c o n t a i n s ( s e l f , e l e m e n t ) : 9 f o r t in s e l f . t a b l e : 10 i f e l e m e n t in t : 11 return True 12 return F a l s e 13 14 def bad hash ( s e l f , e l e m e n t ) : 15 return l e n ( e l e m e n t ) % l e n ( s e l f . t a b l e ) 16 str ( self ): 17 def 18 s t = ”” 19 f o r t in s e l f . t a b l e : 20 s t += s t r ( t ) + ” \n” 21 return s t 22 23 h t a b l e = WonkyHashTable ( ) 24 f o r elm in ’ I w r e s t l e d a b e a r once ’ . s p l i t ( ) : 25 h t a b l e . a d d e l e m e n t ( elm ) (a) Show what the hash table looks like after the for loop on line 24 completes. [ once , a , [ ] , []] (b) What is wrong with the code? What can we do to make the function behave as Chris expects it to behave? The issue is on line 6. Whenever bad hash() dictates that an element should be placed in an occupied bucket, that bucket’s contents get overwritten! Change s e l f . t a b l e [ s e l f . bad hash ( element ) ] = element to s e l f . t a b l e [ s e l f . bad hash ( element ) ] . append ( element ) (c) Draw the table of the properly behaving hash function. [ [ ’ w r e s t l e d ’ , ’ bear ’ , ’ once ’ ] , [ ’ I ’ , ’ a ’ ] , [ ] , []] (d) Assuming that this hash table will only be used on strings, is the hashing function being used a good one? Why or why not? No: It ignores the fact that most English words are the roughly the same length. The number of collisions is expected to be massive. We should take advantage of the characters in the input strings, not the number of characters. 3 Classes 5. Write one or more classes and maker function(s) to describe the following: • A Hotel – hotel name (a string) – hotel location (a string) – the hotel’s Rooms (a list). • A Room – room price per night (a number) – room capacity (a number) – room number/label (a number). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #! / u s r / b i n / python from r i t o b j e c t import ∗ class Hotel ( r i t o b j e c t ) : s l o t s =(”name” , ” rooms ” , ” l o c a t i o n ” ) def mkHotel ( name , rooms , l o c a t i o n ) : h o t e l = Hotel ( ) h o t e l . name = name h o t e l . rooms = rooms # a l i s t c o n t a i n i n g Room o b j e c t s hotel . location = location return h o t e l c l a s s Room( r i t o b j e c t ) : s l o t s =(”number” , ” c a p a c i t y ” , ” p r i c e ” ) def mkRoom( number , c a p a c i t y , p r i c e ) : room = Room ( ) room . number = number room . c a p a c i t y = c a p a c i t y room . p r i c e = p r i c e return room 4 Sorting 6. Fill in the table for the asymptotic running time of each sorting algorithm. Best Merge sort Quicksort Heap sort Worst Average O(n ∗ log(n)) O(n ∗ log(n)) O(n ∗ log(n)) O(n ∗ log(n)) O(n2 ) O(n ∗ log(n)) O(n ∗ log(n)) O(n ∗ log(n)) O(n ∗ log(n)) 7. What sorting algorithm splits its input list into two other lists, one which has all elements that are smaller than a value (which is randomly selected) and one which has elements that are larger than the randomly selected value? Quicksort. 8. In what scenario does Quicksort experience its worst-case time complexity? You may assume that we always pick the first element as the pivot. Data that is (nearly) sorted or is (nearly) sorted in reverse order. 9. What causes Quicksort to run so slowly on the input you describe in the last question? Quicksort splits its input into two lists based on the value of the pivot. If the pivot is either the smallest or the largest element, then one list will only have no elements, while the others will have all of the elements except the pivot. 10. In Quicksort, why should we select a random pivot value, rather than always pivoting on, for example, the first or last element? With real-world data, we’re more likely to encounter ordered or semi-ordered data than randomized data. This makes it more likely for us run into Quicksort’s worst-case time complexity. We run into this bad time complexity if we select pivots which are near the lowest or highest values. Selecting a random value to pivot on helps us encounter the average case evens out the distribution of ordered and unordered data. Even if we’re getting in sorted data, if we select pivots randomly, we should be able to end up with average time complexity. 5 11. Show the stages of a merge sort and a quicksort on the following list: [3,5,1,3,2,7,9]. Be sure to identify your pivot. Merge sort: 3513279 3129 32 3 537 19 2 1 23 57 9 5 19 3 7 3 57 1239 3 357 1233579 Quicksort (using the first element in the list as a pivot): 3513279 12 1 12 2 3, 3 579 3, 3 5 79 3, 3 5 7 3, 3 5 79 3, 3 579 1233579 6 9 Heaps and Heapsort 12. For a binary heap containing n elements, what is the maximum number of swaps occurring after an insert operation? log2 (n + 1), rounded down. 13. Given a node in an array-based binary heap at index i, where are the indices of both its children? What is the index of its parent? The children are at 2i + 1 and 2i + 2. The parent is at b i−1 c. 2 14. Run a heap sort on the following list: [3,5,1,3,2,7,9], showing the heap at each stage. Be sure to heapify the list first. Heapify: [3, 5, 1, 3, [3, 5, 1, 3, [1, 5, 3, 3, [1, 3, 3, 5, [1, 2, 3, 5, [1, 2, 3, 5, [1, 2, 3, 5, 2, 2, 2, 2, 3, 3, 3, 7, 7, 7, 7, 7, 7, 7, 9] 9] 9] 9] 9] 9] 9] Sort: [2, 3, [3, 3, [3, 5, [5, 9, [7, 9, [9, 7, [9, 7, 9, 9, 3, 3, 3, 3, 3, 7, 2, 2, 2, 2, 2, 2, 1] 1] 1] 1] 1] 1] 1] 3, 7, 7, 7, 5, 5, 5, 5, 5, 9, 3, 3, 3, 3, 7 Note that in a 1 indexed array system, the children would be at 2i and 2i + 1. The parent would be at b 2i c. Greedy Algorithms 15. Given that an algorithm is greedy, is it guaranteed to return an optimal solution? NO. Greedy algorithms always choose the current best solution, which is not necessarily the overall best solution! 16. In the game Black and White1 , the player is faced with a row of identical double-sided chips. You can probably guess what colors the two sides of each chip are. The objective is to flip as many chips as necessary so their exposed colors match that of a target pattern. The catch? Reordering the chips is said to be Impossible by those who seem to know what they’re talking about. The real catch? Flipping a group of consecutive tiles can be accomplished in a single “action.” If every flip takes one “action,” write a function bwMoves that, given a starting pattern and target pattern as equal-length strings, returns the minimum number of actions required to get them to match. For instance, bwMoves( ’BBWBBWBBBB’, ’WWWWWBBWWB’ ) should return 3. 1 def bwMoves ( s t a r t , t a r g e t ) : 2 a c t i o n s =0 3 f i r s t =0 4 for i n d e x in range ( l e n ( s t a r t ) ) : 5 i f s t a r t [ i n d e x]== t a r g e t [ i n d e x ] : 6 i f f i r s t != i n d e x : # Each f l i p works up t o ( b u t not i n c l u d i n g ) 7 a c t i o n s+=1 # t h e i n d e x p o i n t e r . I f f i r s t==index , t h a t ’ s 8 f i r s t =i n d e x+1 # 0 e l e m e n t s , so t h e r e i s n o t h i n g t o f l i p . 9 # ( i . e . There were two no− f l i p s i n a row . ) 10 i f s t a r t [ −1]!= t a r g e t [ − 1 ] : 11 a c t i o n s+=1 12 return a c t i o n s 1 Special thanks to Professor Butler for unwittingly allowing us to rip off his problem. 8
© Copyright 2024