Вы находитесь на странице: 1из 7

CS 473 (ug): Combinatorial Algorithms, Fall 2005

Homework 2 Solutions

Note: Please read How to read homework solutions on the homework page
of the course website.

This homework was due on Sep 16, with an automatic extension to Sep 19.
CS 473 (ug) Homework 2 Solutions Fall 2005

1. Problem 4.10:
a) Add the edge e = (v, w) with cost c to the tree T ; this creates a cycle in the tree. Examine
all the edges in the cycle, which can be done by performing a BFS or DFS beginning at v in
the original tree T and searching for the vertex w. Let S be the set of edges of the cycle, not
counting the newly added edge e. Find the edge e0 with greatest cost (say c0 ) in S. If c0 > c,
the old minimum spanning tree is not a minimum spanning tree of the new graph. (We can
remove e0 and add e; this will leave us with a spanning tree that is cheaper than the original
tree by an amount c0 − c.)

If c0 ≤ c, the original tree T is a minimum spanning tree of the new graph. Any spanning
tree that does not use the new edge e is a spanning tree of the original graph, and we already
know that T costs less than or as much as any such tree. Consider the lowest cost spanning
tree T 0 that does use e. Removing e from T 0 leaves us with two connected components, with
v in one component, and w in the other. Some edge from the set S must connect these
components, because S is a collection of edges that forms a path from v to w. Let s be this
edge. Since the most expensive edge in S costs c0 ≤ c, the cost of s is ≤ c. Therefore, if we
remove e from T 0 and add s, we have a new spanning tree that costs at most as much as T 0 ,
and does not use the edge e. But T costs less than or as much as any such edge, and so the
cost of T is at most the cost of T 0 . Therefore, T is a minimum spanning tree of the new graph.

The running time of the algorithm to determine if T remains a minimum spanning tree is
Θ(|V |), because we simply have to perform a BFS or some similar traversal in T .

b) Our algorithm to construct the new MST is similar to the one for part (a). Find the
largest edge e0 with cost c0 in the cycle created by adding e to T . Remove this edge, and add
e; this is the new minimum spanning tree T 0 . As in part (a), this takes Θ(|V |) time.

It may seem like we’re done at this point, but all we’ve shown so far is that we can construct
a spanning tree T 0 of the new graph that is cheaper than the minimum spanning tree T of
the old graph. We have not shown that T 0 is an MST of the new graph; we will now prove
this using contradiction. We use the notation C(T ) to denote the total cost of the tree T . We
know C(T 0 ) = C(T )−(c0 −c), because we constructed T 0 from T by removing e0 and adding e.

Suppose by way of contradiction that T 0 is not a minimum spanning tree of the new graph;
this implies that there is another spanning tree Tmin such that C(Tmin ) < C(T 0 ) < C(T ).
Tmin must use the edge e, if not, it would be a spanning tree of the original graph with cost
less than C(T ), which contradicts the fact that T is an MST of the original graph.

Now remove the edge e from Tmin ; this leaves us with two connected components, one con-
taining v and the other containing w. Call these components A and B respectively. Consider
the vertices that are on the path from v to w in the original tree T . Some of these vertices
must be in A, and others in B (we know at least v ∈ A, u ∈ B). Now examine the vertices
in the order they appeared on the path from v to w in the original tree T ; there must be
some pair (a, b) such that a ∈ A, b ∈ B, and the edge (a, b) was in the tree T . Further, (a, b)
was in the cycle of T created by adding e, and so it has cost cab ≤ c0 . Construct a new tree
Tnew from Tmin by removing e and adding (a, b). Tnew is a spanning tree of the new graph,

1
CS 473 (ug) Homework 2 Solutions Fall 2005

because it has an edge connecting the components A and B, and each of these is a connected
component without cycles. What is the cost of Tnew ?
C(Tnew ) = C(Tmin ) − c + cab
≤ C(Tmin ) − c + c0
= C(Tmin ) − (c − c0 )
< C(T 0 ) − (c − c0 )
= C(T ) − (c0 − c) − (c − c0 )
= C(T )

That is, C(Tnew ) < C(T ). Tnew is a spanning tree of the new graph that does not include the
edge e; hence it is a spanning tree of the original graph. But then we have a spanning tree of
the original graph with cost less than that of T , which was an MST. This is a contradiction;
and hence, T 0 must be an MST of the new graph.

Note: If we were allowed to assume that the edge weights were all distinct, we could simplify
some of the arguments above considerably (by using the cycle property, for example).

2. Problem 4.18: This problem requires you to find the fastest way to travel through a directed
graph G(V, E) from a designated start vertex to a designated destination vertex. This is very
similar to the problem of finding the shortest path in a directed graph (which Dijkstra’s al-
gorithm solves); the only real difference is that in our problem, edge weights are not fixed,
but can change during the course of the trip. The key insight here is that the edge weights
can only change in certain constrained ways; the constraints ensure that Dijkstra’s algorithm
solves this problem as well.

Since Dijkstra’s algorithm is described in the textbook (and has been covered in CS 225),
you can refer to it for information about the algorithm, running time, implementation details,
etc. Here, we’ll describe the main idea of the algorithm, and prove that it solves this problem.

