计算几何(一)凸包
求解三角形面积:海伦-秦九韶公式
判断某点在某条直线的左侧:利用上面的海伦公式
bool ToLeft(Point p, Point q, Point s)
{
return Area2(p, q, s) > 0;
}
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;
}
判断某点是否在三角形内部:那么就对该三个点以逆时针方向两两连成有向边,如果该点在这三条直线的左侧,那么成立。(复杂度为O(n^4))
bool InTriangle(Point p, Point q, Point r, Point s)
{
bool pqLeft = ToLeft(p, q, s);
bool qrLeft = ToLeft(q, r, s);
bool rpLeft = ToLeft(r, p, s);
return (pdLeft == qrLeft) && (qrLeft == rqLeft);
}
找出所有极点
1、沿着这个点作直线,必然能找到一条直线,使得其他所有点都在该直线的一侧来判断是否是极点(复杂度为O(n^3))
void markEE(Point S[], int n) //n>2
{
for(int k = 0; k <n; k ++)
S[k].extreme = False; //先假设所有的点都不是极点
for(int p = 0; p < n; p ++)
for(int q = p + 1; q < n; q ++)
checkEdge(S, n, p, q);
}
void checkEdge(Point S[], int n, int p, int q)
{
bool LEmpty = True, REmpty = True;
for(int k = 0; k < n && (LEmpty || REmpty); k ++)
{
if(k != p && k != q)
{
ToLeft(S[p], S[q], S[k]) ? LEmpty = False : REmpty = False;
}
}
if(LEmpty || REmpty)
S[p].extreme = S[q].extreme = True;
}
2、Jarvis算法:在这里定义逆时针为正方向,一个点的前驱就是指该点在逆时针方向的下一个点,后继是顺时针方向的下一个点。Javis March算法是输出敏感的,它的复杂性会随着凸包的规模变化而变化
void Jarvis(Point S[], int n)
{
for(int k = 0; k < n; k ++)
S[k].extreme = false; //假设所有的点都不是极点
int ltl = LTL(S, n); int k = ltl; //先利用LTL算法找到第一个极点,k是起点
do
{
P[k].extreme = true; int s = -1; //s是要找的下一个极点,用t去循环找
for(int t = 0; t < n; t ++)
{
if(t != k && t != s && (s == -1 || !ToLeft(P[k], P[s], P[t]))
s = t; //如果t在pq的右边,更新s
}
P[k].succ = s; k = s; //新的极边pq确定
}while(ltl != k); //如果循环回到了原点,结束
}
int LTL(Point S[], int n)
{
int ltl = 0;
for(int k = 1; k < n; k ++)
{
if(P[k].y < P[ltl].y || (P[k].y == P[ltl].y && P[k].x < P[ltl].x)
ltl = k;
}
return ltl;
}
判断某个点是否在多边形内部:对于x点与原凸包的任意一个点v的做一个射线,那么v存在一个前驱和后继,如果前驱和后继都在射线右边或左边,那么它是一条切线。如果找不到这样的切线,那么该点在凸包内部。
3、Graham Scan算法:首先先有一条极边(LTL),然后在根据极角将剩下的点由小到大排序。有S栈和T栈,初始时点1和点2在S栈中,其他的点依次在T栈栈顶到栈底。核心代码:如果T栈顶的点在S栈顶次栈顶的两个点的左边,那么就把T栈顶的点push进S栈,否则就把S栈顶的点pop出来。程序最后S栈中的所有元素就是所有极点。
while(!T.empty())
{
toLeft(S[1],S[0],T[0]) ? S.push(T.pop()) : S.pop();
}
4、分而治之(Divided-And-Conquer):对于若干个点进行适当的分组,分别构造子凸包,然后再把它们合并成一个凸包
对于上图是属于比较好的情况,因为两个子凸包的极点都分别于核有序,那么只需要二路归并将这些点变成整体有序,然后在进行Graham Scan。
我们就找出两个切点,那么ts段是无用的。然后舍弃完ts后就对st段的点和另外一个子凸包进行2-way merge,然后再进行Graham Scan。
首先两个子凸包构建的时候就让他们是可以通过一条垂线分开的。我们只需要找到两条切线就可以把这两个子凸包合并。我们的实际操作是首先找到左边的子凸包的最右边的点,然后找到右边子凸包最左边的点。在这里的寻找方法只要之前在一步步构造子凸包的时候对他们的最左最右点进行记录,那么在需要的时候只需花费O(1)的时间就能找到,不然的话就需要用O(n)的时间来寻找这两个点。