凸包Graham Scan算法实现

凸包算法实现点集合中搜索凸包顶点的功能,可以处理共线情况,可以输出共线点也可以不输出而只输出凸包顶点。经典的Graham Scan算法,点排序使用极角排序方式,并对共线情况做特殊处理。一般算法是将共线的点去掉距离小的,保留最远的,这样处理会导致不能输出凸包边上的点,只能输出顶点。但是有时候需要输出这些边上的点,因此这里我将共线点都保留,并按照顺序排列。共线点排列方式是:非起始边按照从远道近排列,起始边按从近到远排列。

算法原理参见如下网址,讲解很详细:

http://softsurfer.com/Archive/algorithm_0109/algorithm_0109.htm

实现如下:

#include <iostream>
#include <math.h>
using namespace std;
typedef struct{double x,y;} Point;
void qsortpoint(Point s[],Point base,int start,int end);
void sortstartedge(Point s[],int nums);
//向量(x1,y1),(x2,y2)的叉积
double CrossMul(double x1,double y1,double x2,double y2)
{
    return x1*y2-x2*y1;
}
//向量(x1,y1),(x2,y2)的点积
double DotMul(double x1,double y1,double x2,double y2)
{
    return x1*x2+y1*y2;
}
//跨立判断
//判断点c是在向量ab的逆时针方向还是顺时针方向,大于零逆时针,等于0则共线
double CrossMul(Point a,Point b,Point c)
{
    return CrossMul(b.x-a.x,b.y-a.y,c.x-a.x,c.y-a.y);
}
//计算向量ab和ac点积
double DotMul(Point a,Point b,Point c)
{
    return DotMul(b.x-a.x,b.y-a.y,c.x-a.x,c.y-a.y);
}
//判断浮点数符号
int doublecmp(double d)
{
    if(fabs(d)<10e-6)
        return 0;
    return d>0?1:-1;
}
//判断同一直线上的三个点位置,点c是否在点ab之间
bool betweenCmp(Point a,Point b,Point c)
{
    if(doublecmp(DotMul(c,a,b))<=0)
        return true;
    return false;
}
//判断j是否在base->i向量的左边或当共线时j是否位于它们的线段之间
bool isLeftorNearer(Point base,Point i,Point j)
{
    if(CrossMul(base,i,j)>0)
        return true;
    if(CrossMul(base,i,j)==0 && betweenCmp(base,i,j))
        return true;
    return false;
}
void swap(Point& a,Point& b)
{
    Point temp = b;
    b=a;
    a=temp;
}
//以s中的最低点为参考点,对其他所有点进行极角排序(逆时针)
//共线时离参考点较远的点排在前面,凸包的起始边共线点从近到远排列
void sortpoint(Point s[],int nums)
{
    //找最低点
    for(int i=1;i<nums;i++)
    {
        if(s[i].y<s[0].y || (s[i].y==s[0].y && s[i].x<s[0].x))
            swap(s[0],s[i]);
    }
    qsortpoint(s,s[0],1,nums);
    //将起始边上的共线点重新排列
    sortstartedge(s,nums);
}
void sortstartedge(Point s[],int nums)
{
    int i,j;
    for(i=2;i<nums;i++)
    {
        if(CrossMul(s[0],s[1],s[i])!=0)
            break;
    }
    for(j=1;j<(i+1)/2;j++)
        swap(s[j],s[i-j]);
}

//将点按极角逆时针排序
void qsortpoint(Point s[],Point base,int start,int end)
{
    if(start>=end)
        return;
    Point partition = s[end-1];
    int i=start-1,j=start-1;
    while(++j<end-1)
    {
        if(isLeftorNearer(base,s[j],partition))
        {
            swap(s[++i],s[j]);
        }
    }
    swap(s[++i],s[end-1]);
    qsortpoint(s,base,start,i);
    qsortpoint(s,base,i+1,end);
}
void ConvexHull(Point s[],int nums,Point result[],int& resultnums)
{
    sortpoint(s,nums);

    resultnums = 0;
    if(nums<=3)
    {
        for(int i=0;i<nums;i++)
            result[resultnums++] = s[i];
        return;
    }
    int top=0;
    int i;
    for(i=0;i<2;i++)
        result[top++] = s[i];
    while(i<nums)
    {
        //用<号判断则包含凸包边上的共线点,<=号判断则只包含凸包顶点
        if(CrossMul(result[top-2],result[top-1],s[i])<=0)
        {
            top--;
        }
        else
        {
            result[top++] = s[i++];
        }
    }
    //最后加入起点形成闭包
    while(CrossMul(result[top-2],result[top-1],s[0])<=0)
    {
        top--;
    }
    result[top++]=s[0];
    resultnums = top;
}
int main()
{
    Point pa[] = {{0,0},{1,0},{2,0},{3,0},{4,0},
                {4,1},{4,2},{4,3},{4,4},
                {3,4},{2,4},{1,4},{0,4},
                {0,3},{0,2},{0,1},{2,2},{1,1}};

    cout<<"convex hull is:"<<endl;
    Point result[18];
    int nums;
    ConvexHull(pa,18,result,nums);
    for(int i=0;i<nums;i++)
        cout<<result[i].x <<"," <<result[i].y<<endl;
    return 0;
}
经验证,算法无误。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值