跳转到内容

算法实现/几何/凸包/单调链

来自 Wikibooks,开放书籍,开放世界
描绘单调凸包算法的动画

安德鲁的单调链凸包算法 时间内构建了一组二维点的 凸包

它首先按字典顺序对点进行排序(首先按x坐标,如果相等,则按y坐标),然后在 时间内构建点的上包络和下包络。

上包络是凸包的一部分,从上面可以看到。它从最右边的点到最左边的点按逆时针顺序运行。下包络是凸包的剩余部分。

一组点的上包络和下包络

伪代码

[编辑 | 编辑源代码]
Input: a list P of points in the plane.

Precondition: There must be at least 3 points.

Sort the points of P by x-coordinate (in case of a tie, sort by y-coordinate).

Initialize U and L as empty lists.
The lists will hold the vertices of upper and lower hulls respectively.

for i = 1, 2, ..., n:
    while L contains at least two points and the sequence of last two points
            of L and the point P[i] does not make a counter-clockwise turn:
        remove the last point from L
    append P[i] to L

for i = n, n-1, ..., 1:
    while U contains at least two points and the sequence of last two points
            of U and the point P[i] does not make a counter-clockwise turn:
        remove the last point from U
    append P[i] to U

Remove the last point of each list (it's the same as the first point of the other list).
Concatenate L and U to obtain the convex hull of P.
Points in the result will be listed in counter-clockwise order.

请注意,此实现适用于排序的输入点。该算法基于数组。

FUNCTION Cross(v1,v2,v3)
! IMPLICIT VARIABLE HANDLING
IMPLICIT NONE
!-----------------------------------------------
! INPUT VARIABLES
REAL,INTENT(IN) :: v1(2)    !< input vector 1
REAL,INTENT(IN) :: v2(2)    !< input vector 2
REAL,INTENT(IN) :: v3(2)    !< input vector 3
!-----------------------------------------------
! OUTPUT VARIABLES
REAL            :: Cross    !< cross product
!-----------------------------------------------
! LOCAL VARIABLES
!===============================================
Cross=(v2(1)-v1(1))*(v3(2)-v1(2))-(v2(2)-v1(2))*(v3(1)-v1(1))
END FUNCTION Cross

SUBROUTINE ConvHull(nPoints,Points,nHull,Hull)
! IMPLICIT VARIABLE HANDLING
IMPLICIT NONE 
!------------------------------------------------
! INPUT VARIABLES
INTEGER,INTENT(IN)  :: nPoints
REAL,INTENT(IN)     :: Points(2,0:nPoints-1)
!------------------------------------------------
! OUTPUT VARIABLES
INTEGER,INTENT(OUT) :: nHull
! NOTE: allocate Hull always one point greater than Points, because we save the first value twice
REAL,INTENT(OUT)    :: Hull(2,0:nPoints)
!------------------------------------------------
! LOCAL VARIABLES
REAL                :: Lower(2,0:nPoints-1)
REAL                :: Upper(2,0:nPoints-1)
INTEGER             :: i,iLower,iUpper
!================================================
IF(nPoints.LE.1)THEN
  Hull  = Points
  nHull = nPoints
ELSE
  iLower = 0
  Lower  = -HUGE(1.)
  DO i=0,nPoints-1
    DO WHILE(iLower.GE.2.AND.Cross(Lower(:,iLower-2),Lower(:,iLower-1),Points(:,i)).LE.0.)
      Lower(:,iLower) = -HUGE(1.)
      iLower          = iLower - 1
    END DO
    Lower(:,iLower) = Points(:,i)
    iLower = iLower + 1
  END DO

  iUpper = 0
  Upper  = HUGE(1.)
  DO i=nPoints-1,0,-1
    DO WHILE(iUpper.GE.2.AND.Cross(Upper(:,iUpper-2),Upper(:,iUpper-1),Points(:,i)).LE.0.)
      Upper(:,iUpper) = HUGE(1.)
      iUpper          = iUpper - 1
    END DO
    Upper(:,iUpper) = Points(:,i)
    iUpper = iUpper + 1
  END DO

  iLower = iLower-1
  iUpper = iUpper-1
  nHull  = iLower+iUpper+1
  
  ! NOTE: Initialize Hull with zeros
  Hull   = 0.

  ! NOTE: save values in Hull
  Hull(:,0     :iLower       -1) = Lower(:,0:iLower-1)
  Hull(:,iLower:iLower+iUpper-1) = Upper(:,0:iUpper-1)

  ! NOTE: save first value twice
  Hull(:,       iLower+iUpper  ) = Hull (:,0         )
END IF

END SUBROUTINE

JavaScript

[编辑 | 编辑源代码]
function cross(a, b, o) {
   return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
}

/**
 * @param points An array of [X, Y] coordinates
 */
function convexHull(points) {
   points.sort(function(a, b) {
      return a[0] == b[0] ? a[1] - b[1] : a[0] - b[0];
   });

   var lower = [];
   for (var i = 0; i < points.length; i++) {
      while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) {
         lower.pop();
      }
      lower.push(points[i]);
   }

   var upper = [];
   for (var i = points.length - 1; i >= 0; i--) {
      while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) {
         upper.pop();
      }
      upper.push(points[i]);
   }

   upper.pop();
   lower.pop();
   return lower.concat(upper);
}
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.StringTokenizer;

