Sei sulla pagina 1di 8

U.C.

Berkeley CS170 : Algorithms


Lecturers: Umesh Vazirani & Christos Papadimitriou

Midterm 2
April 1, 2004

Midterm 2
Name:

TA:
Answer all questions. Read them carefully first. Be precise and concise. The number of points indicate the
amount of time (in minutes) each problem is worth spending. Write in the space provided, and use the back
of the page for scratch. Good luck!

Problem 1
(20 points)
6

5
2

In the graph shown above:


(a) In what order are the vertices deleted from the priority queue in Dijkstras algorithm for the shortest
path? (Start node: A.)
I A, B, D, E, C.
(b) In Prims algorithm for the minimum spanning tree? (Start node: A.)
I A, B, D, E, C. Note that, while this coincides with Dijkstras traversal order for this graph, that doesnt
always happen.
(c) In what order are the edges added in Kruskals algorithm?
I (B,D); (D,E); (A,B); (B,C)
(d) Show the union-find trees at the end of Kruskals algorithm (in case of a tie in rank, the lexicograpfically
first node becomes root).
I Union(B,D) puts B as the parent (by the lexicographical constraint). Union(D,E) links E to B since
Find(D) returns B. Union(A,B) and Union(B,C) also link A and C to B. This yields:

Problem 2
True or false? Provide a brief explanation. The space provided should suffice.
1. (3 points) Suppose all edge weights are different. Then the shortest edge must be in the minimum
spanning tree.
I True. Let e = (u, v) be the shortest edge. Suppose there exists an MST T of G that does not
contain e. Then adding e to T creates a unique cycle. Since all of the edge weights are unique and
e is the shortest edge, there must be some edge e0 in the cycle such that w(e0 ) > w(e). Removing
e0 creates a new spanning tree T 0 = T {e} {e0 }. Since w(T 0 ) < w(T ), T is not a minimum
spanning tree.
There are a few other ways to argue that e must be in an MST of G. One way is to argue that
Kruskals algorithm will always put e in the MST it finds, and then use part (3) to argue that
Kruskals algorithm finds the only MST. Note that it is not enough to just say that Kruskals
algorithm always puts e in the MST it finds.
2. (3 points) Suppose all edge weights are different. Then the longest edge cannot be in the minimum
spanning tree.
I False. Let G be a tree. Then the MST of G is G itself, so the longest edge of G is in its MST.
3. (3 points) Suppose all edge weights are different. Then the minimum spanning tree is unique.
I True. Suppose there exist two distinct MSTs T1 and T2 of G. Let E be ordered e1 , e2 , e3 , . . . in
increasing order. Consider the first edge ei that belongs to T1 and not to T2 , and the first edge ej
that belongs to T2 and not to T1 . Suppose, without loss of generality, that ei is shorter than ej .
Adding ei to T2 creates a unique cycle in T1 . There must be some edge ek in the cycle such that
k > i, since otherwise T1 would have a cycle. But in this case w(ek ) > w(ei ) so we can remove ek
and get a lighter tree T = T2 {ei } {ek }. So T2 was not an MST.
Again, there are a number of ways to prove this. However, it is not sufficient to simply say that
Kruskals algorithm only finds one MST when the edge weights are distinct.
4. (3 points) Suppose all edge weights are different. Then the shortest path from A to B is unique.
I False. Two disjoint sets of integers can easily sum up to the same number. A simple counterexample is the following graph (for shortest paths from A to C):

A
2

C
1

B
5. (3 points) There is an efficient algorithm for finding the longest (highest length) path in a dag.
I True. Negate all the edges. The shortest path in the resulting graph is the longest path in the
original graph. Since the graph is a DAG, there can be no negative cycles in the resulting graph.
Thus, all we need to do is run Bellman-Ford on the graph with negated edges. This yields a
reasonably efficient O(V E) running time.
3

6. (3 points) There is an efficient algorithm for finding the longest (highest length) path in a graph with
only positive weights.
I The question is poorly stated. Do we want the longest path in general, or the longest simple path
(that is, the longest path that does not visit any node more than once)? And if we want the
longest path in general, what does this mean if there is a positive cycle? Thus, we gave full credit
for three different responses:
(a) False. It was mentioned in lecture that finding the longest (simple) path in general graphs
is very hard comparably hard to the infamously hard Travelling Salesman Problem (that
is, NP-Complete). This was the answer we were hoping to see.
(b) True. Negate all the edges, and run Bellman-Ford. If the distances are still changing after the
|V | 1th iteration, there is a positive cycle in the original graph, which renders the problem
meaningless. Otherwise, there is no positive cycle that can be abused by the longest path,
so the shortest path in the negated-edge graph is our output.
(c) False. If there is a positive cycle that the path can reach, the longest path is infinitely long
outputting such a path would require infinite time, which is certainly not efficient. Hence,
there is no algorithm that can be efficient in the worst case.
7. (3 points) There is an efficient algorithm for finding longest (highest length) paths in a graph with only
negative weights.
I True. We can negate the edges and run Dijkstras algorithm. Negating the edges will produce a
graph with all positive edge weights. We can run Dijkstras algorithm on this graph to find the
shortest path. The shortest path on this graph corresponds to the longest path on the original
graph.
8. (3 points) Any operation in the union-find data structure takes at most O(log n) time.
I True. Every operation in the union-find data structure takes time at most a constant factor times
the running time for the find operation. The find operations running time is O(h), where h is
the height of the tree it searches, since find(x) begins at element x and follows the path to xs
parent, and then to its grandparent, and so on, until it reaches the root. Since we make unions
carefully, by always making the root node of lesser rank point to the root node of greater rank,
the height of any tree is bounded by log n.
9. (3 points) Even without path compression, any operation in the union-find data structure takes at
most O(log n) time.
I True. The argument in part (8) did not rely on path compression. Path compression only makes
the path that the find operation follows shorter.
10. (3 points) Any operation in the union-find data structure takes at most O(log n) time.
I False. This is the amortized cost, not an upper bound on the cost of any operation. A single
operation, even with path compression, could take as long as O(log n).

