CS 506: Solution to Test #1 |
Problem 1.
Let us restrict the problem to finding the upper common tangent of the two
upper hulls -- that is, the top half of each hull. We can certainly extract
the upper fulls from the full hulls in linear time.
If we draw a half-line (an oriented line) from a point on the left-hand side
hull to a point on the right-hand side hull, we can test the two neighbors of
the right-hand side point and distinguish three cases: (i) the predecessor
is below the line and the successor is above it; (ii) the predecessor is
above the line and the successor below it; and (iii) both points are below
the line. In the last case, we have found the tangency point on the right-hand
side hull for the chosen point on the left-hand hull. In the first case,
we need to move forward (toward larger abscissae) along the upper hull,
while in the second case we need to move backward. Thus, in logarithmic time,
we can easily find the tangency point on the right-hand side hull for any
given point on the left-hand side hull. How then to we determine the proper
point on the left-hand side hull?
Well, if we now consider the leftward extension of the tangent, we can run the same case analysis, this time with the predecessor and successor of the left-hand side point, with similar results. This means that we can also conduct a binary search on the left hull. However, at this point, it would appear that we will then incur a running time of logn*logn, not just logn, since a binary search on the right is embedded within each trial on the left. The trick, then, is to prevent that two-level search.
Observe that moving the point on the left-hand side can only move the tangency point on the right-hand side in one direction: for instance, if the point on the left moves forward, so does the tangency point on the right-hand side. Thus we do not need to run a full binary search on the right-hand side; but does this observation gain us enough? Well, it points out that the reverse observation also holds: when the tangency point moves on the right-hand side, the matching tangency point on the left-hand side must also move in the same direction. We can thus play a game of "ping-pong" between the two upper hulls: start a the bottom of the left-hand side polygon, find the tangency point on the right-hand side one; then using that point as pivot, find its tangency point on the left-hand side polygon; then use this new point as a pivot and find its tangency point on the right; etc. In other words, we do not nest the binary searches, but simply alternate them; they progress monotonically to the upper tangent. How many steps does this all take?
Curiously enough, it is actually simpler to use linear search, since it is monotonic: the pivot points move in only one direction and so can each move by at most O(n) steps, so that the running time with linear search cannot exceed O(n). One would expect the binary search version to be at least as good, but its analysis is harder, since it could easily search some of the same points over and over again, unlike the linear search.
Problem 2.
Obviously, identifying the closest pair on a single staircase is trivially
doable in linear time, as the only candidates are consecutive "steps" of the
staircase (the L1 norm is a metric and so obeys the triangle inequality). The
problem thus reduces to finding a pair of vertices, one on one staircase and
the other on the other staircase, that is closest. We can trivially do that in
quadratic time, but that is quite uninteresting. Can we do it in nlogn time?
or even in linear time (since both staircases are sorted)?
The answer is yes to both. Let us skip directly to a linear-time solution. In an L1 metric, the locus of points within distance T of a point is an 45-degree axis-aligned square (a diamond) of diameter 2T centered at that point. In order for a pair of points from the two staircases to be closer to each other than the closest pair on a single staircase, we must find a point from one staircase within that locus for a point on the other staircase, using the distance between the closest pair on the same staircase as initial value of T. Since the process is symmetric, let us assume that we will use staircase A for the centers of the loci and staircase B for candidates. Note that the loci cannot extend past one step of staircase A in either direction, by our choice of T. In particular, given point Pi on staircase A, the only candidates from staircase B are those points with abscissae larger than that of Pi-1 and smaller than that of Pi+1 and with ordinates differing from that of pi by at most T, a subset that we can of course identify easily in linear time since points on each staircase are sorted in order of both abscissae and ordinates. Given that the minimum distance between points on staircase B is also T, the number of points that can meet these conditions is a constant. Hence we can use brute-force search for each list of candidates and still complete the scan in linear time. Whether or not the two staircases intersect is immaterial.
Note that this problem is a special case of our nearest-neighbor pair problem, in which the points are given to us already sorted on both axes, as well as already decomposed into two subsets, even though these two subsets do not correspond to any kind of optimal divide-and-conquer strategy.
Problem 3.
(i) The simplest implementation is TreeSort: use a simple binary search tree to
maintain the processed values. Because the location of the next element
within the current sorted list is distributed uniformly at random, the
running time is described exactly by the average-case recurrence relation for
quicksort and so has solution O(nlogn).
(ii) Remember that a conflict is an unprocessed object whose processing
will cause the alteration of some structure defined when processing the previous
elements. Thus conflicts appear to be elements that have not yet been sorted;
what they conflict with is perhaps less obvious, however. Note that adding
a new element will disrupt (one or) two adjacencies of already processed
elements, so what gets damaged by the conflict is element adjacencies.
Adjacencies are not directly maintained by TreeSort, but intervals are,
which amounts to the same thing.
(iii) Actually, the two are identical, although this may take a bit of
looking. Consider the randomized version: we pick a threshold uniformly
at random, partition the set into two subsets, then recurse on the two
subsets. Consider now the incremental version: we pick a first element and
put it at the root of the binary search tree; thereafter, all elements will
be partitioned into the left and right subtrees, as we handle them, which
is exactly what the partitioning step in the randomized version does.
In other words, choosing a random partitioning element within the subset
to be processed is the same as being fed a random element from that same subset
and asked to insert it into the sorted order: both induce a partition of the
subset. Returning to the issue of conflicts, then, we can see that what gets
destroyed is an interval between two consecutive elements in the already
sorted set; and a conflict list can be viewed as an assignment of each
unprocessed element to an adjacency in the already sorted set. TreeSort
as we implemented it above does not store nor use conflict lists -- instead,
it uses a dynamic element location structure -- but we could have designed
it to do so, replacing the slightly more sophisticated tree by a collection
of lists.
Problem 4.
This is in the spirit of the randomized incremental convex hull algorithm
and also of our randomized incremental 2D linear-programming solution.
Given a collection of halfplanes, we will start with a single halfplane,
then start adding new halfplanes in random order until all halfplanes
have been processed or the resulting polygon is empty (whichever
comes first), always maintaining a current intersection polygon.
Conflicts come from unprocessed halfplanes; and an unprocessed halfplane conflicts with the current polygon if it does not contain it in its entirety; more specifically, it conflicts with a vertex of the current polygon if it does not contain that vertex, or with an edge of that polygon if it does not contain that entire edge.
In this setting, we can set up a simple conflict graph as a bipartite graph, in which one set of vertices correspond to unprocessed half-planes and the other set to the edges of the current intersection polygon, and in which an edge denotes that the corresponding half-plane does not include the corresponding polygon edge (so that inclusion of that half-plane would require elimination of that polygon edge). To avoid constant confusion, we will not use the word "edge" for the edges of the bipartite graph after this sentence ;-) Note that including a half-plane may force us to eliminate a large number of vertices in the bipartite graph, but we need only one polygon edge to get started, as after that we can just follow the perimeter of the convex polygon in both directions until we re-enter the half-plane or complete the perimeters. This is an efficient process, since we use k+2 tests to eliminate k polygon edges and introduce at most three new ones (two that get shortened and an entirely new chord that is subtended by the boundary of the halfplane). Removing the k corresponding vertices from our bipartite conflict graph and adding the three new vertices can obviously be done in O(k) time; since we cannot create more than three vertices per step, the total work due to handling vertices is linear. We have no reason to require that each polygon edge point to every halfplane that excludes it, nor that every halfplane point to all polygon edges that do not lie within it, since, as we have seen, we need only a starting point, i.e., one polygon edge for each half-plane. This makes our work simpler: we can update the one (if any) polygon edge per half-plane during the perimeter traversal that identifies the k polygon edges to remove. We shall keep as conflict edge for a halfplane an edge of the current intersection that is bisected by the halfplane.
The key observation here is the following: if, when we are done with the perimeter scan and have identified the two edges where the new halfplane crosses, we find that the new chord lies completely within an unprocessed halfplane that did not include one of the eliminated edges, then we can conclude that this unprocessed halfplane is now redundant, because it contains the entire intersection so far. The reason is convexity: since the halfplane did not completely contain one of the edges on the convex chain that is now replaced by its chord, everything below that chord is within the halfplane. This allows us to update the conflict lists easily: as we scan the perimeter, we take note (make a list) of any unprocessed halfplane whose conflict edge is being eliminated; once the scan is done, we test whether the halfplanes on the list contain the new chord; if so, they are eliminated, otherwise the new chord becomes their new conflict edge.
This procedure is not guaranteed to take constant time per edge on the perimeter, because some of these edges (such as past chords) may be on many conflict lists, and testing all resulting halfplanes could result in testing them all and so taking linear time per step and quadratic time overall. But the procedure is simple and its expected running time depends only on the number of halfplanes in conflict with edges on the scanned (and deleted) portion of the convex polygon. To analyze this quantity, we work, as usual, backwards. The last halfplane inserted can be any of the i halfplanes inserted by the end of step i; each of the i halfplanes subtends its own edge (chord) of the intersection, although that edge may have degenerated to empty if the corresponding halfplane was made redundant by later insertions. Since we have at most n-i remaining conflicts, the designated ith chord includes, with probability (1/i), at most (n-i) conflicts; summing over all steps (all i) yields the desired answer.
| Back to CS 506 home page |