关于graham扫描法求凸包的小记

Posted tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于graham扫描法求凸包的小记相关的知识,希望对你有一定的参考价值。

1、首先,凸包是啥:

若是在二维平面上,则一般的,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它能包含点集中所有的点。

───────────────────────────────────────────────────────────────────────────────────────────────────────────

2、那么,如何通过某种算法求二维平面上的凸包呢?

有Graham扫描法(Graham scan algorithm),复杂度O(nlogn)。

话不多说,先上当年大佬的论文……

 

呃,可以看到,这个标题是非常的酷嗷,对于有限平面点集的凸包计算的高效算法,划重点。

 

给一个平面点集S,标号为s1~sn,据说我们经常对找它的凸包感兴趣(真的吗……我怎么从来没感兴趣过……)

然后graham教授就给了我们一种炫酷的五步法,来求凸包。

 

第一步:

  目标是找个在凸包内部的点P。

  我们对集合S三个点三个点进行检测,检测它们是否共线:

    若共线,扔掉中点;

    若不共线,就选这三个点所组成的三角形的质心作为点P。

 

第二步:

  以P为原点,任意一个方向为θ=0轴,建立一个极坐标系;

  对集合S中的每个点si,都按这个坐标系表示一下它们的坐标。

 

第三步:

  现在每个点都有坐标 r ∠ θ ,我们对这些点,按照θ的升序进行排序。

 

第四步:

  如果有某两个点的角度相等,就删掉r较小的那个点,因为它显然不可能是凸包边界上的点。

  另外呢,所有r=0的,也可以删了,反正也impossible。

  then,重新给还存在着的点编号,新的集合记为S\'。

 

           

 

第五步:

  对于S\'中连续的三个点k,k+1,k+2,如图2,有两种可能:

    1)α + β ≥ π,看图,就很容易知道,这个点k+1,显然不可能是凸包边界上的点了;

      回到步骤五,重新选择点k-1,k,k+2作为新的三个点;

    2)α + β < π,就回到步骤五,选择点k+1,k+2,k+3作为新的三个点;

      相当于往前进。

 

原文件:https://files.cnblogs.com/files/dilthey/graham%E6%89%AB%E6%8F%8F%E6%B3%95.pdf

───────────────────────────────────────────────────────────────────────────────────────────────────────────

3、那么放到程序中,具体如何实现呢?

我们保留“对于三个点,判断角α、β和是否小于180度,并且进行相应的前进退后”的思想,不过对于选取原点的方法进行一定的修改。

  ①找到点集S中纵坐标最小的点(如果y坐标相同,则选其中横坐标最小的),作为原点P0

  ②计算其他所有点的辐角,并且将他们按从小到大排序,如果遇到辐角相同的一些点,则按与原点距离从小到大排序,记为P1~Pn

  ③建栈,入栈P0,P1,P2

  ④选取一个点Pi(i初始值为3),前往步骤⑤;

  ⑥获得栈顶点和次栈顶点Pk,Pk-1

    进行判定:如果 Pk-1 -> P-> P是右转的(其实就是α + β ≥ π),就弹出栈顶元素,并且返回步骤⑥;

         如果是左转的(α + β < π),就入栈点Pi,并且i+=1,返回步骤④;

───────────────────────────────────────────────────────────────────────────────────────────────────────────

4、代码模板:

#include<bits/stdc++.h>
#define MAX 10005
#define eps 1e-6
using namespace std;
struct Point{
    double x,y;
    Point(double tx=0,double ty=0):x(tx),y(ty){}
}p[MAX];
typedef Point Vctor;
Vctor operator - (Point A,Point B){return Vctor(A.x-B.x,A.y-B.y);}
int dcmp(double x)
{
    if(fabs(x)<eps) return 0;
    else return (x<0)?(-1):(1);
}
//叉积
double Cross(Vctor A,Vctor B){return A.x*B.y-A.y*B.x;}
//距离
double dist(Point p1,Point p2){return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));}
bool cmp(Point p1,Point p2)
{
    double tmp=Cross(p1-p[0],p2-p[0]);
    if(!dcmp(tmp)) return dist(p[0],p1)<dist(p[0],p2);
    else return tmp>0;
}
vector<Point> graham_scan(int n)
{
    vector<Point> ans;
    
    if(n<=0) return ans;
    if(n<=2)//当只有1或2个点时
    {
        if(n==2 && (p[1].y<p[0].y || (p[1].y == p[0].y && p[1].x < p[0].x)) ) swap(p[0],p[1]);
        for(int i=0;i<n;i++) ans.push_back(p[i]);
        return ans;
    }
    
    int idx=0;
    for(int i=1;i<n;i++)//选出Y坐标最小的点,若Y坐标相等,选择X坐标小的点
    {
        if(p[i].y<p[idx].y || (p[i].y == p[idx].y && p[i].x < p[idx].x)) idx=i;
    }
    swap(p[0],p[idx]);
    sort(p+1,p+n,cmp);
    for(int i=0;i<=2;i++) ans.push_back(p[i]);
    int top=2;
    for(int i=3;i<n;i++)
    {
        while(top>0 && Cross(p[i]-ans[top-1],ans[top]-ans[top-1]) >= 0)
        {
            ans.pop_back();
            top--;
        }
        ans.push_back(p[i]);
        top++;
    }
    return ans;
}

 

以上是关于关于graham扫描法求凸包的小记的主要内容,如果未能解决你的问题,请参考以下文章

凸包——Graham扫描法和Andrew算法

凸包(Convex Hull)构造算法——Graham扫描法

[hdu contest 2019-07-29] Azshara's deep sea 计算几何 动态规划 区间dp 凸包 graham扫描法

10.1 叉积 ,极角排序,扫描法求凸包

Haskell Lesson:实现Graham扫描算法

《算法》BEYOND 部分程序 part 3