【问题标题】:Gift Wrapping Algorithm with Collinear Points具有共线点的礼品包装算法
【发布时间】:2015-11-25 19:58:21
【问题描述】:

所以我根据礼品包装算法的示例编写了以下代码,用于查找一组点的凸包:

std::vector<sf::Vector2f> convexHull(const std::vector<sf::Vector2f>& _shape)
{
    std::vector<sf::Vector2f> returnValue;    
    returnValue.push_back(leftmostPoint(_shape));
    for (std::vector<sf::Vector2f>::const_iterator it = _shape.begin(), end = _shape.end(); it != end; ++it)
    {
        if (elementIncludedInVector(*it, returnValue)) continue;
        bool allPointWereToTheLeft = true;
        for (std::vector<sf::Vector2f>::const_iterator it1 = _shape.begin(); it1 != end; ++it1)
        {
            if (*it1 == *it || elementIncludedInVector(*it1, returnValue)) continue;
            if (pointPositionRelativeToLine(returnValue.back(), *it, *it1) > 0.0f)
            {
                allPointWereToTheLeft = false;
                break;
            }
        }
        if (allPointWereToTheLeft)
        {
            returnValue.push_back(*it);
            it = _shape.begin();
        }
    }
    return returnValue;
}

这是我确定第三个点在哪一侧的函数:

float pointPositionRelativeToLine(const sf::Vector2f& A, const sf::Vector2f& B, const sf::Vector2f& C)
{
    return (B.x - A.x)*(C.y - A.y) - (B.y - A.y)*(C.x - A.x);
}

返回负数表示点在一侧,正数在另一侧,0表示三个点共线。 现在,问题是:如何修改上述代码,使其在 _shape 中存在共线点时也能正常工作?

【问题讨论】:

    标签: c++ algorithm geometry convex


    【解决方案1】:

    如果某些点是共线的,则必须选择离它们最远的点(到当前点的最大距离)

    【讨论】:

    • 最远点不一定是所有其他点都在新形成的线的左侧,从而导致错误。
    • 离共线最远的!
    • 这条规则非常正确。实际上,您实现了字典比较:首先是角度(通过符号区域),然后是距离。
    • 这条规则是正确的,但它确实需要格外小心。当有多个“最左边的点”时,我认为你应该从最上面的一个或最下面的一个开始。因为如果你从中间开始,你会回到 ccw 的最顶部或 cw 的最底部,并且根据你的循环条件,如果你正在检查是否你可能会进行不必要的迭代或陷入无限循环已经到达第一个元素。我不确定这个实现是否会受到影响,但由于它的成本几乎为零,我认为最好总是这样做。
    【解决方案2】:

    正确执行此操作比您演示的代码要复杂一些。我只会关注你的谓词的稳定性,而不是你如何处理共线点。谓词是您进行几何计算的地方 - pointPositionRelativeToLine

    您的代码设计得很好,因为您只在谓词中进行几何计算。这是使它健壮的必要条件。唉,你的谓词不应该返回一个浮点数,而是一个小集合的结果:LEFTRIGHTCOLLINEAR

    enum RelPos { LEFT, RIGHT, COLLINEAR };
    
    RelPos pointPositionRelativeToLine(const sf::Vector2f& A, const sf::Vector2f& B, const sf::Vector2f& C)
    {
        auto result = (B.x - A.x)*(C.y - A.y) - (B.y - A.y)*(C.x - A.x);
        if (result < 0.0) return LEFT;
        else if (result > 0.0) return RIGHT;
        return COLLINEAR;
    }
    

    然后,您可以弄清楚如何保证给定任意三个点,它们的任何排列都会返回正确的答案。这是必要的,否则,您的算法无法保证有效。

    一般有两种方法:

    1. 使用适当的数据类型,以确保在谓词中使用时结果准确。

    2. 接受您使用的不精确数据类型,有些输入无法计算结果。具体来说,您可以让谓词提供第四个值INDETERMINATE,并在这种情况下返回它。

    通过为输入的所有排列调用原始谓词,第二种方法很容易实现:

    enum RelPos { LEFT, RIGHT, COLLINEAR, INDETERMINATE };
    typedef sf::Vector2f Point_2;
    
    RelPos ppImpl(const Point_2 & A, const Point_2 & B, const Point_2 & C)
    {
        auto result = (B.x - A.x)*(C.y - A.y) - (B.y - A.y)*(C.x - A.x);
        if (result < 0.0) return LEFT;
        else if (result > 0.0) return RIGHT;
        return COLLINEAR;
    }
    
    bool inverse(RelPos a, RelPos b) {
      return a == LEFT && b == RIGHT || a == RIGHT && b == LEFT;
    }
    
    bool equal(RelPos a, RelPos b, RelPos c, RelPos d, RelPos e, RelPos f) {
      return a==b && b==c && c==d && d==e && e==f;
    }
    
    RelPos pointPositionRelativeToLine(const Point_2 & A, const Point_2 & B, const Point_2 & C) {
      auto abc = ppImpl(A, B, C);
      auto bac = ppImpl(B, A, C);
      auto acb = ppImpl(A, C, B);
      auto cab = ppImpl(C, A, B);
      auto bca = ppImpl(B, C, A);
      auto cba = ppImpl(C, B, A);
      if (abc == COLLINEAR) return equal(abc, bac, acb, cab, bca, cba) ?
        COLLINEAR : INDETERMINATE;
      if (!inverse(abc, bac) || !inverse(acb, cab) || !inverse(bca, cba))
        return INDETERMINATE;
      if (abc != bca || abc != cab)
        return INDETERMINATE;
      return abc;
    }
    

    上面的逻辑可能有错误,希望我是对的。但这是这里的一般方法。至少上述对给定数据集的测试必须通过,算法才能在数据集上工作。但如果这是一个充分条件,我不记得了。

    当然,算法必须在从谓词中获得INDETERMINATE 结果时终止:

    const auto errVal = std::vector<sf::Vector2f>();
    ...
    auto rel = pointPositionRelativeToLine(returnValue.back(), *it, *it1);
    if (rel == INDETERMINATE) return errVal;
    if (rel == RIGHT) {
      allPointWereToTheLeft = false;
      break;
    }
    

    【讨论】:

      【解决方案3】:

      您可以基于两点之间的“排除”关系(围绕共同中心)进行推理,这意味着如果 A 和 B 的相对位置证明 B 不能在凸包上,则 A 排除 B。

      在图中,绿色点不包括蓝色点,而红色点不包括。在两个对齐的点中,离中心最远的点不包括另一个。排除轨迹是一个开放的半平面和一条半线。

      请注意,“排除”是可传递的,并定义了总排序。

      【讨论】:

        猜你喜欢
        • 2012-04-18
        • 2023-03-21
        • 2011-12-07
        • 1970-01-01
        • 2017-02-09
        • 2015-07-28
        • 2011-01-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多