ToLeft 测试与计算凸包问题

计算几何中的ToLeft测试

判断点是否在三角形内

求解计算几何中的凸包问题时,使用的比较简单,但是复杂度比较高的算法中有一种算法叫做极点法,基本思想是从所有的点中去除所有的不是凸包边上的点.

从所有的剩余的点中,选出三个点,然后再在剩余的点集中,判断是否在三角形的内部,如果在内部,则去除这个点,说明它不是凸包的结果;否则,就暂时保留这个点,说明它可能是凸包边上的点.如下图所示:点a在三角形的内部,肯定不是凸包上的点,点b暂时可能是凸包上的点.

这里写图片描述

ToLeft测试

不难从上面的图中看出,若把三角形的三个边都设定为逆时针方向,那么点a在三个向量边的左侧.b在其中两条边的左侧,但是在p和q那条边的右侧.

由此可以判断,当一个点在三角形三条逆时针边的左侧时,点必定在三角形的内部,不是凸包边上的解,否则不是在三角形内部.这就是所谓的ToLeft测试.

三角形的有向面积

实现ToLeft测试方法也有很多,最经典的是使用求解矩阵行列式求解有向面积的方式.

设两个点p和q,测试点s是在pq连线的左侧还是右侧,如下图所示.

这里写图片描述

让p, q,s构成一个三角形,然后各个边,边的方向与pq的方向一致,这个三角形的有向面积计算如下:

2 S = | p [ x ] p [ y ] p [ z ] q [ x ] q [ y ] q [ z ] s [ x ] s [ y ] s [ z ] |

这样的一个行列式的计算过程只有乘法和加减法,避免了使用除法或者其他的三角函数计算.写成代码形式就更简单了.

int Area2(Point p, Point q, Point k)
{
    return p.x * q.y - p.y* q.x + q.x * s.y - q.y * s.x + s.x * p.y - s.y * p.x;
}

实现ToLeft测试

上面的代码只是计算有向面积,ToLeft测试是判断是否在直线的左侧,当这个有向面积大于0的时候,在左侧,当小于0的时候,在右侧,当等于0 的时候,三点一线,面积为0.

bool ToLeft(Point p, Point q, Point s)
{
    return Area2(p,q,s) > 0;
}

回头想一下,这里其实符合右手定理.

极点法求解凸包

参考邓老师的课件,根据ToLeft测试,实现一个计算凸包的算法,虽然计算复杂度是O(n^4),但是也是一种解法,下面是实现的一个代码.

#include <iostream>
#include <vector>

using namespace std;

struct Point
{
    Point(int x = 0, int y = 0) : _x(x), _y(y) {}

    int _x;
    int _y;
};

int Area2(Point p, Point q, Point s)
{
    return p._x * q._y - p._y * q._x + q._x * s._y - q._y * s._x + s._x * p._y - s._y * p._x;
}

bool ToLeft(Point p, Point q, Point s)
{
    int i = Area2(p, q, s);
    return i > 0;
}

int main()
{
    vector<Point> points =
            {
                    {1, 1},
                    {2, 2},
                    {2, 0},
                    {2, 4},
                    {3, 3},
                    {4, 2}
            };
    vector<bool> flag(points.size(), false);
    for (int i = 0; i < points.size(); ++i)
    {
        for (int j = 0; j < points.size(); ++j)
        {
            for (int k = 0; k < points.size(); ++k)
            {
                for (int l = 0; l < points.size(); ++l)
                {
                    if(i == j || j ==k || k == i)
                        continue;
                    if (l == k || l == j || l == i || flag[l] == true)
                        continue;
                    if (ToLeft(points[i], points[j], points[l]) &&
                        ToLeft(points[j], points[k], points[l]) &&
                        ToLeft(points[k], points[i], points[l]))
                        flag[l] = true;
                }
            }
        }
    }
    for (int i = 0; i < points.size(); ++i)
    {
        if (flag[i] == false)
            cout << points[i]._x << " " << points[i]._y << endl;
    }
    return 0;
}

极边法求解凸包

极边法要比极点法快一点,时间复杂度是O(n^3).

#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

struct Point
{
    Point(int x = 0, int y = 0) : _x(x), _y(y) {}

    int _x;
    int _y;
};

int Area2(Point p, Point q, Point s)
{
    return p._x * q._y - p._y * q._x + q._x * s._y - q._y * s._x + s._x * p._y - s._y * p._x;
}

bool ToLeft(Point p, Point q, Point s)
{
    int i = Area2(p, q, s);
    return i > 0;
}

int main()
{
    vector<Point> points =
            {
                    {1, 1},
                    {2, 2},
                    {2, 0},
                    {2, 4},
                    {3, 3},
                    {4, 2}
            };

    vector<bool> flag (points.size(), false);

    for (int i = 0; i < points.size(); ++i)
    {
        for (int j = 0; j < points.size(); ++j)
        {
            if (i == j)
                continue;
            bool leftNotEmpty = false;
            bool rightNotEmpty = false;
            for (int k = 0; k < points.size(); ++k)
            {
                if (i == k || j == k)
                    continue;
                int area = Area2(points[i], points[j], points[k]);
                if (area > 0)
                    leftNotEmpty = true;
                if (area < 0)
                    rightNotEmpty = true;
                if (leftNotEmpty && rightNotEmpty)
                    break;
            }
            if (leftNotEmpty == false || rightNotEmpty == false)
            {
                flag[i] = true;
                flag[j] = true;
            }
        }
    }
    for(int i = 0; i< flag.size();++i)
    {
        if(flag[i] == true)
            cout << points[i]._x << " " << points[i]._y << endl;
    }
    return 0;
}

参考

  1. 学堂在线-计算几何

猜你喜欢

转载自blog.csdn.net/qust_waiwai/article/details/81065857