Answers - RIT Computer Science Community

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