2019春季阿里笔试算法题——判断一个点是否在多边形内部

题目描述:自己任意输入几个点构造一个多边形,然后再随机输入一个点,判断该点是否在多变形里面,如果不在,那么该点离多变形的最短距离是多少。

思路一:

下面是几个比较基本的方法: 
(1)面积法:将这个点与多边形的所有顶点连线,将所形成的所有三角形面和求和,如果和多边形面积相等则点在多边形内部 
(2)夹角法:将这个点与多边形的所有顶点连线,如果夹角和为360°则点在多边形内部 
(3)射线法:以点P为端点,向左方作射线L,由于多边形是有界的,所以射线L的左端一定在多边形外,考虑沿着L从无穷远处开始自左向右移动,遇到和多边形的第一个交点的时候,进入到了多边形的内部,遇到第二个交点的时候,离开了多边形,……所以很容易看出当L和多边形的交点数目C是奇数的时候,P在多边形内,是偶数的话P在多边形外。

解释一下射线法,如下图所示,在点左边有5个交点,奇数个,所以点在多边形内(其实向左向右做射线都是一样的) 
 

思路二:

(1)若为凸多边形

法1:二维平面内判断点是否在一个简单多边形内部,在程序设计中我们一般采用射线法,或者内角和法。

如果这个简单多边形是一个凸多边形,可以在logN的时间复杂度内判断点是否在N个顶点的凸多边形中。

如图 判断点P是否在凸多边形内 设凸多边形顶点保存在convex[0..n-1]中

首先必须满足 向量convex[0]-P X convex[0]-convex[1]<0 convex[0]-P X convex[0]-convex[6]>0

X代表叉乘 如果允许点在多边形边上 <0 >0可以改写为<=0 >=0

这样确定点在角106范围内后   二分枚举2--6每个点x  利用叉乘可以判断向量convex[0]-P在convex[0]-convex[x]逆时针还是顺时针 如果是在逆时针 继续向x--6寻找顶点 否则在2--x-1寻找顶点 直到寻找到最接近P且在P顺时针方向的边convex[0]--convex[x]

此时可以确定点在角x-1 o x范围内

然后向量convex[x-1]-P X convex[x-1]-convex[x] 判断P是否在线段x-1--x的左侧 在左侧点在多边形内 否则不在。

因为任意凸多边形都可以按照其中一个顶点三角剖分 所以有了logN的二分高效算法:

