凸包检测介绍及Graham算法实现

本文详细介绍了2维Graham算法用于凸包检测的过程,包括算法步骤、排序原理以及C++代码实现。通过比较点的横纵坐标的差异和向量叉积来确定顶点顺序,有效构造凸包.
摘要由CSDN通过智能技术生成

1. 问题引出

凸包是计算几何的一个概念,所谓的凸包问题就是:给定点集,求构成凸包的点.所谓凸,用数学上的描述是,凸集中的任意两点连线,该线上的任意点均在凸集内,与之相对就是凹包.


凸集


凹集

目前的算法有:

  • 穷举法:
    • 思路:逐条线检测是否所有其它边都在它的一侧
    • 时间复杂度:O(n^3)
  • 分治法:
    • 思路:将大问题分成结构相同的小问题,再用递归的方法解决.简单来说,对集合求距离最远的点,连接该点后将集合分上下包,再将子集重复上面步骤,参考[1];
    • 时间复杂度:O(nLogn)
  • Jarvis步进法:
    • 思路:先找到几何某一个方向的最小点(例如纵坐标),然后逆时针方向找下一个顶点,判断是否为顶点的依据是前后两点组成的边的夹角;
    • 时间复杂度:O(nH)
  • Graham算法:
    • 思路:与Jarvis步进法类似,不同点事不直接使用夹角,而是两边的叉积;
    • 时间复杂度:O(nLogn)

下文介绍以2维的Graham算法为例介绍凸包检测.

2. Graham算法(2维)

2.1 算法流程

步骤:

  1. 从集合中找到纵坐标最小的点,设为P0
  2. 计算其它所有点与P0组成的向量的模长和角度,利用模长和角度对点集排序,原则如下:
    • 向量与x轴(0,1)的夹角 α \alpha α 越小则越靠前
    • 当夹角 α \alpha α 相等时,模长越短越靠前
  3. 将P0和排序后的第一个点放入栈中
  4. while(取出当前点Pi != end):
    1. while(将栈顶[top]和栈顶下一个点[top-1]连线L1,Pi和[top]连线L2,通过L1和L2叉积判断L2是否满足逆时针的关系,如果不满足):
      栈顶出栈;
    2. 将当前点放入栈.

分析:为什么排序?

我认为:由于我们是按照逆时针的顺序使用边进行逐一"包裹",那么最后出来的顶点一定也同样沿着逆时针顺序出现,既然如此,事先对点集排序,可以有效避免盲目地进行顶点搜索.

2.2 C++实现


#define EPS 1e-15
typedef std::vector<Eigen::Vector2f> VecPoint;

float Cross(const Eigen::Vector2f& p1, const Eigen::Vector2f& p2)
{
    return (p1.x()*p2.y() - p1.y()*p2.x());
}

bool Compare(Eigen::Vector2f& p1, Eigen::Vector2f& p2)
{
    float tmp = Cross(p1, p2);

    if(tmp > EPS)
        return true;
    else if(tmp < -EPS)
        return false;
    else {
        if(p1.norm() < p2.norm())
            return true;
        else
            return false;
    }
}

void GrahamSlove(VecPoint vPoints, VecPoint& vTops, VecPoint& vInliers)
{
    if(vPoints.size() <= 3)
        return;

    // 1. 找到最左下角的点
    size_t first = 0;
    for(size_t i = 1; i < vPoints.size(); i++) {
        float tmp = vPoints[i].y() - vPoints[first].y();
        if(tmp < -EPS) {
            first = i;
        } else if(fabs(tmp) < EPS && vPoints[i].x() < vPoints[first].x()) {
            if(vPoints[i].x() < vPoints[first].x())
                first = i;
        }
    }

    vPoints[0].swap(vPoints[first]);
    Eigen::Vector2f origin = vPoints[0];

    // 2. 排序
    for(auto& p: vPoints) {
        p -= origin;
    }
    std::sort(vPoints.begin()+1, vPoints.end(), Compare);

    // 3. 如符合要求入栈
    std::vector<size_t> stack(vPoints.size(), 0);
    stack[0] = 0;
    stack[1] = 1;
    size_t top = 2;
    
    for(size_t i = 2; i < vPoints.size(); i++) {
        while((top > 1) && 
              (Cross(vPoints[stack[top-1]] - vPoints[stack[top-2]], vPoints[i]-vPoints[stack[top-2]])  < 0.) ) {
            top--;
        }
        stack[top++] = i;
    }

    vTops.resize(top);
    vInliers.resize(vPoints.size() - top);
    for(size_t i = 0, j = 0, k = 0; i < vPoints.size(); i++) {
        if(stack[j] == i) {
            vTops[j++] = vPoints[i] + origin;
        } else {
            vInliers[k++] = vPoints[i] + origin;
        }
    }
}

参考文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值