Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Geometry / Polygons and Point Sets

7.3.3 Convex Hull and Diametral Pair

7-Geometry/7.3.3_Convex_Hull_and_Diametral_Pair.cpp

The convex hull of points in two dimensions is the smallest convex set containing them. A diametral pair is a pair of input points at maximum distance. Monotone chain computes the hull by sorting the points lexicographically and building the lower and upper boundaries in one pass each, popping any point that would fail to create a counter-clockwise turn. Rotating calipers then walks two antipodal pointers around the hull, advancing whichever increases the separation and visiting every candidate diametral pair in linear time. Both functions accept either floating-point or integral coordinates, and use exact comparisons for integral points.

  • convex_hull(p) returns the convex hull of points p in counter-clockwise order. Duplicate input points and collinear points along hull edges are omitted. To instead return the hull points in clockwise order, replace the cross product comparisons <= 0 with >= 0.
  • diametral_pair(p) returns the maximum-distance pair of points in p.

Overflow warning: cross() and the squared distance in diametral_pair() grow like the squared coordinate magnitude. With 32-bit int coordinates they overflow once coordinates exceed a few tens of thousands; use a 64-bit (int64_t) coordinate type for larger integer inputs.

Implementation

#include <algorithm>
#include <cmath>
#include <utility>
#include <vector>

template<typename Pt>
auto cross(const Pt &a, const Pt &b, const Pt &o) {
  // Overflow risk for integer Pt: these products are ~O(max_coord^2); use int64_t if necessary.
  return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
}

// Convex hull: exact for integer-coordinate points.
template<typename Pt>
std::vector<Pt> convex_hull(std::vector<Pt> p) {
  std::sort(p.begin(), p.end());
  auto same_point = [](const Pt &a, const Pt &b) { return !(a < b) && !(b < a); };
  p.erase(std::unique(p.begin(), p.end(), same_point), p.end());
  int n = static_cast<int>(p.size());
  if (n <= 1) {
    return p;
  }
  int k = 0;
  std::vector<Pt> res(2 * n);
  for (const Pt &q : p) {
    while (k >= 2 && cross(res[k - 1], q, res[k - 2]) <= 0) {
      k--;
    }
    res[k++] = q;
  }
  int t = k + 1;
  for (int i = n - 2; i >= 0; i--) {
    while (k >= t && cross(res[k - 1], p[i], res[k - 2]) <= 0) {
      k--;
    }
    res[k++] = p[i];
  }
  res.resize(k - 1);
  return res;
}

// Diametral pair: squared-distance comparisons are exact for integer-coordinate points.
template<typename Pt>
std::pair<Pt, Pt> diametral_pair(const std::vector<Pt> &p) {
  auto h = convex_hull(p);
  int m = static_cast<int>(h.size());
  if (m == 0) {
    return std::pair<Pt, Pt>{};
  }
  if (m == 1) {
    return std::pair<Pt, Pt>{h[0], h[0]};
  }
  if (m == 2) {
    return std::pair<Pt, Pt>{h[0], h[1]};
  }
  int k = 1;
  while (std::abs(cross(h[0], h[(k + 1) % m], h[m - 1])) > std::abs(cross(h[0], h[k], h[m - 1]))) {
    k++;
  }
  auto sqdist = [](const Pt &a, const Pt &b) {
    // Overflow risk for integer Pt: ~O(max_coord^2); use int64_t if necessary.
    auto dx = a.x - b.x, dy = a.y - b.y;
    return dx * dx + dy * dy;
  };
  auto maxsq = sqdist(h[0], h[0]);
  std::pair<Pt, Pt> res{h[0], h[0]};
  for (int i = 0, j = k; i <= k && j < m; i++) {
    auto d = sqdist(h[i], h[j]);
    if (d > maxsq) {
      maxsq = d;
      res = {h[i], h[j]};
    }
    while (j < m && std::abs(cross(h[(i + 1) % m], h[(j + 1) % m], h[i])) >
                        std::abs(cross(h[(i + 1) % m], h[j], h[i]))) {
      d = sqdist(h[i], h[(j + 1) % m]);
      if (d > maxsq) {
        maxsq = d;
        res = {h[i], h[(j + 1) % m]};
      }
      j++;
    }
  }
  return res;
}

Example Usage

#include <cassert>
#include <random>
#include <vector>
using namespace std;

struct PointI {
  int x, y;
  PointI(int x = 0, int y = 0) : x(x), y(y) {}
  bool operator==(const PointI &o) const { return x == o.x && y == o.y; }
  bool operator!=(const PointI &o) const { return !(*this == o); }
  bool operator<(const PointI &o) const { return x != o.x ? x < o.x : y < o.y; }
  bool operator>(const PointI &o) const { return o < *this; }
};

int main() {
  {
    vector<PointI> v{{1, 3}, {1, 2}, {2, 1}, {0, 0}, {-1, 3}};
    mt19937 rng(1234567);  // Fixed seed for reproducibility.
    shuffle(v.begin(), v.end(), rng);
    vector<PointI> h{{-1, 3}, {0, 0}, {2, 1}, {1, 3}};
    assert(convex_hull(v) == h);
  }
  {
    auto [p1, p2] = diametral_pair(vector<PointI>{{0, 0}, {3, 0}, {0, 3}, {1, 1}, {4, 4}});
    assert(p1 == PointI(0, 0) && p2 == PointI(4, 4));
  }
  {
    vector<PointI> v{{0, 0}, {4, 0}, {4, 4}, {0, 4}, {2, 2}};
    auto h = convex_hull(v);
    assert(h.size() == 4);  // interior point (2,2) excluded
    auto [p1, p2] = diametral_pair(v);
    // diametral pair is exact
    assert(
        (p1 == PointI(0, 0) && p2 == PointI(4, 4)) || (p1 == PointI(4, 4) && p2 == PointI(0, 0)) ||
        (p1 == PointI(4, 0) && p2 == PointI(0, 4)) || (p1 == PointI(0, 4) && p2 == PointI(4, 0))
    );
  }
  {
    assert((convex_hull(vector<PointI>{{2, 3}, {2, 3}, {2, 3}}) == vector<PointI>{{2, 3}}));
  }
  return 0;
}