With each vertex, we associate a time t; for any vertex v, tv represents the least amount
of time it will take us to reach v using explored paths (which we will define more precisely
later), beginning at the start vertex (call it s) at time 0. As we explore more and more of the
graph, we may find quicker ways to reach v, and so tv may decrease as the graph is explored.
Let S be the set of explored vertices (again defined more precisely later). When we begin
the algorithm, S is empty (we haven’t explored anything yet), t(s) = 0 (because we begin at
the start vertex at time 0), and for every other vertex v, tv = ∞ (since we haven’t explored
anything, we don’t know how to reach any of the other vertices).

In the first stage, we explore outgoing edges from the start vertex. For every vertex u such
that (s, u) ∈ E, we query the website with edge (s, u) and time 0; this will tell us how long
it takes to get to u if we travel the edge (s, u) at time 0. For each such vertex u, we set tu
equal to the value returned by the website. We then add the start vertex s to S, the set of
explored vertices, because we’ve explored every edge coming out of it.

2
CS 473 (ug) Homework 2 Solutions Fall 2005

Which vertex should we explore next? Can we pick any of the vertices adjacent to s? We
already know some path to each of these vertices, so perhaps we could arbitrarily pick one
to continue our exploration from. This doesn’t work well; suppose we pick such a node u
and begin exploring further from it, we may find paths to vertices x, y and z, use them for
further exploration, and so on. Later on, we may find a quicker way to reach u. But this
will probably give us a quicker way to reach x, y, z and anything else we found via u, which
means that we will have to explore that entire portion of the graph again. Since we’d like
to avoid performing redundant work, the natural idea is to begin exploring from a node v
only if we know that we will never have to come back to v and begin exploring from there
again. Another way of saying this is that we would like to begin exploring from a vertex v if
we know that we already know the quickest way to reach v, so tv will never decrease from its
current value.

How do we find such a vertex? After the first stage, we can select the vertex v which can be
reached most quickly from the start vertex. We will never be able to find a quicker way to
reach v because travelling to any other vertex from the start vertex will take more than tv
units of time. Since we can’t go backwards in time, we can’t get to v more quickly by going
to some other vertex first and then going to v. So we add v to S, the set of explored vertices,
and explore further from v. For every vertex w adjacent to v, we query the website with edge
(v, w) and time tv . This gives us a way to reach w; if this is better than our currently known
quickest way to reach w, we update tw .

In the ith stage, we already have an explored set S of size i − 1. To decide which vertex to
begin exploring from next, we find the vertex v with least value of tv . We know that we have
found the quickest way to reach v because (inductively) we’ve already found the quickest way
to reach everything in S, and for every vertex u outside S, we know the quickest way to reach
u if we can only use vertices in S (this is what it means to have explored from every vertex
in S). To see that we cannot reach v quicker than tv , note that we already know the quickest
way to reach v using only vertices in S. Suppose there is a way to reach v more quickly going
outside S; let w be the first vertex outside S on this path. How long does it take us to reach
w? Because we selected v as the vertex that could be reached most quickly using S, tw > tv .
Since we can’t go back in time, using w can only make it take longer to reach v. Therefore,
we’ve found the shortest path to v. So now add v to S, and explore from it. That is, for
every vertex x adjacent to v, we query the website with the edge (v, x) and time tv , and get
the time to reach x using v. If this is better than the previously found tx , we update tx with
the new value.

We repeat this algorithm until we add the destination vertex to our set S; at this point, we
know we have found the shortest path to it, and so we can terminate the algorithm.

Common mistakes: Several people had the idea of using Dijkstra’s algorithm, but didn’t
go beyond that. Merely saying “We can solve this with Dijkstra’s algorithm” is insufficient.
You have to show why Dijkstra’s algorithm (which we originally described with fixed edge
weights) works correctly in this situation. It isn’t obvious that it works at all; there’s no
immediate reason to think that the algorithm would be able to correctly find shortest paths
when edge weights change mysteriously during the course of the algorithm.

3
CS 473 (ug) Homework 2 Solutions Fall 2005

A few people thought about using Minimum-Cost Arborescences. (An arborescence is some-
thing like a directed version of a spanning tree.) This doesn’t work, because a tree of least cost
that covers the entire graph doesn’t necessarily have the shortest path between the source
and destination. Fig 1 shows a counterexample: the minimum-cost arborescence uses the
edges AB and BC, but the shortest path from A to C is just the single edge AC.

Figure 1.

The most common error was trying to use something like a breadth-first or depth-first search.
The problem with this approach is essentially the one in the discussion above, where we
explain why we can’t explore from an arbitrary vertex. Even if you perform a breadth-first
search beginning with the quickest-to-reach node, there’s no guarantee it’ll continue to work
correctly after the first step. Typically, this kind of approach suffers from one of two problems;
which one depends on what you do if you later find a better path to a vertex you already
explored from:
a) If you ignore it, then you run the risk of ending up with an incorrect solution
b) If you don’t ignore it, then you have to explore from this vertex again. There are patho-
logical graphs which could require you to duplicate your work so often that you end up taking
exponential time.