double cross(cpoint p0, cpoint p1, cpoint p2)
{
    return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
 
bool Compl_inside_convex(const cpoint & p,cpoint *con,int n)
{
    if(n<3) return false;
    if(cross(con[0],p,con[1])>-eps) return false;
    if(cross(con[0],p,con[n-1])<eps) return false;
 
    int i=2,j=n-1;
    int line=-1;
 
    while(i<=j)
    {
        int mid=(i+j)>>1;
        if(cross(con[0],p,con[mid])>-eps)
        {
            line=mid;
            j=mid-1;
        }
        else i=mid+1;
    }
    return cross(con[line-1],p,con[line])<-eps;
}

法2:如果一个点在这个凸四边形内,那么按照逆时针方向走的话,该点一定会在每一条的左边。
所以方法就是:按照逆时针方向遍历这个凸四边形的每一条边的两个顶点A(X1,Y1)和B(X2,Y2),然后判断给定点是否在AB矢量左边就可以了。
而判断一个点是否在一个矢量的左边,可以利用矢量叉积。
设A(x1,y1),B(x2,y2),给定点是C(x3,y3),构造两条矢量边:
AB=(x2-x1,y2-y1), AC=(x3-x1,y3-y1)
则AB和AC的叉积为(2*2的行列式):

|x2-x1, y2-y1|

|x3-x1, y3-y1|

值为:r = (x2-x1)(y3-y1) - (y2-y1)(x3-x1)
然后利用右手法则进行判断:
如果r > 0,则点C在矢量AB的左边
如果r < 0,则点C在矢量AB的右边
这样就可以判断点C与AB的相对位置了。然后按照逆时针方向,对凸四边形的每一条边都如此判断,如果每次都判断出C在左边,那么就说明这个点在凸多边形内部了。

(2)若为凹多边形

如果给定的多边形是凹多边形,需要这样:先在凹多边形上添加一些边,使得凹多边形变成凸多边形,然后再按照第一步的方法去判断。示例图如下:

请输入图片描述


对于凹多边形ABCDEFGH,补边BE和FH(图中虚线所示),则可以构成三个凸多边形:凸多边形ABEFH,凸多边形BCDE和凸多边形FGH,然后判断给定点与这三个凸多边形的关系。
如果给定点在凸多边形ABEFH内部,而在凸多边形BCDE和凸多边形FGH的外部,那么就可以说明给定点在多边形ABCDEFGH的内部了。

下面就分别说明如何判断一个三角形是逆时针还是顺时针,如何判断一个多边形的凹凸性,如何补边,以及如何通过补边构造凸多边形。只要构造出所有的凸多边形,就可以按照上述第二种情况判断了。

判断一个三角形的顺时针还是逆时针
(学过3D渲染的同学应该知道,这个判断很重要,涉及到3D空间中哪些三角形需要渲染,而哪些三角形不需要渲染)
可以利用矢量叉积来判断一个给定的三角形是逆时针还是顺时针。
假设给定三角形的三个顶点A(x1,y1),B(x2,y2),C(x3,y3),构造三角形两边的矢量分别是:
AB=(x2-x1,y2-y1), AC=(x3-x1,y3-y1)
则AB和AC的叉积为:

|x2-x1, y2-y1|

|x3-x1, y3-y1|

值为:r = (x2-x1)(y3-y1) - (y2-y1)(x3-x1)
然后利用右手法则进行判断:
如果r>0,则三角形ABC是逆时针的
如果r<0,则三角形ABC是顺时针的
这样就可以判断一个给定三角形是顺时针还是逆时针了。
(如果这里你发现计算出r=0,那么说明ABC三点共线,即给定点在多边形的边上,可以直接返回)

判断一个多边形的凹凸性。
利用上述判断三角形是顺时针还是逆时针的方法来辅助判断一个多边形的凹凸性。
对于一个给定的多边形,按照逆时针方向遍历该多边形的所有顶点,并放入一个首尾相连的循环链表中。然后从这个循环链表中每次取出相邻三个顶点,判断这个三个顶点构成的三角形是顺时针三角形还是逆时针三角形,如果是逆时针,则说明该三角形三个顶点中位于中间的顶点是凸点;如果是顺时针,则说明位于中间的顶点是凹点。
例如:假设取出的三个顶点是A、B、C,如果三角形ABC是逆时针三角形,则说明顶点B(即位于中间的顶点)是凸点,否则是凹点。然后可以取出D点,通过三角形BCD判断C点的凹凸性,同理D点,E点...最后也可以判断出A点的凹凸性。(通过由最后一个顶点,A点,B点按顺序构成的三角形来判断A点的凹凸性)。如果该多边形所有的顶点经过判断都是凸点,则说明该多边形是凸多边形,否则就是凹多边形。
对于上述示例用图,多边形ABCDEFGH中,点A,B,E,F,H是凸点,点C,D,G是凹点。由于并不是所有的顶点都是凸点,所以多边形ABCDEFGH是凹多边形。

补边
利用上述对给定多边形的每一个顶点的凹凸性的判断来进行补边操作。
补边算法:
1.从存储多边形顶点的循环链表中,找到一个凸点(一定存在这个凸点,即不可能所有顶点都是凹点)
2.从这个顶点开始往后遍历顶点,当遇到的第一个凹点时标记紧邻这个凹点的前一个凸点,因为这个凸点就是所需要补的边的一个顶点,然后继续往后遍历,如果还是凹点,跳过继续遍历,直至找到第一个凸点,那么这个凸点就是所需要补的边的另一个顶点。这样就找出了所需要补的边的两个顶点,就可以确定需要补的一条边了。
3.从找到的凸点开始,重复上述第2步操作。直至回到最开始找到的凸点,则算法结束。这样就可以找到所有需要补的边了。
可以发现,每一条补边,起点和终点都是凸点,中间经过的顶点都是凹点。
上述示例用图中,对于补边BE,顶点B和E都是凸点,而它们中间的顶点C和D则都是凹点。对于补边FH,顶点F和H都是凸点,而它们中间的顶点G则是凹点。

构造凸多边形
利用上述补边来构造凸多边形。
算法如下:
1.对于找到每一条补边,起点和终点都知道,就可以从多边形顶点的循环链表中取出这两个顶点中间的所有的顶点,而这些顶点加上起点和终点就构成了一个外在凸多边形(即不属于原始多边形的多边形)。
示例中,外在凸多边形有凸多边形BCDE和凸多边形FGH。
2.在原始多边形顶点的循环链表中把所有的凹点都去除,只留下凸点。那么这些凸点即构成了原始多边形经补边之后构造的大凸多边形了。
示例中,去除所有凹点C,D,G,则剩下的顶点A,B,E,F,G构成了补边之后的大凸多边形ABEFH

经过上面的所有分析,就可以轻松判断任意一个点是否在一个给定多边形的内部了。

猜你喜欢

转载自blog.csdn.net/hitzijiyingcai/article/details/89206558