Graham Scan 寻找二维凸包(C++实现)

这里的代码参照《算法导论》第三版第 33 章第 3 节的理论,以洛谷P2742圈奶牛为背景实现。

// lg-p2742
#include <algorithm>
#include <climits>
#include <cmath>
#include <cstdio>
#include <vector>

template<typename T>
T square(const T x) {
    return x * x;
}

struct Point {
    double x, y;

    int quadrant() const {
        // 此处可以直接与零比较是否相等,因为 0 是可以准确表达的。
        if (x == 0.0 && y == 0.0) return 0;     // 原点
        else if (x > 0.0 && y == 0.0) return 1; // x正
        else if (x > 0.0 && y > 0.0) return 2;  // 第一象限
        else if (x == 0.0 && y > 0.0) return 3; // y正
        else if (x < 0.0 && y > 0.0) return 4;  // 第二象限
        else if (x < 0.0 && y == 0.0) return 5; // x负
        else if (x < 0.0 && y < 0.0) return 6;  // 第三象限
        else if (x == 0.0 && y < 0.0) return 7; // y负
        else return 8;                          // 第四象限
    }

    double crossProduct(const Point &other) const {
        return x * other.y - y * other.x;
    }

    double distance(const Point &other) const {
        return sqrt(square(x-other.x) + square(y-other.y));
    }

    Point operator-(const Point &other) const {
        return Point { .x = x-other.x, .y = y-other.y };
    }

    bool operator==(const Point &other) {
        return x == other.x && y == other.y;
    }
};

using Vector2d = Point;

void sortPoints(std::vector<Point> &points) {
    const int n = points.size();
    Point bottomMost {.x = INT_MAX, .y = INT_MAX};
    int indBottomMost;
    for (int i = 0; i < n; ++i) {
        // 找到 y 坐标最小的点
        if (points[i].y < bottomMost.y) {
            bottomMost = points[i];
            indBottomMost = i;
        }
    }

    points[indBottomMost] = points[0];
    points[0] = bottomMost;
    std::sort(points.begin()+1, points.end(), [&](const Point &a, const Point &b) -> bool {
        // 以 points[0] 为极点,做极角排序。
        const Vector2d oa = a - bottomMost;
        const Vector2d ob = b - bottomMost;
        if (oa.quadrant() != ob.quadrant()) return oa.quadrant() < ob.quadrant();
        return oa.crossProduct(ob) > 0.0f;
    });
}

bool sameAngle(const Vector2d &a, const Vector2d &b) {
    if (a.quadrant() != b.quadrant()) return false;
    else if (a.x == 0 || b.x == 0 || a.y == 0 || b.y == 0) return true;
    return a.y / a.x == b.y / b.x;
}

// @return new size of <points>
int removeSameAngle(std::vector<Point> &points) {
    const Point origin = points.front();
    const int n = points.size();
    int tail = 0;
    for (int i = 1; i < n; ++i) {
        if (sameAngle(points[i] - origin, points[tail] - origin)) {
            if (origin.distance(points[i]) > origin.distance(points[tail])) {
                points[tail] = points[i];
            }
        } else {
            points[++tail] = points[i];
        }
    }
    return tail+1;
}

bool leftTurn(const Point &o, const Point &p1, const Point &p2) {
    const Vector2d v2 = p2 - o;
    const Vector2d v1 = p1 - o;
    return v2.crossProduct(v1) < 0.0;
}

// Graham Scan
double calcLengthOfFence(std::vector<Point> &points) {
    int n = points.size();
    if (n < 3) return 0;
    sortPoints(points);

    n = removeSameAngle(points);

    std::vector<Point> convexHull; convexHull.reserve(3);
    for (int i = 0; i < 3; ++i) {
        convexHull.push_back(points[i]);
    }
    for (int i = 3; i < n; ++i) {
        while (convexHull.size() > 2 && !leftTurn(convexHull[convexHull.size()-2], convexHull.back(), points[i])) {
            convexHull.pop_back();
        }
        convexHull.push_back(points[i]);
    }

    double fenceLength = convexHull.front().distance(convexHull.back());
    for (int i = convexHull.size()-1; i > 0; --i) {
        fenceLength += convexHull[i].distance(convexHull[i-1]);
    }

    return fenceLength;
}

int main() {
    int m;
    scanf("%d", &m);
    std::vector<Point> points(m);
    for (int i = 0; i < m; ++i) {
        scanf("%lf %lf", &points[i].x, &points[i].y);
    }

    printf("%.2lf\n", calcLengthOfFence(points));

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是 Graham-Scan 算法C++ 实现,用于求解凸包问题: ```cpp #include <bits/stdc++.h> using namespace std; struct Point { int x, y; }; // 按照 x 坐标从小到大排序,若 x 坐标相等,则按照 y 坐标从小到大排序。 bool cmp(Point a, Point b) { if (a.x == b.x) return a.y < b.y; return a.x < b.x; } // 计算叉积。 int cross(Point a, Point b, Point c) { return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); } // Graham-Scan 算法求解凸包。 vector<Point> grahamScan(vector<Point> &points) { int n = points.size(); if (n <= 1) return points; sort(points.begin(), points.end(), cmp); vector<Point> hull(2 * n); int k = 0; // 构建下凸壳。 for (int i = 0; i < n; ++i) { while (k >= 2 && cross(hull[k - 2], hull[k - 1], points[i]) <= 0) k--; hull[k++] = points[i]; } // 构建上凸壳。 for (int i = n - 2, t = k + 1; i >= 0; --i) { while (k >= t && cross(hull[k - 2], hull[k - 1], points[i]) <= 0) k--; hull[k++] = points[i]; } // 去除重复点。 hull.resize(k - 1); return hull; } int main() { // 测试数据。 vector<Point> points = {{0, 3}, {1, 1}, {2, 2}, {4, 4}, {0, 0}, {1, 2}, {3, 1}, {3, 3}}; vector<Point> hull = grahamScan(points); // 输出凸包的顶点。 for (int i = 0; i < hull.size(); ++i) { cout << "(" << hull[i].x << ", " << hull[i].y << ")" << endl; } return 0; } ``` 注意点: 1. 为了方便起见,我直接使用了 C++11 的新特性,使用 vector 存储点集,如果你使用的是较老的编译器,可以使用数组代替 vector。 2. 实现中为了方便起见,我使用了三个点 $A(a_x,a_y)$、$B(b_x,b_y)$、$C(c_x,c_y)$ 的叉积 $cross(A,B,C)$ 表示向量 $\vec{AB}$ 和 $\vec{AC}$ 的叉积。当叉积 $cross(A,B,C)>0$ 时,表示 $\vec{AB}$ 在 $\vec{AC}$ 的逆时针方向;当叉积 $cross(A,B,C)<0$ 时,表示 $\vec{AB}$ 在 $\vec{AC}$ 的顺时针方向;当叉积 $cross(A,B,C)=0$ 时,表示 $\vec{AB}$ 和 $\vec{AC}$ 共线。 3. 为了避免精度误差,最好使用整数类型存储坐标,如 int 类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值