• 判断点在多边形内,适合打卡,地图判定


    射线法

    时间复杂度:O(n) 适用范围:任意多边形
    个人认为是非常不错的算法(不需考虑精度误差和多边形点给出的顺序),可以作为第一选择。

    算法思想:
    以被测点Q为端点,向任意方向作射线(一般水平向右作射线),统计该射线与多边形的交点数。如果为奇数,Q在多边形内;如果为偶数,Q在多边形外。计数的时候会有一些特殊情况,如图:

    image-20220507224110225

    图片已经把特殊情况和算法实现说的很清楚了,下面我直接贴代码,具体可看代码注释。

    const double eps = 1e-6;
    const double PI = acos(-1);
    //三态函数,判断两个double在eps精度下的大小关系
    int dcmp(double x)
    {
        if(fabs(x)<eps) return 0;
        else
            return x<0?-1:1;
    }
    //判断点Q是否在P1和P2的线段上
    bool OnSegment(Point P1,Point P2,Point Q)
    {
        //前一个判断点Q在P1P2直线上 后一个判断在P1P2范围上
        return dcmp((P1-Q)^(P2-Q))==0&&dcmp((P1-Q)*(P2-Q))<=0;
    }
    //判断点P在多边形内-射线法
    bool InPolygon(Point P)
    {
        bool flag = false; //相当于计数
        Point P1,P2; //多边形一条边的两个顶点
        for(int i=1,j=n;i<=n;j=i++)
        {
            //polygon[]是给出多边形的顶点
            P1 = polygon[i];
            P2 = polygon[j];
            if(OnSegment(P1,P2,P)) return true; //点在多边形一条边上
            //前一个判断min(P1.y,P2.y)<P.y<=max(P1.y,P2.y)
            //这个判断代码我觉得写的很精妙 我网上看的 应该是大神模版
            //后一个判断被测点 在 射线与边交点 的左边
            if( (dcmp(P1.y-P.y)>0 != dcmp(P2.y-P.y)>0) && dcmp(P.x - (P.y-P1.y)*(P1.x-P2.x)/(P1.y-P2.y)-P1.x)<0)
                flag = !flag;
        }
        return flag;
    }
    

    hdu1756-射线法代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const double eps = 1e-6;
    const double PI = acos(-1);
    
    int n,m;
    
    struct Point{
        double x,y;
        Point(double x=0,double y=0):x(x),y(y){}
    
        //向量+
        Point operator +(const Point &b)const
        {
            return Point(x+b.x,y+b.y);
        }
        //向量-
        Point operator -(const Point &b)const
        {
            return Point(x-b.x,y-b.y);
        }
        //点积
        double operator *(const Point &b)const
        {
            return x*b.x + y*b.y;
        }
        //叉积
        //P^Q>0,P在Q的顺时针方向;<0,P在Q的逆时针方向;=0,P,Q共线,可能同向或反向
        double operator ^(const Point &b)const
        {
            return x*b.y - b.x*y;
        }
    }polygon[105];
    typedef Point Vector;
    
    //三态函数,判断两个double在eps精度下的大小关系
    int dcmp(double x)
    {
        if(fabs(x)<eps) return 0;
        else
            return x<0?-1:1;
    }
    
    //判断点Q是否在P1和P2的线段上
    bool OnSegment(Point P1,Point P2,Point Q)
    {
        return dcmp((P1-Q)^(P2-Q))==0&&dcmp((P1-Q)*(P2-Q))<=0;
    }
    
    //判断点P在多边形内-射线法
    bool InPolygon(Point P)
    {
        bool flag = false;
        Point P1,P2; //多边形一条边的两个顶点
        for(int i=1,j=n;i<=n;j=i++)
        {
            P1 = polygon[i];
            P2 = polygon[j];
            if(OnSegment(P1,P2,P)) return true; //点在多边形一条边上
            if( (dcmp(P1.y-P.y)>0 != dcmp(P2.y-P.y)>0) && dcmp(P.x - (P.y-P1.y)*(P1.x-P2.x)/(P1.y-P2.y)-P1.x)<0)
                flag = !flag;
        }
        return flag;
    }
    
    int main()
    {
        while(~scanf("%d",&n))
        {
            for(int i=1;i<=n;i++) scanf("%lf %lf",&polygon[i].x,&polygon[i].y);
            Point test;
            scanf("%d",&m);
            while(m--)
            {
                scanf("%lf %lf",&test.x,&test.y);
                if(InPolygon(test)) printf("Yes\n");
                else printf("No\n");
            }
        }
        return 0;
    }
    
    

    角度和判断法

    时间复杂度:O(n) 适用范围:任意多边形
    感觉这个方法和之后要介绍的转角法类似,个人感觉转角法就是这个方法的优化变形。个人非常不推荐这个算法,最好就是不用。这个算法对精度的要求很高(会造成很大精度误差),不强调多边形点给出顺序,我用这个算法没过hdu1756。

    算法思想:
    连接被测点与多边形所有顶点所形成的所有角的角度和在精度范围内等于2π2π则该点在多边形内,否则在多边形外。
    不推荐,所以就不画图了。

    const double eps = 1e-6;
    const double PI = acos(-1);
    //三态函数,判断两个double在eps精度下的大小关系
    int dcmp(double x)
    {
        if(fabs(x)<eps) return 0;
        else
            return x<0?-1:1;
    }
    //判断点Q是否在P1和P2的线段上
    bool OnSegment(Point P1,Point P2,Point Q)
    {
        //前一个判断点Q在P1P2直线上 后一个判断在P1P2范围上
        return dcmp((P1-Q)^(P2-Q))==0&&dcmp((P1-Q)*(P2-Q))<=0;
    }
    //判断点P在多边形内-角度和判断法
    //精度要求高 最好不用
    bool InPolygon(Point P)
    {
        double angle = 0;
        Point P1,P2; //多边形一条边的两个顶点
        Vector V1,V2; //以被测点为原点 P1 P2与P形成的向量
        for(int i=1,j=n;i<=n;j=i++)
        {
            P1 = polygon[i];
            P2 = polygon[j];
            if(OnSegment(P1,P2,P)) return true; //点在多边形一条边上
            V1 = P1-P;
            V2 = P2-P;
            double res = atan2(V2.y,V2.x)-atan2(V1.y,V1.x);
            res = abs(res);
            if(dcmp(res-PI)>0) res = 2*PI-res;
            angle += res;
        }
        return dcmp(2*PI-angle)==0;
    }
    

    转角法

    时间复杂度:O(n) 适用范围:任意多边形
    个人感觉是O(n)算法的第二推荐,该算法本来对精度要求较高,之后会有一个改进让其不用考虑精度误差,不过该算法要强调多边形点给出的顺序。
    一般博客都以多边形正向即逆时针介绍,我这里也主要介绍逆时针,但hdu1756是顺时针给出,我会在括号中介绍一下顺时针(其实本质是一样的),顺时针具体在代码注释中提一下。不会画图,希望大家看了思想自己画图体会一下。

    算法思想:
    转角法非常简单,按照多边形顶点逆时针顺序,从P点到顶点Vi分别做连线,其中αi为Vi和Vi+1之间的夹角。其中α角度逆时针为正,顺时针为负,这样所有到顶点做连线之间夹角和为(环绕数)0,这点P在多边形外部,否则在内部。(感觉和角度和判断法本质一样,加了个方向)
    (顺时针就是角度顺时针为正,逆时针为负)

    image-20220507224450862

    直接环绕数的推导会需要用到反三角函数,这样即会耗时又会造成较大的精度误差,所以这里有一个优化。
    从P点向右做射线R,如果边从射线R下方跨到上方,那么穿越+1,如果从上方跨到下方,则是-1。最终和为wn环绕数。如下图所示:
    (写博客的时候才发现其实本质不就是射线吗,不过理解后代码会感觉写的比射线简单)

    image-20220507224513071

    这种方法不必去计算射线和边的交点,但需要判断点P和边的左右关系,而且对于方向向上和向下的边的判断规则不同。对于方向向上的边,如果穿过射线,那么P是在有向边的左侧;方向向下的边如果穿过射线,那么P在有向边的右边(意思是说判断点P与边的关系,而不是相对坐标系内位置)。

    image-20220507224531657

    这里有一点要注意,如下图

    image-20220507224550213

    这里射线经过了BCCD并穿过C点,但是要计算两次,一次+1一次-1,代码中会有体现。

    const double eps = 1e-6;
    const double PI = acos(-1);
    //三态函数,判断两个double在eps精度下的大小关系
    int dcmp(double x)
    {
        if(fabs(x)<eps) return 0;
        else
            return x<0?-1:1;
    }
    //判断点Q是否在P1和P2的线段上
    bool OnSegment(Point P1,Point P2,Point Q)
    {
        //前一个判断点Q在P1P2直线上 后一个判断在P1P2范围上
        return dcmp((P1-Q)^(P2-Q))==0&&dcmp((P1-Q)*(P2-Q))<=0;
    }
    //判断点P在多边形内-转角法(多边形点顺时针给出)
    bool InPolygon(Point P)
    {
        int wn = 0;
        Point P1,P2; //多边形一条边的两个顶点
        Vector V1,V2; //被测点与P1分别于P2形成的向量
        for(int i=1,j=n;i<=n;j=i++)
        {
            P1 = polygon[j];  //顺时针中前一点
            P2 = polygon[i];  //顺时针中后一点
            if(OnSegment(P1,P2,P)) return true; //点在多边形一条边上
            V1 = P2-P1;
            V2 = P-P1;
            int k = dcmp(V1^V2); //用于判断被测点在有向边的左右
            int d1 = dcmp(P1.y-P.y); //用于判断向上还是向下穿过
            int d2 = dcmp(P2.y-P.y);
            //V1在V2的顺时针方向即测试点在有向边左边 并且有向边向上穿过
            if(k>0 && d1<=0&&d2>0) wn--;  
            //V1在V2的逆时针方向即测试点在有向边右边 并且有向边向下穿过
            if(k<0 && d1>0&&d2<=0) wn++;  
    //上面wn+和wn- 一个允许起点在射线上另一个允许终点在射线上 最后特殊情况就会算两次
    //逆时针的wn+和wn-自己画图体会一下
        }
        //不为0即在多边形内 不管是正还是负
        return wn!=0;
    }
    

    hdu1756-转角法代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const double eps = 1e-6;
    const double PI = acos(-1);
    
    int n,m;
    
    struct Point{
        double x,y;
        Point(double x=0,double y=0):x(x),y(y){}
    
        //向量+
        Point operator +(const Point &b)const
        {
            return Point(x+b.x,y+b.y);
        }
        //向量-
        Point operator -(const Point &b)const
        {
            return Point(x-b.x,y-b.y);
        }
        //点积
        double operator *(const Point &b)const
        {
            return x*b.x + y*b.y;
        }
        //叉积
        //P^Q>0,P在Q的顺时针方向;<0,P在Q的逆时针方向;=0,P,Q共线,可能同向或反向
        double operator ^(const Point &b)const
        {
            return x*b.y - b.x*y;
        }
    }polygon[105];
    typedef Point Vector;
    
    //三态函数,判断两个double在eps精度下的大小关系
    int dcmp(double x)
    {
        if(fabs(x)<eps) return 0;
        else
            return x<0?-1:1;
    }
    
    //判断点Q是否在P1和P2的线段上
    bool OnSegment(Point P1,Point P2,Point Q)
    {
        return dcmp((P1-Q)^(P2-Q))==0&&dcmp((P1-Q)*(P2-Q))<=0;
    }
    
    //判断点P在多边形内-转角法(多边形点顺时针给出)
    bool InPolygon(Point P)
    {
        int wn = 0;
        Point P1,P2; //多边形一条边的两个顶点
        Vector V1,V2; //被测点与P1分别于P2形成的向量
        for(int i=1,j=n;i<=n;j=i++)
        {
            P1 = polygon[j];  //顺时针中前一点
            P2 = polygon[i];  //顺时针中后一点
            if(OnSegment(P1,P2,P)) return true; //点在多边形一条边上
            V1 = P2-P1;
            V2 = P-P1;
            int k = dcmp(V1^V2);
            int d1 = dcmp(P1.y-P.y);
            int d2 = dcmp(P2.y-P.y);
            if(k>0 && d1<=0&&d2>0) wn--;  //测试点在有向边左边 有向边向上穿过
            if(k<0 && d1>0&&d2<=0) wn++;  //测试点在有向边右边 有向边向下穿过
        }
        return wn!=0;
    }
    
    int main()
    {
        while(~scanf("%d",&n))
        {
            for(int i=1;i<=n;i++) scanf("%lf %lf",&polygon[i].x,&polygon[i].y);
            Point test;
            scanf("%d",&m);
            while(m--)
            {
                scanf("%lf %lf",&test.x,&test.y);
                if(InPolygon(test)) printf("Yes\n");
                else printf("No\n");
            }
        }
        return 0;
    }
    
    

    改进弧长法

    时间复杂度:O(n) 适用范围:任意多边形
    该算法感觉是转角法的另一种优化,也解决了传统转角法的精度问题,也要求多边形点给出的顺序。

    算法思想:
    以被测点O为坐标原点,将平面划分为4个象限,对每个多边形顶点P[i],计算其所在的象限,然后顺序访问多边形的各个顶点P[i],分析P[i]和P[i+1],有下列三种情况:

    1. P[i+1]在P[i]的下一象限。此时弧长和加π/2π/2;(代码中+1)
    2. P[i+1]在P[i]的上一象限。此时弧长和减π/2π/2;(代码中-1)
    3. P[i+1]在Pi的相对象限。利用叉积res=OP[i]xOP[i+1]计算OP[i]与OP[i+1]的关系。
      若f=0,OP[i]与OP[i+1]共线,点在多边形边上;若f<0,OP[i]在OP[i+1]逆时针方向,弧长和减ππ(代码中-2);若f>0,OP[i]在OP[i+1]顺时针方向,弧长和加ππ(代码中+2)。

    有点累,逆时针的代码自己画图推一推

    //判断点P在多边形内-改进弧长法(多边形点顺时针给出)
    //用这个还不如用上一个转角法
    bool InPolygon(Point P)
    {
        int q1,q2,ans=0;
        Point P1,P2;
        Vector V1,V2;
        for(int i=1,j=n;i<=n;j=i++)
        {
            P1 = polygon[j];
            P2 = polygon[i];
            V1 = P1-P;
            V2 = P2-P;
            if(OnSegment(P1,P2,P)) return true;
            q1 = V1.x>0 ? (V1.y>0 ? 0:3) : (V1.y>0 ? 1:2);
            q2 = V2.x>0 ? (V2.y>0 ? 0:3) : (V2.y>0 ? 1:2);
            int g = (q2-q1+4)%4;
            if(g==1) ans--; //在上一象限
            if(g==3) ans++; //在下一象限
            if(g==2) dcmp(V1^V2)>0 ? (ans-=2) : (ans+=2); //在相对象限
        }
        return ans!=0;
    }
    

    hdu1756-改进弧长法代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const double eps = 1e-6;
    const double PI = acos(-1);
    
    int n,m;
    
    struct Point{
        double x,y;
        Point(double x=0,double y=0):x(x),y(y){}
    
        //向量+
        Point operator +(const Point &b)const
        {
            return Point(x+b.x,y+b.y);
        }
        //向量-
        Point operator -(const Point &b)const
        {
            return Point(x-b.x,y-b.y);
        }
        //点积
        double operator *(const Point &b)const
        {
            return x*b.x + y*b.y;
        }
        //叉积
        //P^Q>0,P在Q的顺时针方向;<0,P在Q的逆时针方向;=0,P,Q共线,可能同向或反向
        double operator ^(const Point &b)const
        {
            return x*b.y - b.x*y;
        }
    }polygon[105];
    typedef Point Vector;
    
    //三态函数,判断两个double在eps精度下的大小关系
    int dcmp(double x)
    {
        if(fabs(x)<eps) return 0;
        else
            return x<0?-1:1;
    }
    
    //判断点Q是否在P1和P2的线段上
    bool OnSegment(Point P1,Point P2,Point Q)
    {
        return dcmp((P1-Q)^(P2-Q))==0&&dcmp((P1-Q)*(P2-Q))<=0;
    }
    
    //判断点P在多边形内-改进弧长法
    bool InPolygon(Point P)
    {
        int q1,q2,ans=0;
        Point P1,P2;
        Vector V1,V2;
        for(int i=1,j=n;i<=n;j=i++)
        {
            P1 = polygon[j];
            P2 = polygon[i];
            V1 = P1-P;
            V2 = P2-P;
            if(OnSegment(P1,P2,P)) return true;
            q1 = V1.x>0 ? (V1.y>0 ? 0:3) : (V1.y>0 ? 1:2);
            q2 = V2.x>0 ? (V2.y>0 ? 0:3) : (V2.y>0 ? 1:2);
            int g = (q2-q1+4)%4;
            if(g==1) ans--; //在上一象限
            if(g==3) ans++; //在下一象限
            if(g==2) dcmp(V1^V2)>0 ? (ans-=2) : (ans+=2);
        }
        return ans!=0;
    }
    
    int main()
    {
        while(~scanf("%d",&n))
        {
            for(int i=1;i<=n;i++) scanf("%lf %lf",&polygon[i].x,&polygon[i].y);
            Point test;
            scanf("%d",&m);
            while(m--)
            {
                scanf("%lf %lf",&test.x,&test.y);
                if(InPolygon(test)) printf("Yes\n");
                else printf("No\n");
            }
        }
        return 0;
    }
    
    

    版权声明:本文为CSDN博主「WilliamSun0122」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/WilliamSun0122/article/details/77994526

  • 相关阅读:
    仿12306客户端
    object-c开发中混合使用或不使用ARC
    Objective-c 的 @property 详解
    iPhone的Push(推送通知)功能原理浅析
    Objective-C内存管理教程和原理剖析3
    IDEA 创建JAVA Maven Web 工程
    Linux CenOS 7 安装Redis
    Linux CenOS 7 安装Tomcat
    Linux CentOS 7 安装wordpress
    Linux CenOS 7 安装JDK
  • 原文地址:https://www.cnblogs.com/cuianbing/p/16244419.html
Copyright © 2020-2023  润新知