计算几何中的ToLeft测试
判断点是否在三角形内
求解计算几何中的凸包问题时,使用的比较简单,但是复杂度比较高的算法中有一种算法叫做极点法,基本思想是从所有的点中去除所有的不是凸包边上的点.
从所有的剩余的点中,选出三个点,然后再在剩余的点集中,判断是否在三角形的内部,如果在内部,则去除这个点,说明它不是凸包的结果;否则,就暂时保留这个点,说明它可能是凸包边上的点.如下图所示:点a在三角形的内部,肯定不是凸包上的点,点b暂时可能是凸包上的点.
ToLeft测试
不难从上面的图中看出,若把三角形的三个边都设定为逆时针方向,那么点a在三个向量边的左侧.b在其中两条边的左侧,但是在p和q那条边的右侧.
由此可以判断,当一个点在三角形三条逆时针边的左侧时,点必定在三角形的内部,不是凸包边上的解,否则不是在三角形内部.这就是所谓的ToLeft测试.
三角形的有向面积
实现ToLeft测试方法也有很多,最经典的是使用求解矩阵行列式求解有向面积的方式.
设两个点p和q,测试点s是在pq连线的左侧还是右侧,如下图所示.
让p, q,s构成一个三角形,然后各个边,边的方向与pq的方向一致,这个三角形的有向面积计算如下:
这样的一个行列式的计算过程只有乘法和加减法,避免了使用除法或者其他的三角函数计算.写成代码形式就更简单了.
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;
}