Problem 3
(15 points) Suppose that, in the single-source shortest path problem, we wish to find not just any shortest
path, but among those the shortest path that has the fewest hops.
There are at least two ways to do this: (a) Modify slightly Dijkstras algorithm by adding an array (besides
dist and prev) and adding a line to the update step, or (b) Apply the original Dijkstra algorithm but with
the edge weights changed slightly to reflect our new interest in few hops.
Describe briefly one of these ways (or, if you prefer, your own new idea for an algorithm for this problem).
I (a) We add an array (call it hops[v]) indexed by vertices, and initialized with hops[v] = for v 6= s,
and hops[s] = 0. The idea is that we run dijkstras algorithm and at all points in time, the
tuple (dist[v], hops[v]) carries the distance and number of hops of the current shortest path of the
minimum number of hops through node v.
There are two ways of achieving this invariant. Either we modify the deletemin() call to the heap
to return the node with the current minimum distance (or if there are multiple ones, the one with
the minimum hops) and then run update as normal. Or else, we modify the update function to
read:
if(dist(v)>dist(u)+l(u,v) ||
(dist(v)==dist(u)+l(u,v) && hops(v)>hops(u)+1))
dist(v)=dist(u)+l(u,v)
hops(v)=hops(u)+1
prev(v)=u
changekey(v,dist(v))
The runtime will be the same as dijkstras O(V log V + E) since in either case, we are only making
a constant change per iteration of the inner loop.
We may argue correctness by considering, for any destination t, the shortest path that has the
minimum num of hops. We want to say that when we pop a node along this path from the heap,
that it has the same dist(v) and hops(v) values as it does on this path. We show this inductively:
it is certainly true for s. Now assume that we pick a point on the path. We know by the inductive
hypothesis, that the node before it on this path, u, had the correct dist(u) and hops(u) values
when popped from the heap. This means that dist(v) must be at most dist(u) + l(u, v) and
hops(v) at most hops(u) + 1. They also are at least these values, or else there would be a better
path than the one we were considering. Now we must show that v has not been popped from
the heap already. But this follows for the same reason that it was true in the original dijkstras
algorithm (see lecture notes for the argument).
Note: many students didnt change the condition in the if statement, and just put hops(v) =
hops(u) + 1. This will end up returning the length of the shortest path that you would find in the
original algorithm which may not be the shortest one.
(b) This was a tough one. No one got this completely right. Many people got the basic gist: add a
very small value to each edge to bias toward shorter paths. Yes, this means that if two paths were
of equal weight before, then the shorter one ill be chosen in the new graph. However, you have
to show that the increments are small enough so that you dont get a new shortest path that just
happened to have a very small amount of extra weight from the old one, but was dramatically
shorter. Take the following example:

where the path from s to t has weight 2.0000000001 and the path from s to a and from a to t are
both 1. Adding even a very small amount (like 0.0001) to all of the edges will cause an error.
Anyway, the correct answer is to add an  to each edge, where  is less than A/V , where
A=minimum difference between any two different-length paths in the graph and V =num of vertices.
If we have integer weights, then A = 1.
If not, then its not feasible to calculate this. Instead, just run the original dijkstras algorithm,
and get the length of a shortest path. Then use progressively smaller values of  and run Dijkstras
until you get a path that has equivalent distance to the shortest path in the original graph. This
is a feasible answer.

Problem 4
(15 points) Given an array of integers a1 , a2 , . . . , an , positive and negative, such as 1, 3, 2, 7, 4, 2, 2, 3, 1,
you want to find the largest sum of contiguous integers; in this case it would be 4 + 2 2 + 3 = 7.
We can accomplish this by (you guessed it!) dynamic programming. For each i = 0, . . . , n define maxsum[i]
to be the value of the largest sum seen so far. We also need maxsuff[i] to be the largest sum of a suffix
ending at ai . (In the array above, maxsum[4] = 5, maxsuff[4] = 0).
Fill in the blanks in the dynamic programming algorithm:

(a) initialize: maxsum[0] =

maxsuff[0] =

I There are several ways to initialize. Credit was given for answers that were consistent with the answers
proposed for part (b). For example, one could initialize with maxsum[0] = 0 and maxsuff[0] = 0.
(b) iteration: for i = 1, . . . , n 1 maxsum[i + 1] =
I Again, several answers were acceptable. For example, max{maxsum[i], maxsuff[i + 1]}

maxsuff[i + 1] =
I max{maxsuff[i] + ai+1 , 0}

(c) What other data structure do you need in order to recover in the end the beginning and end of the
maximum contiguous sum?
I The most common approach was to use two pointers to keep track of the beginning and the end of
the best sum seen so far. However, one also needs a pointer to keep track of the beginning of the best
suffix in order to be able to update the two pointers properly.
It is possible to find the beginning and the end without keeping track of any new information. Once we
are done, we can search the maxsuff array for the element that equals maxsum[n]. This marks the end
of the desired sum. Then we can start at this point in the original sequence and proceed backwards,
adding terms until we reach the desired sum.

(d) What is the running time of the algorithm?


I Credit was given for answers that were consistent with the proposed answers to the previous questions.
In this case, calculating each element of our arrays takes constant time. Updating the pointers takes
constant time as well. The total running time is then O(n).

Potrebbero piacerti anche