Convex Hull Notes
Convex Hull Notes
572
12.5
Convex Hulls
One of the most studied geometric problems is that of computing the convex hull of a set of points. Informally speaking, the convex hull of a set of points in the plane is the shape taken by a rubber band that is placed around the points and allowed to shrink to a state of equilibrium. (See Figure 12.16.)
(a)
(b)
Figure 12.16: The convex hull of a set of points in the plane: (a) an example rubber
band placed around the points; (b) the convex hull of the points. The convex hull corresponds to the intuitive notion of a boundary of a set of points and can be used to approximate the shape of a complex object. Indeed, computing the convex hull of a set of points is a fundamental operation in computational geometry. Before we describe the convex hull and algorithms to compute it in detail, we need to rst discuss some representational issues for geometric data objects.
573
(a)
(b)
(c)
Figure 12.17: Examples of polygons: (a) intersecting, (b) simple, (c) convex.
574
The function (p1 , p2 , p3 ) is often called the signed area function, because its absolute value is twice the area of the (possibly degenerate) triangle formed by the points p1 , p2 , and p3 . In addition, we have the following important fact relating this function to orientation testing. Theorem 12.8: The orientation of a triplet (p1 , p2 , p3 ) of points in the plane is counterclockwise, clockwise, or collinear, depending on whether (p1 , p2 , p3 ) is positive, negative, or zero, respectively. We sketch the proof of Theorem 12.8; we leave the details as an exercise (R12.4). In Figure 12.18, we show a triplet (p1 , p2 , p3 ) of points such that x1 < x2 < x3 . Clearly, this triplet makes a left turn if the slope of segment p2 p3 is greater than the slope of segment p1 p2 . This is expressed by the following question: y y y y Is 3 2 > 2 1 ? (12.2) x3 x2 x2 x1 By the expansion of (p1 , p2 , p3 ) shown in 12.1, we can verify that inequality 12.2 is equivalent to (p1 , p2 , p3 ) > 0.
p3 y3y2 p2 x3x2 y2y1 p1 x2x1
Figure 12.18: An example of a left turn. The differences between the coordinates
575
Example 12.9: Using the notion of orientation, let us consider the problem of testing whether two line segments s1 and s2 intersect. Specically, Let s1 = p1 q1 and s2 = p2 q2 be two segments in the plane. s1 and s2 intersect if and only if one of the following two conditions is veried: 1. 2. (a) (p1 , q1 , p2 ) and (p1 , q1 , q2 ) have different orientations, and (b) (p2 , q2 , p1 ) and (p2 , q2 , q1 ) have different orientations. (a) (p1 , q1 , p2 ), (p1 , q1 , q2 ), (p2 , q2 , p1 ) and (p2 , q2 , q1 ) are all collinear, and (b) the x-projections of s1 and s2 intersect, and (c) the y-projections of s1 and s2 intersect.
Condition 1 is illustrated in Figure 12.19. We also show, in Table 12.20, the respective orientation of the triplets (p1 , q1 , p2 ), (p1 , q1 , q2 ), (p2 , q2 , p1 ), and (p2 , q2 , q1 ) in each of the four cases for Condition 1. A complete proof is left as an exercise (R-12.5). Note that the conditions also hold if s1 and/or s2 is a degenerate segment with coincident endpoints.
q1 s2 p2 s1 p1
q2
s2 p2 q1
q2 p2 s1
q1 s2
q2 p2 q1 s2
q2
s1 p1
p1
p1
s1
(a)
(b)
(c)
(d)
(p1 , q1 , q2 ) CW CW CW CW
(p2 , q2 , p1 ) CW CW CW CW
Table 12.20: The four cases shown in Figure 12.19 for the orientations specied by
Condition 1 of Example 12.9, where CCW stands for counterclockwise, CW stands for clockwise, and COLL stands for collinear.
576
This shortest chain is the shortest path in the plane that avoids the obstacle P.
Figure 12.21: An example shortest trajectory from a point s to a point t that avoids
577
There are a number of applications of the convex hull problem, including partitioning problems, shape testing problems, and separation problems. For example, if we wish to determine whether there is a half-plane (that is, a region of the plane on one side of a line) that completely contains a set of points A but completely avoids a set of points B, it is enough to compute the convex hulls of A and B and determine whether they intersect each other. There are many interesting geometric properties associated with convex hulls. The following theorem provides an alternate characterization of the points that are on the convex hull and of those that are not. Theorem 12.11: Let S be a set of planar points with convex hull H . Then A pair of points a and b of S form an edge of H if and only if all the other points of S are contained on one side of the line through a and b. A point p of S is a vertex of H if and only if there exists a line l through p, such that all the other points of S are contained in the same half-plane delimited by l (that is, they are all on the same side of l ). A point p of S is not a vertex of H if and only if p is contained in the interior of a triangle formed by three other points of S or in the interior of a segment formed by two other points of S. The properties expressed by Theorem 12.11 are illustrated in Figure 12.22. A complete proof of them is left as an exercise (R-12.6). As a consequence of Theorem 12.11, we can immediately verify that, in any set S of points in the plane, the following critical points are always on the boundary of the convex hull of S: A point with minimum x-coordinate A point with maximum x-coordinate A point with minimum y-coordinate A point with maximum y-coordinate.
b b q p
q p
(a)
(b)
(c)
Figure 12.22: Illustration of the properties of the convex hull given in Theo-
rem 12.11: (a) points a and b form an edge of the convex hull; (b) points a and b do not form an edge of the convex hull; (c) point p is not on the convex hull.
578
a a
(a)
(b)
(c)
(d)
Figure 12.23: Initial four wrapping steps of the gift wrapping algorithm.
579
Each time we rotate our rope around the current peg until it hits another point, we perform an operation called a wrapping step. Geometrically, a wrapping step involves starting from a given line L known to be tangent to the convex hull at the current anchor point a, and determining the line through a and another point in the set making the smallest angle with L. Implementing this wrapping step does not require trigonometric functions and angle calculations, however. Instead, we can perform a wrapping step by means of the following theorem, which follows from Theorem 12.11. Theorem 12.12: Let S be a set of points in the plane, and let a be a point of S that is a vertex of the convex hull H of S. The next vertex of H , going counterclockwise from a, is the point p, such that triplet (a, p, q) makes a left turn with every other point q of S. Recalling the discussion from Section 2.4.1, let us dene a comparator C(a) that uses the orientation of (a, p, q) to compare two points p and q of S. That is, C(a).isLess(p, q) returns true if triplet (a, p, q) makes a left turn. We call the comparator C(a) the radial comparator, as it compares points in terms of their radial relationships around the anchor point a. By Theorem 12.12, the vertex following a counterclockwise on the hull is simply the minimum point with respect to the radial comparator C(a).
Performance
We can now analyze the running time of the gift wrapping algorithm. Let n be the number of points of S, and let h n be the number of vertices of the convex hull H of S. Let p0 , . . . , ph1 be the vertices of H. Finding the anchor point a = p0 takes O(n) time. Since with each wrapping step of the algorithm we discover a new vertex of the convex hull, the number of wrapping steps is equal to h. Step i is a minimum-nding computation based on radial comparator C(pi1 ), which runs in O(n) time, since determining the orientation of a triplet takes O(1) time and we must examine all the points of S to nd the smallest with respect to C(pi1 ). We conclude that the gift wrapping algorithm runs in time O(hn), which is O(n2 ) in the worst case. Indeed, the worst case for the gift wrapping algorithm occurs when h = n, that is, when all the points are on the convex hull. The worst-case running time of the gift wrapping algorithm in terms of n is therefore not very efcient. This algorithm is nevertheless reasonably efcient in practice, however, for it can take advantage of the (common) situation when h, the number of hull points, is small relative to the number of input points, n. That is, this algorithm is an output sensitive algorithman algorithm whose running time depends on the size of the output. Gift wrapping has a running time that varies between linear and quadratic, and is efcient if the convex hull has few vertices. In the next section, we will see an algorithm that is efcient for all hull sizes, although it is slightly more complicated.
580
Figure 12.24: Sorting around the anchor point in the Graham scan algorithm.
3. After adding the anchor point a at the rst and last position of S, we scan through the points in S in (radial) order, maintaining at each step a list H storing a convex chain surrounding the points scanned so far. Each time we consider new point p, we perform the following test: (a) If p forms a left turn with the last two points in H, or if H contains fewer than two points, then add p to the end of H. (b) Otherwise, remove the last point in H and repeat the test for p. We stop when we return to the anchor point a, at which point H stores the vertices of the convex hull of P in counterclockwise order. The details of the scan phase (Phase 3) are spelled out in Algorithm Scan, described in Algorithm 12.25. (See Figure 12.26.)
581
Algorithm Scan(S, a): Input: A list S of points in the plane beginning with point a, such that a is on the convex hull of S and the remaining points of S are sorted counterclockwise around a Output: List S with only convex hull vertices remaining S.insertLast(a) {add a copy of a at the end of S} prev S.rst() {so that prev = a initially} curr S.after(prev) {the next point is on the current convex chain} repeat next S.after(curr) {advance} if points (point(prev), point(curr), point(next)) make a left turn then prev curr else S.remove(curr) { point curr is not in the convex hull} prev S.before(prev) curr S.after(prev) until curr = S.last() S.remove(S.last()) {remove the copy of a}
Algorithm 12.25: The scan phase of the Graham scan convex hull algorithm. (See
Figure 12.26.) Variables prev, curr, and next are positions (Section 2.2.2) of the list S. We assume that an accessor method point(pos) is dened that returns the point stored at position pos. We give a simplied description of the algorithm that works only if S has at least three points, and no three points of S are collinear.
Performance
Let us now analyze the running time of the Graham scan algorithm. We denote the number of points in P (and S) with n. The rst phase (nding the anchor point) clearly takes O(n) time. The second phase (sorting the points around the anchor point) takes O(n log n) time provided we use one of the asymptotically optimal sorting algorithms, such as heap-sort (Section 2.4.4) or merge-sort (Section 4.1). The analysis of the scan (third) phase is more subtle. To analyze the scan phase of the Graham scan algorithm, let us look more closely at the repeat loop of Algorithm 12.25. At each iteration of the loop, either variable next advances forward by one position in the list S (successful if test), or variable next stays at the same position but a point is removed from S (unsuccessful if test). Hence, the number of iterations of the repeat loop is at most 2n. Therefore, each statement of algorithm Scan is executed at most 2n times. Since each statement requires the execution of O(1) elementary operations in turn, algorithm Scan takes O(n) time. In conclusion, the running time of the Graham scan algorithm is dominated by the second phase, where sorting is performed. Thus, the Graham scan algorithm runs in O(n log n) time.
582
(a)
(b)
(c)
(d)
(e)
(f)
(g)
(h)
(i)
Figure 12.26: Third phase of the Graham scan algorithm (see Algorithm 12.25).
583
12.6
584
public class ConvexHull { private static Sequence hull; private static Point2D anchorPoint; private static GeomTester2D geomTester = new GeomTester2DImpl(); // public class method public static Sequence grahamScan (Sequence points) { Point2D p1, p2; copyInputPoints(points); // copy into hull the sequence of input points switch (hull.size()) { case 0: case 1: return hull; case 2: p1 = (Point2D)hull.rst().element(); p2 = (Point2D)hull.last().element(); if (geomTester.areEqual(p1,p2)) hull.remove(hull.last()); return hull; default: // at least 3 input points // compute anchor point and remove it together with coincident points anchorPointSearchAndRemove(); switch (hull.size()) { case 0: case 1: hull.insertFirst(anchorPoint); return hull; default: // at least 2 input points left besides the anchor point sortPoints();// sort the points in hull around the anchor point // remove the (possible) initial collinear points in hull except the // farthest one from the anchor point removeInitialIntermediatePoints(); if (hull.size() == 1) hull.insertFirst(anchorPoint); else { // insert the anchor point as rst and last element in hull hull.insertFirst(anchorPoint); hull.insertLast(anchorPoint); scan(); // Grahams scan // remove one of the two copies of the anchor point from hull hull.remove(hull.last()); } return hull; } } } Code Fragment 12.27: Method grahamScan in the Java implementation of the Gra-
585
private static void copyInputPoints (Sequence points) { // copy into hull the sequence of input points hull = new NodeSequence(); Enumeration pe = points.elements(); while (pe.hasMoreElements()) { Point2D p = (Point2D)pe.nextElement(); hull.insertLast(p); } } private static void anchorPointSearchAndRemove () { // compute the anchor point and remove it from hull together with // all the coincident points Enumeration pe = hull.positions(); Position anchor = (Position)pe.nextElement(); anchorPoint = (Point2D)anchor.element(); // hull contains at least three elements while (pe.hasMoreElements()) { Position pos = (Position)pe.nextElement(); Point2D p = (Point2D)pos.element(); int aboveBelow = geomTester.aboveBelow(anchorPoint,p); int leftRight = geomTester.leftRight(anchorPoint,p); if (aboveBelow == GeomTester2D.BELOW | | aboveBelow == GeomTester2D.ON && leftRight == GeomTester2D.LEFT) { anchor = pos; anchorPoint = p; } else if (aboveBelow == GeomTester2D.ON && leftRight == GeomTester2D.ON) hull.remove(pos); } hull.remove(anchor); } private static void sortPoints() { // sort the points in hull around the anchor point SortObject sorter = new ListMergeSort(); ConvexHullComparator comp = new ConvexHullComparator(anchorPoint, geomTester); sorter.sort(hull,comp); } Code Fragment 12.28: Auxiliary methods copyInputPoints, anchorPointSearchAn-
586
private static void removeInitialIntermediatePoints() { // remove the (possible) initial collinear points in hull except the // farthest one from the anchor point boolean collinear = true; while (hull.size() > 1 && collinear) { Position pos1 = hull.rst(); Position pos2 = hull.after(pos1); Point2D p1 = (Point2D)pos1.element(); Point2D p2 = (Point2D)pos2.element(); if (geomTester.leftRightTurn(anchorPoint,p1,p2) == GeomTester2D.COLLINEAR) if (geomTester.closest(anchorPoint,p1,p2) == p1) hull.remove(pos1); else hull.remove(pos2); else collinear = false; } } private static void scan() { // Grahams scan Position rst = hull.rst(); Position last = hull.last(); Position prev = hull.rst(); Position curr = hull.after(prev); do { Position next = hull.after(curr); Point2D prevPoint = (Point2D)prev.element(); Point2D currPoint = (Point2D)curr.element(); Point2D nextPoint = (Point2D)next.element(); if (geomTester.leftRightTurn(prevPoint,currPoint,nextPoint) == GeomTester2D.LEFT TURN) prev = curr; else { hull.remove(curr); prev = hull.before(prev); } curr = hull.after(prev); } while (curr != last); }