Algorithms Design Exam Help
Algorithms Design Exam Help
f1 = n, f2 = f3 = n +
the correct answers are (f2, {f1, f3}) or (f2, {f3, f1}).
Note: Recall that abc means a(bc) , not (ab )c , and that log means
log2 unless a different base is specified explicitly. Stirling’s
approximation may help for comparing factorials.
Solution:
a. (f5, f3, f4, f1, f2). Using exponentiation and logarithm rules, we
can simplify these to f1 = Θ(n log n), f2 = Θ((log n)n), f3 = Θ(log n),
f4 = Θ((log n)6006), and f5 = Θ(log log n). For f2, note that (log n)n
> 2n for large n, so this function grows at least exponentially and is
therefore bigger than the rest.
b. (f1, f2, f5, f4, f3). This order follows after converting all the
exponent bases to 2. (For example, Remember that
asymptotic growth is much more sensitive to changes in exponents:
even if the exponents are both Θ(f(n)) for some function f(n), the
functions will not be the same asymptotically unless their exponents
only differ by a constant.
c. ({f2, f5}, f4, f1, f3). This order follows from the definition of the
binomial coefficient and Stirling’s approximation. f2 has most
terms cancel in the numerator and denominator, leav- √ ing a
polynomial with leading term n6/6!. The trickiest one is
(by √ repeated use of Stirling), which is about Θ(1.57n/ √
n). Thus f4 is bigger than the polynomi- n als but smaller than the
factorial and nn.
which grows asymptotically faster than nn by a
factor of Θ(n5n+(1/2)(6/e)6n). (Originally, f3 was presented as 6n!
which could reasonably be interpretted as 6(n!), which would put f3
before f1 in the order. Because of this, graders should accept f1 and
f3 in either order.)
d. (f5, f2, f1, f3, f4). It is easiest to see this by taking the
logarithms of the functions, which give us Θ(√ n log n), Θ( n log
n), Θ(n log n), Θ(n2), Θ(log n) respectively. However,
asymptotically similar logarithms do not imply that the functions
are asymptotically the same, so (4log n)3n log 4)3n 6n we
consider f1 and f3 further. Note
(by about a factor of n5n) than
the larger of f1’s terms, so f3 is asymptotically larger.
Rubric:
• 0 points minimum
Rubric:
Note that after removing the item A at index i, if j > i, item B will
shift down to be at index j - 1. Similarly, after inserting A in front of
B, item B will be at an index that is one larger than before, while the
next item in the subsequence to be moved will also be at a larger
index if i > j. Maintaining these indices then results in a correct
algorithm. This recursive procedure makes no more than k = O(k)
recursive calls before reaching a base case, doing O(log n) work per
call, so the algorithm runs in O(k log n) time.
1 def move(D, i, k, j):
2 if k < 1:
3 return
4 x = D.delete_at(i)
5 if j > i
6 j=j-1
7 D.insert_at(j, x)
8 j=j+1
9 if i > j
10 i=i+1
11 move(D, i, k - 1, j)
Rubric:
place mark(i, m) Place bookmark m ∈ {A, B} between the page at index i and the
page at index i + 1 in O(n) time.
move page(m) Take the page currently in front of bookmark m ∈ {A, B}, and
move it in front of the other bookmark in O(1) time.
Solution: There are many possible solutions. First note that the
problem specifications ask for a constant-time read page(i)
operation, which can only be supported using array-based
implementations, so linked-list approaches will be incorrect. Also
note that that until both bookmarks are placed, we can simply store
all pages in a static array of size n, since no operations can change
the sequence of pages until both bookmarks are placed. We present
a solution generalizing the dynamic array we discussed in class.
Another common approach could be to reduce to using two
dynamic arrays (one on either end of bookmarks A and B), together
with an array-based deque as described in Problem Session 1 to
store the pages between bookmarks A and B.
For our approach, after both bookmarks have been placed, we
will store the n pages in a static array S of size 3n, which we can
completely re-build in O(n) time whenever build(X) or place
mark(i, m) are called (assuming n = |X|). To build S:
To support shift mark(m, d), move the relevant page at one of indices
(a1, a2, b1, b2) to the index location (a2 - 1, a1 + 1, b2 - 1, b1 + 1)
respectively, and then increment the stored indices to maintain the
invariants. This algorithm maintains the invariants of the data
structure so is correct, and runs in O(1) time based on one array index
lookup, and one index write. Note that this operation does not change
the amount of extra space between sections P1, P2, and P3, so the
running time of this operation is worst-case.
Rubric:
delete first(): This method only makes sense for a list containing at
least one node, so assume the list has a head node. Extract and store
the item x from the head node of the list. Then set the head to point
to the node a pointed to by the head node’s next pointer. If a is not a
node, then we removed the last item from the list, so set the tail to
None (head is already set to None). Otherwise, set the new head’s
previous pointer to None. Then return item x.
delete last(): This method only makes sense for a list containing at
least one node, so assume the list has a tail node. Extract and store
the item x from the tail node of the list. Then set the tail to point to
the node a pointed to by the tail node’s previous pointer. If a is not a
node, then we removed the last item from the list, so set the head to
None (tail is already set to None). Otherwise, set the new tail’s next
pointer to None. Then return item x.
Rubric:
• 2 points for description and analysis of each correct operation
• Partial credit may be awarded
(b) Given two nodes x1 and x2 from a doubly linked list L, where
x1 occurs before x2, describe a constant-time algorithm to remove
all nodes from x1 to x2 inclusive from L, and return them as a new
doubly linked list
Solution: Construct a new empty list L2 in O(1) time, and set its
head and tail to be x1 and x2 respectively. To extract this sub-list,
care must be taken when x1 or x2 are the head or tail of L
respectively. If x1 is the head of L, set the new head of L to be the
node a pointed to by x2’s next pointer; otherwise, set the next
pointer of the node pointed to by x1’s previous pointer to a.
Similarly, if x2 is the tail of L, set the new tail of L to be the node b
pointed to by x1’s previous pointer; otherwise, set the previous
pointer of the node pointed to by x2’s next pointer to b.
Rubric:
Rubric:
Solution:
1 class Doubly_Linked_List_Seq:
2 # other template methods omitted
3
4 def insert_first(self, x):
5 new_node = Doubly_Linked_List_Node(x)
6 if self.head is None:
7 self.head = new_node
8 self.tail = new_node
9 else:
10 new_node.next = self.head
11 self.head.prev = new_node
12 self.head = new_node
13
14 def insert_last(self, x):
15 new_node = Doubly_Linked_List_Node(x)
16 if self.tail is None:
17 self.head = new_node
18 self.tail = new_node
19 else:
20 new_node.prev = self.tail
21 self.tail.next = new_node
22 self.tail = new_node
23
24 def delete_first(self):
25 assert self.head
26 x = self.head.item
27 self.head = self.head.next
28 if self.head is None: self.tail = None
29 else: self.head.prev = None
30 return x
31
32 def delete_last(self):
33 assert self.tail
34 x = self.tail.
35 item self.tail = self.tail.prev
36 if self.tail is None: self.head = None
37 else: self.tail.next = None
38 return x
39
40 def remove(self, x1, x2):
41 L2 = Doubly_Linked_List_Seq()
42 L2.head = x1
43 L2.tail = x2
44 if x1 == self.head: self.head = x2.next
45 else: x1.prev.next = x2.next
46 if x2 == self.tail: self.tail = x1.prev
47 else: x2.next.prev = x1.prev
48 x1.prev = None
49 x2.next = None
50 return L2
51
52 def splice(self, x, L2):
53 xn = x.next
54 x1 = L2.head
55 x2 = L2.tail
56 L2.head = None
57 L2.tail = None
58 x1.prev = x
59 x.next = x1
60 x2.next = xn
61 if xn: xn.prev = x2
62 else: self.tail = x2