class Point implements Comparable<Point> {
	int x, y;

	public int compareTo(Point p) {
		if (this.x == p.x) {
			return this.y - p.y;
		} else {
			return this.x - p.x;
		}
	}

	public String toString() {
		return "(" + x + "," + y + ")";
	}

}

public class ConvexHull {

	public static long cross(Point O, Point A, Point B) {
		return (A.x - O.x) * (long) (B.y - O.y) - (A.y - O.y) * (long) (B.x - O.x);
	}

	public static Point[] convex_hull(Point[] P) {

		if (P.length > 1) {
			int n = P.length, k = 0;
			Point[] H = new Point[2 * n];

			Arrays.sort(P);

			// Build lower hull
			for (int i = 0; i < n; ++i) {
				while (k >= 2 && cross(H[k - 2], H[k - 1], P[i]) <= 0)
					k--;
				H[k++] = P[i];
			}

			// Build upper hull
			for (int i = n - 2, t = k + 1; i >= 0; i--) {
				while (k >= t && cross(H[k - 2], H[k - 1], P[i]) <= 0)
					k--;
				H[k++] = P[i];
			}
			if (k > 1) {
				H = Arrays.copyOfRange(H, 0, k - 1); // remove non-hull vertices after k; remove k - 1 which is a duplicate
			}
			return H;
		} else if (P.length <= 1) {
			return P;
		} else {
			return null;
		}
	}

	public static void main(String[] args) throws IOException {

		BufferedReader f = new BufferedReader(new FileReader("hull.in")); 	// "hull.in"  Input Sample => size x y x y x y x y
		StringTokenizer st = new StringTokenizer(f.readLine());
		Point[] p = new Point[Integer.parseInt(st.nextToken())];
		for (int i = 0; i < p.length; i++) {
			p[i] = new Point();
			p[i].x = Integer.parseInt(st.nextToken()); // Read X coordinate 
			p[i].y = Integer.parseInt(st.nextToken()); // Read y coordinate
		}
		
		Point[] hull = convex_hull(p).clone();
		
		for (int i = 0; i < hull.length; i++) {
			if (hull[i] != null)
				System.out.print(hull[i]);
		}
	}

}
def convex_hull(points):
    """Computes the convex hull of a set of 2D points.

    Input: an iterable sequence of (x, y) pairs representing the points.
    Output: a list of vertices of the convex hull in counter-clockwise order,
      starting from the vertex with the lexicographically smallest coordinates.
    Implements Andrew's monotone chain algorithm. O(n log n) complexity.
    """

    # Sort the points lexicographically (tuples are compared lexicographically).
    # Remove duplicates to detect the case we have just one unique point.
    points = sorted(set(points))

    # Boring case: no points or a single point, possibly repeated multiple times.
    if len(points) <= 1:
        return points

    # 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
    # Returns a positive value, if OAB makes a counter-clockwise turn,
    # negative for clockwise turn, and zero if the points are collinear.
    def cross(o, a, b):
        return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])

    # Build lower hull 
    lower = []
    for p in points:
        while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0:
            lower.pop()
        lower.append(p)

    # Build upper hull
    upper = []
    for p in reversed(points):
        while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
            upper.pop()
        upper.append(p)

    # Concatenation of the lower and upper hulls gives the convex hull.
    # Last point of each list is omitted because it is repeated at the beginning of the other list. 
    return lower[:-1] + upper[:-1]

