凸包——Graham-Scan算法

凸包(Convex Hull)是一个计算几何(图形学)中的概念。
在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,…Xn)的凸组合来构造.
在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。

凸包
那么,怎么才能从平面上的点中构造出凸包呢

在这里插入图片描述

一: Graham-Scan(复杂度nlogn)
1: 找到所有点中 x x x坐标最小的点,若有相同 x x x坐标的多个点,取 y y y坐标最小的.以这个点为基准对其余所有的点进行极角排序,极角相等的情况下距离极点 ( p [ 1 ] ) (p[1]) (p[1])最近的优先
上图排序后:
在这里插入图片描述
2: 用一个栈(数组) s t k [ N ] stk[N] stk[N], 存储凸包上的点,先把 p [ 1 ] p[1] p[1], p [ 2 ] p[2] p[2]压入栈,用k记录当前栈里的元素个数.
3: 扫描所有的点,不断地更新最外围的点,是否在最外围可由当前点与 s t k [ k ] stk[k] stk[k] s t k [ k − 1 ] stk[k-1] stk[k1]之间的叉积判断。即
( s t k [ k ] − s t k [ k − 1 ] ) × ( p [ i ] − s t k [ k − 1 ] ) , (stk[k]-stk[k-1])\times (p[i]-stk[k-1]), (stk[k]stk[k1])×(p[i]stk[k1]),
叉积大于零即 p [ i ] p[i] p[i]在向量 ( s t k [ k ] − s t k [ k − 1 ] ) (stk[k]-stk[k-1]) (stk[k]stk[k1])左边.
点在右边就弹出栈顶元素,继续判断, 否则压入新点 p [ i ] p[i] p[i]; 如图
在这里插入图片描述
p 1 p 2 ⃗ × p 1 p 3 ⃗ > 0 说 明 p 3 在 p 1 p 2 ⃗ 的 左 边 , 压 入 p 3 , 继 续 扫 描 \vec{p_1p_2} × \vec{p_1p_3} > 0 说明p_3在\vec{p_1p_2}的左边,压入p_3,继续扫描 p1p2 ×p1p3 >0p3p1p2 ,p3,
在这里插入图片描述
p 2 p 3 ⃗ × p 2 p 4 ⃗ > 0 说 明 p 4 在 p 2 p 3 ⃗ 的 左 边 , 压 入 p 4 , 继 续 扫 描 \vec{p_2p_3} × \vec{p_2p_4} > 0 说明p_4在\vec{p_2p_3}的左边,压入p_4,继续扫描 p2p3 ×p2p4 >0p4p2p3 ,p4,
在这里插入图片描述
p 3 p 4 ⃗ × p 3 p 5 ⃗ > 0 说 明 p 5 在 p 3 p 4 ⃗ 的 左 边 , 压 入 p 5 , 继 续 扫 描 \vec{p_3p_4} × \vec{p_3p_5} > 0 说明p_5在\vec{p_3p_4}的左边,压入p_5,继续扫描 p3p4 ×p3p5 >0p5p3p4 ,p5,
在这里插入图片描述
p 4 p 5 ⃗ × p 4 p 6 ⃗ < 0 说 明 p 6 在 p 4 p 5 ⃗ 的 右 边 , 弹 出 栈 顶 元 素 , 即 弹 出 p 5 , 继 续 判 断 \vec{p_4p_5} × \vec{p_4p_6} < 0 说明p_6在\vec{p_4p_5}的右边,弹出栈顶元素,即弹出p_5,继续判断 p4p5 ×p4p6 <0p6p4p5 ,,p5,
在这里插入图片描述
p 3 p 4 ⃗ × p 3 p 6 ⃗ > 0 说 明 p 6 在 p 3 p 4 ⃗ 的 左 边 , 压 入 p 6 , 继 续 扫 描 \vec{p_3p_4} × \vec{p_3p_6} > 0 说明p_6在\vec{p_3p_4}的左边,压入p_6,继续扫描 p3p4 ×p3p6 >0p6p3p4 ,p6,
在这里插入图片描述
p 4 p 6 ⃗ × p 4 p 7 ⃗ < 0 说 明 p 7 在 p 4 p 6 ⃗ 的 右 边 , 弹 出 栈 顶 元 素 , 即 弹 出 p 6 , 继 续 判 断 \vec{p_4p_6} × \vec{p_4p_7} < 0 说明p_7在\vec{p_4p_6}的右边,弹出栈顶元素,即弹出p_6,继续判断 p4p6 ×p4p7 <0p7p4p6 ,,p6,
在这里插入图片描述
p 3 p 4 ⃗ × p 3 p 7 ⃗ > 0 说 明 p 7 在 p 3 p 4 ⃗ 的 左 边 , 压 入 p 7 , 继 续 扫 描 \vec{p_3p_4} × \vec{p_3p_7} > 0 说明p_7在\vec{p_3p_4}的左边,压入p_7,继续扫描 p3p4 ×p3p7 >0p7p3p4 ,p7,

在这里插入图片描述
p 4 p 7 ⃗ × p 4 p 8 ⃗ > 0 说 明 p 8 在 p 4 p 7 ⃗ 的 左 边 , 压 入 p 8 , 扫 描 完 毕 \vec{p_4p_7} × \vec{p_4p_8} > 0 说明p_8在\vec{p_4p_7}的左边,压入p_8,扫描完毕 p4p7 ×p4p8 >0p8p4p7 ,p8,
在这里插入图片描述

栈中剩下的元素就是凸包点了.ok
有人:“你这不是在逗我嘛,这图都不连通,怎么就凸包了…”

代码:

struct Point;
typedef Point Vector;

struct Point{
    double x,y;
    Point(){}
    Point(double a, double b):x(a),y(b){}
    double len(){return sqrt(x*x+y*y);}
    double disToPoint(Point p){return sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y));}
    void read(){scanf("%lf%lf",&x,&y);}
    Point operator+(Vector v){return {x+v.x,y+v.y};}
    Vector operator-(Point p){return {x-p.x,y-p.y};}
    double operator^(Vector v){return x*v.y-y*v.x;}//叉乘
    double operator*(Vector v){return x*v.x+y*v.y;}//点乘
    Vector operator*(double d){return {x*d,y*d};}
    Vector operator/(double d){return {x/d,y/d};}
    bool operator==(Point p){return cmp(x,p.x)==0&&cmp(y,p.y)==0;}
    bool operator<(Point p){if(cmp(x,p.x)==0) return y<p.y;return x<p.x;}
};
Point base;
bool grahamCmp(Point& p1,Point& p2){
    if(sgn((p1-base)^(p2-base))==0)
        return (p1-base).len()<(p2-base).len();
    return ((p1-base)^(p2-base))>0;
}
Point p[MAXN];
Point stk[MAXN];
int graham(){//点的存储位置[1,n]
    for(int i=2;i<=n;i++){
        if(p[i]<p[1])
            swap(p[i],p[1]);
    }
    base=p[1];
    sort(p+2,p+1+n,grahamCmp);//将其余的点排序
    int k=0;//凸包点的个数
    stk[++k]=p[1];stk[++k]=p[2];
    for(int i=3;i<=n;i++){
        while(k>1&&sgn((stk[k]-stk[k-1])^(p[i]-stk[k-1]))<0)
            k--;
        stk[++k]=p[i];
    }
    return k;
}

例题:
P2742 【模板】二维凸包
HDU - 1392 Surround the Trees
HDU - 2297 Run

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
好的,以下是 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 类型。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值