Note that on unweighted graphs, breadth-first search and Dijkstra’s algorithm are essentially
the same. On other graphs, Dijkstra’s performs a kind of ’best-first’ search. Instead of search-
ing deep like DFS or near the top like BFS, Dijkstra’s algorithm always searches beginning
from the ’best’ vertex, that is, the one which can be reached in least time. ‘Best-first’ implies
that we can never later find a better path than the one we have, so we never face the problems
described in the previous paragraph.

3. Problem 4.19:
This problem requires you to find a spanning tree T of a graph G such that for every pair
of vertices (u, v), the bottleneck between u and v in T is the same as the bottleneck between
u and v in G. Any algorithm to produce a maximum spanning tree (take any minimum
spanning tree algorithm and exchange the role of large and small edges) actually constructs
such a tree T .

To prove that this works, think about the (modified) reverse-delete algorithm. We have a
connected graph that is not a spanning tree; therefore, it must have a cycle. Remove the
smallest edge from this cycle; if there is a tie for the smallest edge, remove any one of the
smallest edges. It is easy to see that for every pair of vertices, the new graph has the same

4
CS 473 (ug) Homework 2 Solutions Fall 2005

bottleneck as the original graph. Suppose some path between vertices u and v used the edge
(x, y) that was deleted; in the new graph, we can replace the (x, y) edge in this path with the
rest of the cycle from x to y. We now have a new path from u to v that is identical to the
old path, except that (x, y) has been replaced by some set of edges, each of which is at least
as large as (x, y) (because (x, y) was the smallest in the cycle). Therefore, the bottleneck
between u and v in our new graph is unchanged.

Therefore, we have a new connected graph with one fewer edge than the old one, but with
the same bottleneck for every pair of vertices. If this graph is not a spanning tree, there
must exist another cycle, and we can again remove the smallest edge without hurting any
bottleneck. This process can be repeated until we are left with a spanning tree. Since at
each stage, the bottleneck for every pair of vertices is the same as in the previous stage, the
bottlenecks in the final tree are the same as in the original graph.

Another approach would be to reverse Kruskal’s algorithm; sort the edges in decreasing order
of weight, instead of increasing, and then run Kruskal’s algorithm to construct a maximum
spanning tree. We prove by contradiction that for every pair of vertices (u, v), the bottleneck
between u and v in the spanning tree T we construct is the same as in the graph G.
Suppose there were some pair of vertices (u, v) for which this were not true; this implies that
the bottleneck edge between u and v (say edge e, with weight b) is smaller than the bottleneck
edge e0 with weight b0 in the graph G. Since the best path from u to v in the graph is different
from the path from u to v in T , some portion of these two paths must form a cycle. The
edge e must be the smallest edge in the cycle, because e is the smallest edge along the path
in the tree, and since the path in the graph has bottleneck greater than b (the weight of e),
every edge along the path in the graph has weight greater than b. But the cycle property
of maximum-spanning trees (analogous to the property for minimum spanning trees) states
that the smallest edge in any cycle can never be in a maximum spanning tree. But then
our algorithm could not have selected e, because it is the smallest edge in a cycle. This is a
contradiction, and so there cannot be a pair (u, v) such that the bottleneck between u and v
in T is less than the bottleneck between them in G.

Note: The Kruskal’s algorithm-based argument above is not quite a proof, because I skipped
over a few details. For one thing, we never considered cycle properties for maximum spanning
trees; a more serious objection is that the cycle property strictly applies only to graphs where
all the edge weights are distinct. Still, you should be able to convert this into a complete proof
without too much difficulty. I sketched the idea here to illustrate that you could prove this in
different ways, based on any MST algorithm. The first argument (based on the reverse-delete
algorithm) is a correct proof, with no assumptions such as distinct edge-weights.

Common Mistakes: Most people correctly realized that a maximum spanning tree would
be helpful, but several groups tried to show that the bottlenecks in the maximum spanning
tree were the best possible for any tree. This is a weaker statement than the one you were
required to prove; you had to show that the bottlenecks were as good as in the original graph.
Most proofs to show that the maximum spanning tree was the best possible tree actually
generalized to show that the maximum spanning tree was as good as the original graph, so I
think the only reason to prove the weaker form is that the question wasn’t correctly read or

5
CS 473 (ug) Homework 2 Solutions Fall 2005

understood.

4. Problem 4.24: A detailed solution will be posted shortly (tonight, at the latest); in the
meantime, you can check the grading rubric for this hw, which describes the main idea of
never adding weight to both children of a node. Here’s a recursive algorithm to correctly (and
optimally) balance the tree:

Balance(Node n)
{
if(isLeaf(n))
return 0
else
{
lc = Balance(leftchild(n))
rc = Balance(rightchild(n))
left_total = lc + weight(leftedge(n))
right_total = rc + weight(rightedge(n))
if(left_total < right_total)
{
weight(leftedge(n)) += (right_total - left_total)
return right_total
}
else
{
weight(rightedge(n)) += (left_total - right_total)
return left_total
}
}
}