# Example: convex hull of a 10-by-10 grid.
assert convex_hull([(i//10, i%10) for i in range(100)]) == [(0, 0), (9, 0), (9, 9), (0, 9)]
# the python code converted to ruby syntax
def convex_hull(points)
  points.sort!.uniq!
  return points if points.length <= 3
  def cross(o, a, b)
    (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
  end
  lower = Array.new
  points.each{|p|
    while lower.length > 1 and cross(lower[-2], lower[-1], p) <= 0 do lower.pop end
    lower.push(p)
  }
  upper = Array.new
  points.reverse_each{|p|
    while upper.length > 1 and cross(upper[-2], upper[-1], p) <= 0 do upper.pop end
    upper.push(p)
  }
  return lower[0...-1] + upper[0...-1]
end
fail unless convex_hull((0..9).to_a.repeated_permutation(2).to_a) == [[0, 0], [9, 0], [9, 9], [0, 9]]
// Implementation of Andrew's monotone chain 2D convex hull algorithm.
// Asymptotic complexity: O(n log n).
// Practical performance: 0.5-1.0 seconds for n=1000000 on a 1GHz machine.
#include <algorithm>
#include <vector>
using namespace std;

typedef double coord_t;         // coordinate type
typedef double coord2_t;  // must be big enough to hold 2*max(|coordinate|)^2

struct Point {
	coord_t x, y;

	bool operator <(const Point &p) const {
		return x < p.x || (x == p.x && y < p.y);
	}
};

// 3D cross product of OA and OB vectors, (i.e z-component of their "2D" cross product, but remember that it is not defined in "2D").
// Returns a positive value, if OAB makes a counter-clockwise turn,
// negative for clockwise turn, and zero if the points are collinear.
coord2_t cross(const Point &O, const Point &A, const Point &B)
{
	return (A.x - O.x) * (B.y - O.y) - (A.y - O.y) * (B.x - O.x);
}

// Returns a list of points on the convex hull in counter-clockwise order.
// Note: the last point in the returned list is the same as the first one.
vector<Point> convex_hull(vector<Point> P)
{
	size_t n = P.size(), k = 0;
	if (n <= 3) return P;
	vector<Point> H(2*n);

	// Sort points lexicographically
	sort(P.begin(), P.end());

	// Build lower hull
	for (size_t i = 0; i < n; ++i) {
		while (k >= 2 && cross(H[k-2], H[k-1], P[i]) <= 0) k--;
		H[k++] = P[i];
	}

	// Build upper hull
	for (size_t i = n-1, t = k+1; i > 0; --i) {
		while (k >= t && cross(H[k-2], H[k-1], P[i-1]) <= 0) k--;
		H[k++] = P[i-1];
	}

	H.resize(k-1);
	return H;
}
use Sort::Key::Radix qw(nkeysort); # use radix sort for an O(n) algorithm

sub convex_hull {
    return @_ if @_ < 2;

    my @p = nkeysort { $_->[0] } @_;

    my (@u, @l);
    my $i = 0;
    while ($i < @p) {
        my $iu = my $il = $i;
        my ($x, $yu) = @{$p[$i]};
        my $yl = $yu;
        # search for upper and lower Y for the current X
        while (++$i < @p and $p[$i][0] == $x) {
            my $y = $p[$i][1];
            if ($y < $yl) {
                $il = $i;
                $yl = $y;
            }
            elsif ($y > $yu) {
                $iu = $i;
                $yu = $y;
            }
        }
        while (@l >= 2) {
            my ($ox, $oy) = @{$l[-2]};
            last if ($l[-1][1] - $oy) * ($x - $ox) < ($yl - $oy) * ($l[-1][0] - $ox);
            pop @l;
        }
        push @l, $p[$il];
        while (@u >= 2) {
            my ($ox, $oy) = @{$u[-2]};
            last if ($u[-1][1] - $oy) * ($x - $ox) > ($yu - $oy) * ($u[-1][0] - $ox);
            pop @u;
        }
        push @u, $p[$iu];
    }

    # remove points from the upper hull extremes when they are already
    # on the lower hull:
    shift @u if $u[0][1] == $l[0][1];
    pop @u if @u and $u[-1][1] == $l[-1][1];

    return (@l, reverse @u);
}

C 代码来自 Math::ConvexHull::MonotoneChain Perl 模块。请注意,此实现适用于排序的输入点。否则,它与上面的 C++ 实现非常相似。

typedef struct {
  double x;
  double y;
} point_t;

typedef point_t* point_ptr_t;

/* Three points are a counter-clockwise turn if ccw > 0, clockwise if
 * ccw < 0, and collinear if ccw = 0 because ccw is a determinant that
 * gives the signed area of the triangle formed by p1, p2 and p3.
 */
static double
ccw(point_t* p1, point_t* p2, point_t* p3)
{
  return (p2->x - p1->x)*(p3->y - p1->y) - (p2->y - p1->y)*(p3->x - p1->x);
}

/* Returns a list of points on the convex hull in counter-clockwise order.
 * Note: the last point in the returned list is the same as the first one.
 */
void
convex_hull(point_t* points, ssize_t npoints, point_ptr_t** out_hull, ssize_t* out_hullsize)
{
  point_ptr_t* hull;
  ssize_t i, t, k = 0;

  hull = *out_hull;

  /* lower hull */
  for (i = 0; i < npoints; ++i) {
    while (k >= 2 && ccw(hull[k-2], hull[k-1], &points[i]) <= 0) --k;
    hull[k++] = &points[i];
  }

  /* upper hull */
  for (i = npoints-2, t = k+1; i >= 0; --i) {
    while (k >= t && ccw(hull[k-2], hull[k-1], &points[i]) <= 0) --k;
    hull[k++] = &points[i];
  }

  *out_hull = hull;
  *out_hullsize = k;
}
import Data.List (sort)

-- Coordinate type
type R = Double

-- Vector / point type
type R2 = (R, R)

-- Checks if it's shortest to rotate from the OA to the OB vector in a clockwise
-- direction.
clockwise :: R2 -> R2 -> R2 -> Bool
clockwise o a b = (a `sub` o) `cross` (b `sub` o) <= 0

-- 2D cross product.
cross :: R2 -> R2 -> R
cross (x1, y1) (x2, y2) = x1 * y2 - x2 * y1

-- Subtract two vectors.
sub :: R2 -> R2 -> R2
sub (x1, y1) (x2, y2) = (x1 - x2, y1 - y2)

-- Implements the monotone chain algorithm
convexHull :: [R2] -> [R2]
convexHull [] = []
convexHull [p] = [p]
convexHull points = lower ++ upper
  where
    sorted = sort points
    lower = chain sorted
    upper = chain (reverse sorted)

chain :: [R2] -> [R2]
chain = go []
  where
    -- The first parameter accumulates a monotone chain where the most recently
    -- added element is at the front of the list.
    go :: [R2] -> [R2] -> [R2]
    go acc@(r1:r2:rs) (x:xs) =
      if clockwise r2 r1 x
        -- Made a clockwise turn - remove the most recent part of the chain.
        then go (r2:rs) (x:xs)
        -- Made a counter-clockwise turn - append to the chain.
        else go (x:acc) xs
    -- If there's only one point in the chain, just add the next visited point.
    go acc (x:xs) = go (x:acc) xs
    -- No more points to consume - finished!  Note: the reverse here causes the
    -- result to be consistent with the other examples (a ccw hull), but
    -- removing that and using (upper ++ lower) above will make it cw.
    go acc [] = reverse $ tail acc

main :: IO ()
main =
    if result == expected
      then putStrLn "convexHull worked!"
      else fail $ "convexHull broken, got " ++ show result
  where
    result = convexHull [(x, y) | x <- [0..9], y <- [0..9]]
    expected = [(0, 0), (9, 0), (9, 9), (0, 9)]
defmodule ConvexHull do
  @type coordinate :: {number, number}
  @type point_list :: [coordinate]

  @spec find(point_list) :: point_list
  def find(points) do
    sorted = points |> Enum.sort
    left = sorted |> do_half_calc
    right = sorted |> Enum.reverse |> do_half_calc
    [left, right] |> Enum.concat
  end

  @spec do_half_calc(point_list) :: point_list
  defp do_half_calc(points) do
    points
    |> Enum.reduce([], &add_to_convex_list/2)
    |> tl
    |> Enum.reverse
  end

  @spec perp_prod(coordinate, coordinate, coordinate) :: number
  defp perp_prod({x0, y0}, {x1, y1}, {x2, y2}) do
    (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0) 
  end

  @spec add_to_convex_list(coordinate, point_list) :: point_list
  defp add_to_convex_list(p2, list) do
    {:ok, new_tail} =
      list
      |> stream_tails
      |> Stream.drop_while(fn tail -> oa_left_of_ob(p2, tail) end)
      |> Enum.fetch(0)
    [p2 | new_tail]
  end

  @spec oa_left_of_ob(coordinate, point_list) :: number
  defp oa_left_of_ob(b, [a, o | _]), do: perp_prod(o, a, b) <= 0
  defp oa_left_of_ob(_, _), do: false

  @spec stream_tails(point_list) :: Enum.t(point_list) 
  defp stream_tails(list) do
    tails = Stream.unfold(list, fn [_ | t] -> {t, t} end)
    Stream.concat([list], tails)
  end

end

ExUnit.start

defmodule ConvexHullStart do
  use ExUnit.Case, async: true

  @points for x <- 0..9, y <- 0..9, do: {x, y}

  test "finding the convex hull for all points (0,0) .. (9,9)" do
    assert ConvexHull.find(@points) == [{0, 0}, {9, 0}, {9, 9}, {0, 9}]
  end
end
(defn convex-hull
"The function convex-hull accepts a collection of vectors containing pairs of numbers
 which define the [x y] bounds of the polygon,
 e.g. [[0 0] [0 1] [0 2] [0 3]
       [1 0] [1 1] [1 2] [1 3]
       [2 0] [2 1] [2 2] [2 3] [2 4] [2 5] [2 6] [2 7] [2 8] [2 9]
       [3 0] [3 1] [3 2] [3 3]
       [4 0] [4 1] [4 2] [4 3] [4 4]
             [5 1]]

 When run on the above collection of points the output will be
   [[0 0] [0 3] [2 9] [4 4] [5 1] [4 0]]
             
 The input collection of vectors can be either a vector ( e.g. [[0 0] [1 1] [2 2]]) or a quoted sequence
 (e.g. '([0 0] [1 1] [2 2]) or (quote ([0 0] [1 1] [2 2])).
 
 The output collection will always be a vector of vectors (e.g. [[0 0] [0 9] [9 9] [9 0]]) which
 define the bounds of the convex hull."
  [c]
  (let [cc         (sort-by identity c)
        x          #(first %)    ; returns the 'x' coordinate from an [x y] vector
        y          #(second %)   ; returns the 'y' coordinate from an [x y] vector
        ccw        #(- (* (- (x %2) (x %1)) (- (y %3) (y %1))) (* (- (y %2) (y %1)) (- (x %3) (x %1)))) 
        half-hull  #(loop [h  [] ; returns the upper half-hull of the collection of vectors
                           c  %]
                      (if (not (empty? c))
                        (if (and (> (count h) 1) (<= 0 (ccw (h (- (count h) 2)) (h (- (count h) 1)) (first c))))
                          (recur (vec (butlast h)) c)
                          (recur (conj h (first c)) (rest c)))
                        h))
       upper-hull  (butlast (half-hull cc))
       lower-hull  (butlast (half-hull (reverse cc)))]
    (vec (concat upper-hull lower-hull ))))
import scala.collection.mutable.ArrayBuffer
  
  def convexHull(points: ArrayBuffer[(Int, Int)]): ArrayBuffer[(Int, Int)] = {
    
    
    // 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
    // Returns a positive value, if OAB makes a counter-clockwise turn,
    // negative for clockwise turn, and zero if the points are collinear.
    def cross(o: (Int, Int), a: (Int, Int), b: (Int, Int)): Int = {
      (a._1 - o._1) * (b._2 - o._2) - (a._2 - o._2) * (b._1 - o._1)
    }
    
    val distinctPoints = points.distinct
    
    // No sorting needed if there are less than 2 unique points.
    if(distinctPoints.length < 2) {
      return points
    } else {
      
      val sortedPoints = distinctPoints.sorted
      
      // Build the lower hull
      val lower = ArrayBuffer[(Int, Int)]()
      for(i <- sortedPoints){
        while(lower.length >= 2 && cross(lower(lower.length - 2), lower(lower.length - 1) , i) <= 0){
          lower -= lower.last
        }
        lower += i
      }
      
      // Build the upper hull
      val upper = ArrayBuffer[(Int, Int)]()
      for(i <- sortedPoints.reverse){
        while(upper.size >= 2 && cross(upper(upper.length - 2), upper(upper.length - 1) , i) <= 0){
          upper -= upper.last
        }
        upper += i
      }
      
      // Last point of each list is omitted because it is repeated at the beginning of the other list.
      lower -= lower.last
      upper -= upper.last
      
      // Concatenation of the lower and upper hulls gives the convex hull
      return lower ++= upper
    }
  }


$coords = [
    [
        'x' => <point_0_x>,
        'y' => <point_0_y>,
    ],
    [
        'x' => <point_1_x>,
        'y' => <point_1_y>,
    ],
    [
        'x' => <point_n_x>,
        'y' => <point_n_y>,
    ],
];

function orientation(array $coord1, array $coord2, array $coord3)
{
    return ($coord2['x'] - $coord1['x']) * ($coord3['y'] - $coord1['y']) - ($coord2['y'] - $coord1['y']) * ($coord3['x'] - $coord1['x']);
}

usort($coords, function ($a, $b) { return $a['x'] == $b['x'] ? $a['y'] - $b['y'] : $a['x'] - $b['x']; });

$upper_hulls = $lower_hulls = [];

foreach ($coords as $coord) {
    while (count($last_two = array_slice($lower_hulls, -2)) >= 2 && orientation($last_two[0], $last_two[1], $coord) <= 0) {
        array_pop($lower_hulls);
    }
    $lower_hulls[] = $coord;
}

foreach (array_reverse($coords) as $coord) {
    while (count($last_two = array_slice($upper_hulls, -2)) >= 2 && orientation($last_two[0], $last_two[1], $coord) <= 0) {
        array_pop($upper_hulls);
    }
    $upper_hulls[] = $coord;
}

array_pop($upper_hulls);
array_pop($lower_hulls);

return array_merge($upper_hulls, $lower_hulls);
import Foundation

public func cross(_ o: CGPoint, _ a: CGPoint, _ b: CGPoint) -> CGFloat {
    let lhs = (a.x - o.x) * (b.y - o.y)
    let rhs = (a.y - o.y) * (b.x - o.x)
    return lhs - rhs
}

/// Calculate and return the convex hull of a given sequence of points.
///
/// - Remark: Implements Andrew’s monotone chain convex hull algorithm.
///
/// - Complexity: O(*n* log *n*), where *n* is the count of `points`.
///
/// - Parameter points: A sequence of `CGPoint` elements.
///
/// - Returns: An array containing the convex hull of `points`, ordered
///   lexicographically from the smallest coordinates to the largest,
///   turning counterclockwise.
///
public func convexHull<Source>(_ points: Source) -> [CGPoint]
    where Source : Collection,
          Source.Element == CGPoint
{
    // Exit early if there aren’t enough points to work with.
    guard points.count > 1 else { return Array(points) }

    // Create storage for the lower and upper hulls.
    var lower = [CGPoint]()
    var upper = [CGPoint]()

    // Sort points in lexicographical order.
    let points = points.sorted { a, b in
        a.x < b.x || a.x == b.x && a.y < b.y
    }

    // Construct the lower hull.
    for point in points {
        while lower.count >= 2 {
            let a = lower[lower.count - 2]
            let b = lower[lower.count - 1]
            if cross(a, b, point) > 0 { break }
            lower.removeLast()
        }
        lower.append(point)
    }

    // Construct the upper hull.
    for point in points.lazy.reversed() {
        while upper.count >= 2 {
            let a = upper[upper.count - 2]
            let b = upper[upper.count - 1]
            if cross(a, b, point) > 0 { break }
            upper.removeLast()
        }
        upper.append(point)
    }

    // Remove each array’s last point, as it’s the same as the first point
    // in the opposite array, respectively.
    lower.removeLast()
    upper.removeLast()

    // Join the arrays to form the convex hull.
    return lower + upper
}
#lang racket

; Monotone Chain method

; function calculates convex hull from
; unsorted association list of points P
(define (convex-hull P)
    ;
    ; given a set of points P, this function will
    ; recursively build an association list of points H
    ; that define half of the convex hull
    (define (half-hull P H passed-H)
        ; if there are no further points to add
        ; report the half-hull except for the last point
        (if (zero? (length P))
            (cdr H)
            ; if H is empty (the initial state)
            ; add the first two points from P
            (if (zero? (length H))
                (let ([H~ (list (cadr P) (car P))])
                    (half-hull (cddr P) H~ (list)))
                ; if we have run out of line segments in H to test
                ; include the candidate point and
                ; move on to the next point in P
                (if (= 1 (length H))
                    (let ([H~ (append (cons (car P) passed-H) H)])
                        (half-hull (cdr P) H~ (list)))
                    ; otherwise test the next line segment in H (p1 p2)
                    ; against the candidate point p~
                    (let* (
                        [p1  (cadr H)]
                        [p2  (car  H)]
                        [p~  (car  P)]
                        [x1  (x    p1)]
                        [y1  (y    p1)]
                        [x2  (x    p2)]
                        [y2  (y    p2)]
                        [x~  (x    p~)]
                        [y~  (y    p~)])
                        ; if it passes, move on to the next segment 
                        ; if it fails, delete the line segment
                        (if
                            (> (* (- y2 y1) (- x~ x1)) (* (- x2 x1) (- y~ y1)))
                            (half-hull P (cdr H) (append passed-H (list p2)))
                            (half-hull P (cdr H) passed-H)))))))
    ;
    ; body of convex-hull
    (if
        (= 1 (length P))
        P
        (let ([P~ (sort (sort P > #:key cdr) > #:key car)])
            (append
                (half-hull P~ (list) (list))
                (half-hull (reverse P~) (list) (list))))))
function convex_hull(points)
    # Implements Andrew's monotone chain algorithm
    # Input: points - vector of tuples (x,y)
    # Ouput: the subset of points that define the convex hull

    # not enough points
    if length(points) <= 1
        return copy(points)
    end

    # sort the points by x and then by y
    points = sort(points)

    # function for calculating the cross product of vectors OA and OB
    cross(o, a, b) = (a[1] - o[1]) * (b[2] - o[2]) - (a[2] - o[2]) * (b[1] - o[1])

    # build lower hull
    lower = eltype(points)[]
    for p in points
        while length(lower) >= 2 && cross(lower[end-1], lower[end], p) <= 0
            pop!(lower)
        end
        push!(lower,p)
    end

    # build upper hull
    upper = eltype(points)[]
    for i in length(points):-1:1
        p = points[i]
        while length(upper) >= 2 && cross(upper[end-1], upper[end], p) <= 0
            pop!(upper)
        end
        push!(upper,p)
    end

    # concatenates lower hull to upper hull to obtain the convex hull
    vcat(lower[1:end-1], upper[1:end-1])
end

@assert convex_hull(map(x -> (div(x,10), mod(x,10)), 0:99)) == [(0, 0), (9, 0), (9, 9), (0, 9)]
program MonotoneChain;
uses crt;
type TPoint = record
                x,y:Real;
              end;
     TFunc = function (a,b:TPoint):integer;
     TArray = array of TPoint;

function signedAreaOfParallelogram(O,A,B:TPoint):Real;
begin
  signedAreaOfParallelogram := (A.x - O.x)*(B.y - O.y) - (A.y - O.y)*(B.x - O.x);
end;

function monotoneChain(P:TArray;n:integer;var H:TArray):integer;
var k,i,t:integer;
begin
  if n > 3 then
  begin
    k := 0;
    for i := 0 to n - 1 do
    begin
      while (k >= 2)and(signedAreaOfParallelogram(H[k-2],H[k-1],P[i]) <= 0)do
        k := k - 1;
      H[k] := P[i];
      k := k + 1;
    end;
    t := k + 1;
    for i := n - 1 downto 1 do
    begin
      while(k >= t)and(signedAreaOfParallelogram(H[k-2],H[k-1],P[i-1]) <= 0)do
        k := k - 1;
      H[k] := P[i-1];
      k := k + 1;
    end;
    monotoneChain := k - 1;
  end
  else
      monotoneChain := n;
end;

function comparePoints(a,b:TPoint):integer;
var t:integer;
begin
  t := 1;
  if(a.x < b.x)or((a.x = b.x)and(a.y < b.y))then
        t := -1;
  if(a.x = b.x)and(a.y = b.y)then
        t := 0;
  comparePoints := t;
end;

procedure quicksort(var A:TArray;l,r:integer;cmp:TFunc);
var i,j:integer;
    x,w:TPoint;
begin
  i := l;
  j := r;
  x := A[(l+r)div 2];
  repeat
     while cmp(A[i],x) < 0 do i := i + 1;
     while cmp(x,A[j]) < 0 do j := j - 1;
     if i <= j then
     begin
       w := A[i];
       A[i] := A[j];
       A[j] := w;
       i := i + 1;
       j := j - 1;
     end
     until i > j;
     if l < j then quicksort(A,l,j,cmp);
     if i < r then quicksort(A,i,r,cmp)
end;

var esc:char;
    k,m,n:integer;
    P,H:TArray;
BEGIN
  clrscr;
  repeat
     repeat
       writeln('How many points you want to read');
       readln(n);
     until n >= 0;
     SetLength(P,n);
     SetLength(H,n);
     for k := 0 to n-1 do
     begin
       write('P[',k,']=');
       readln(P[k].x,P[k].y);
     end;
     writeln;
     if n > 0 then
        quicksort(P,0,n-1,@comparePoints);
     m := monotoneChain(P,n,H);
     writeln('Array of sorted points');
     for k := 0 to n-1 do
        write('(',P[k].x:1:6,',',P[k].y:1:6,') ');
     writeln;
     writeln;
     writeln('Points on the hull');
     for k := 0 to m-1 do
        write('(',H[k].x:1:6,',',H[k].y:1:6,') ');
     writeln;
     writeln;
     esc := readkey;
  until esc = #27;
END.

参考文献

[编辑 | 编辑源代码]
华夏公益教科书