点在边平行于轴的多边形内

Posted

技术标签:

【中文标题】点在边平行于轴的多边形内【英文标题】:point inside a polygon with edges parallel to the axes 【发布时间】:2012-01-09 03:43:11 【问题描述】:

我已经在 interviewstreet 解决了多边形问题,但它似乎太慢了。解决问题的最佳方法是什么?

X-Y 平面上有 N 个点,坐标为整数坐标 (xi, yi)。给定一组多边形,其所有边都平行于轴(换句话说,多边形的所有角度都是 90 度角,所有线都在基本方向上。没有对角线)。对于每个多边形,您的程序应该找到位于其中的点数(位于多边形边界上的点也被认为在多边形内)。

输入:

第一行两个整数 N 和 Q。下一行包含 N 个空格分隔的整数坐标 (xi,yi)。 Q 查询如下。每个查询由第一行中的单个整数 Mi 组成,然后是 Mi 空间分隔的整数坐标 (x[i][j],y[i][j]),以顺时针顺序指定查询多边形的边界。

多边形是垂直线段和水平线段的交替序列。 多边形有 Mi 边,其中 (x[i][j],y[i][j]) 连接到 (x[i][(j+1)%Mi], y[i][(j+1 )%Mi]。 对于每个 0

输出:

对于每个查询,在单独的一行中输出查询多边形内的点数。

示例输入 #1:

16 2
0 0
0 1
0 2
0 3
1 0
1 1
1 2
1 3
2 0
2 1
2 2
2 3
3 0
3 1
3 2
3 3
8
0 0
0 1
1 1
1 2
0 2
0 3
3 3
3 0
4
0 0
0 1
1 1
1 0

示例输出 #1:

16
4

示例输入 #2:

6 1
1 1
3 3
3 5
5 2
6 3
7 4
10
1 3
1 6
4 6
4 3
6 3
6 1
4 1
4 2
3 2
3 3

示例输出 #2:

4

约束:

1

我对上述语言或伪代码的解决方案感兴趣。

编辑:这是我的代码,但它是 O(n^2)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Polygon

// avoding System.Drawing dependency
public struct Point

    public int X  get; private set; 
    public int Y  get; private set; 

    public Point(int x, int y)
        : this()
    
        X = x;
        Y = y;
    

    public override int GetHashCode()
    
        return X ^ Y;
    

    public override bool Equals(Object obj)
    
        return obj is Point && this == (Point)obj;
    

    public static bool operator ==(Point a, Point b)
    
        return a.X == b.X && a.Y == b.Y;
    

    public static bool operator !=(Point a, Point b)
    
        return !(a == b);
    


public class Solution

    static void Main(string[] args)
    
        BasicTestCase();
        CustomTestCase();
        // to read from STDIN
        //string firstParamsLine = Console.ReadLine();
        //var separator = new char[]  ' ' ;
        //var firstParams = firstParamsLine.Split(separator);
        //int N = int.Parse(firstParams[0]);
        //int Q = int.Parse(firstParams[1]);

        //List<Point> points = new List<Point>(N);

        //for (int i = 0; i < N; i++)
        //
        //    var coordinates = Console.ReadLine().Split(separator);
        //    points.Add(new Point(int.Parse(coordinates[0]), int.Parse(coordinates[1])));
        //

        //var polygons = new List<List<Point>>(Q); // to reduce realocation
        //for (int i = 0; i < Q; i++)
        //

        //    var firstQ = Console.ReadLine().Split(separator);
        //    int coordinatesLength = int.Parse(firstQ[0]);
        //    var polygon = new List<Point>(coordinatesLength);
        //    for (int j = 0; j < coordinatesLength; j++)
        //    
        //        var coordinates = Console.ReadLine().Split(separator);
        //        polygon.Add(new Point(int.Parse(coordinates[0]), int.Parse(coordinates[1])));
        //    
        //    polygons.Add(polygon);
        //

        //foreach (var polygon in polygons)
        //
        //    Console.WriteLine(CountPointsInPolygon(points, polygon));
        //


    

    private static void BasicTestCase()
    
        List<Point> points = new List<Point>() new Point(0, 0),
                                                new Point(0, 1),
                                                new Point(0, 2),
                                                new Point(0, 3),
                                                new Point(1, 0),
                                                new Point(1, 1),
                                                new Point(1, 2),
                                                new Point(1, 3),
                                                new Point(2, 0),
                                                new Point(2, 1),
                                                new Point(2, 2),
                                                new Point(2, 3),
                                                new Point(3, 0),
                                                new Point(3, 1),
                                                new Point(3, 2),
                                                new Point(3, 3) ;

        List<Point> polygon1 = new List<Point>()   new Point(0, 0),
                                                    new Point(0, 1),
                                                    new Point(2, 1),
                                                    new Point(2, 2),
                                                    new Point(0, 2),
                                                    new Point(0, 3),
                                                    new Point(3, 3),
                                                    new Point(3, 0);

        List<Point> polygon2 = new List<Point>()   new Point(0, 0),
                                                    new Point(0, 1),
                                                    new Point(1, 1),
                                                    new Point(1, 0),;

        Console.WriteLine(CountPointsInPolygon(points, polygon1));
        Console.WriteLine(CountPointsInPolygon(points, polygon2));

        List<Point> points2 = new List<Point>()new Point(1, 1),
                                                new Point(3, 3),
                                                new Point(3, 5),
                                                new Point(5, 2),
                                                new Point(6, 3),
                                                new Point(7, 4),;

        List<Point> polygon3 = new List<Point>()   new Point(1, 3),
                                                    new Point(1, 6),
                                                    new Point(4, 6),
                                                    new Point(4, 3),
                                                    new Point(6, 3),

                                                    new Point(6, 1),
                                                    new Point(4, 1),
                                                    new Point(4, 2),
                                                    new Point(3, 2),
                                                    new Point(3, 3),;

        Console.WriteLine(CountPointsInPolygon(points2, polygon3));
    

    private static void CustomTestCase()
    
        // generated 20 000 points and polygons
        using (StreamReader file = new StreamReader(@"in3.txt"))
        
            string firstParamsLine = file.ReadLine();
            var separator = new char[]  ' ' ;
            var firstParams = firstParamsLine.Split(separator);
            int N = int.Parse(firstParams[0]);
            int Q = int.Parse(firstParams[1]);

            List<Point> pointsFromFile = new List<Point>(N);

            for (int i = 0; i < N; i++)
            
                var coordinates = file.ReadLine().Split(separator);
                pointsFromFile.Add(new Point(int.Parse(coordinates[0]), int.Parse(coordinates[1])));
            

            var polygons = new List<List<Point>>(Q); // to reduce realocation
            for (int i = 0; i < Q; i++)
            

                var firstQ = file.ReadLine().Split(separator);
                int coordinatesLength = int.Parse(firstQ[0]);
                var polygon = new List<Point>(coordinatesLength);
                for (int j = 0; j < coordinatesLength; j++)
                
                    var coordinates = file.ReadLine().Split(separator);
                    polygon.Add(new Point(int.Parse(coordinates[0]), int.Parse(coordinates[1])));
                
                polygons.Add(polygon);
            

            foreach (var polygon in polygons)
            
                Console.WriteLine(CountPointsInPolygon(pointsFromFile, polygon));
            

        
    

    public static int CountPointsInPolygon(List<Point> points, List<Point> polygon)
    
        // TODO input check
        polygon.Add(polygon[0]); // for simlicity
        // check if any point is outside of the bounding box of the polygon
        var minXpolygon = polygon.Min(p => p.X);
        var maxXpolygon = polygon.Max(p => p.X);
        var minYpolygon = polygon.Min(p => p.Y);
        var maxYpolygon = polygon.Max(p => p.Y);
        // ray casting algorithm (form max X moving to point)
        int insidePolygon = 0;
        foreach (var point in points)
        
            if (point.X >= minXpolygon && point.X <= maxXpolygon && point.Y >= minYpolygon && point.Y <= maxYpolygon)
                   // now points are inside the bounding box 
                    isPointsInside(polygon, point, ref insidePolygon);
             // else outside
        
        return insidePolygon;

    
    private static void isPointsInside(List<Point> polygon, Point point, ref int insidePolygon)
    
        int intersections = 0;    

        for (int i = 0; i < polygon.Count - 1; i++)
        
            if (polygon[i] == point)
            
                insidePolygon++;
                return;
            
            if (point.isOnEdge(polygon[i], polygon[i + 1]))
            
                insidePolygon++;
                return;
            
            if (Helper.areIntersecting(polygon[i], polygon[i + 1], point))
            
                intersections++;
            
        

        if (intersections % 2 != 0)
        
            insidePolygon++;
           
    


static class Helper

    public static bool isOnEdge(this Point point, Point first, Point next)
    
            // onVertical 
            if (point.X == first.X && point.X == next.X && point.Y.InRange(first.Y, next.Y))
            
                return true;
            
            //onHorizontal 
            if (point.Y == first.Y && point.Y == next.Y && point.X.InRange(first.X, next.X))
            
                return true;
            
            return false;
    

    public static bool InRange(this int value, int first, int second)
    
        if (first <= second)
        
            return value >= first && value <= second;
        
        else
        
            return value >= second && value <= first;
        
    

    public static bool areIntersecting(Point polygonPoint1, Point polygonPoint2, Point vector2End)
    
        // "move" ray up for 0.5 to avoid problem with parallel edges
        if (vector2End.X < polygonPoint1.X )
        
            var y = (vector2End.Y + 0.5);
            var first = polygonPoint1.Y;
            var second = polygonPoint2.Y;
            if (first <= second)
            
                return y >= first && y <= second;
            
            else
            
                return y >= second && y <= first;
            
        
        return false;
    

【问题讨论】:

也许你可以在 makemycode.stackexchange.com 中找到答案(开玩笑) 您希望用哪种语言回答? 任何?甚至是文字描述和比光线投射更好的东西:) 如果您发布了一些示例代码,那么人们可能会提出改进建议 【参考方案1】:

更快的解决方案是将点放入quadtree。

区域四叉树可能更容易编码,但点四叉树可能更快。如果使用区域四叉树,那么当四边形中的点数低于阈值(例如 16 个点)时,它可以帮助停止细分四叉树

每个四边形存储它包含的点数,加上坐标列表(用于叶节点)或指向较小四边形的指针。 (当四边形的大小达到 1 时,您可以省略坐标列表,因为它们必须全部重合)

要计算多边形内的点,请查看代表四叉树根的最大四边形。

    将多边形剪辑到四边形的边缘 如果多边形不与四边形重叠,则返回 0 如果这是大小为 1x1 的四边形,则返回四边形中的点数 如果多边形完全包围四边形,则返回四边形中的点数。 如果这是叶节点,则使用铅垂线算法测试每个点 否则递归计算每个子四边形中的点数

(如果一个四边形只有一个非空子元素,那么您可以跳过步骤 1、2、3、4、5 以加快速度)

(2和4中的测试不必完全准确)

【讨论】:

【参考方案2】:

梯形分解对你有用吗?

【讨论】:

【参考方案3】:

我将您推荐给Jordan Curve Theorem 和Plumb Line Algorithm。

相关伪代码为

int crossings = 0
for (each line segment of the polygon)
    if (ray down from (x,y) crosses segment)
        crossings++;
if (crossings is odd)
    return (inside);
else return (outside);

【讨论】:

我猜你的第二个链接坏了,算法基本上是我正在做的,但我“光线”向右 看起来不错。无论您是向下还是向右照射都无关紧要。【参考方案4】:

我会尝试从底部向上投射一条射线到每个点,跟踪它进入多边形的位置(通过从右到左的线段)或从多边形返回(通过从左到右的线段) .像这样的:

count := 0
For each point (px, py):
   inside := false
   For each query line (x0, y0) -> (x1, y1) where y0 = y1
      if inside
         if x0 <= px < x1 and py > y0
            inside = false
      else 
         if x1 <= px <= x0 and py >= y0
            inside = true
   if inside
      count++

在这两种情况下 > 与 >= 是为了将上边缘上的点视为内部。我实际上并没有对此进行编码以查看它是否有效,但我认为这种方法是合理的。

【讨论】:

这条线可能会多次穿过多边形,但是您可以计算交叉点的数量(奇数或偶数)。这决定了该点在多边形内部还是外部的天气。现在只需要一种有效的方法来测试交叉点:)【参考方案5】:

这是作者的解决方案 - 有点混淆,不是吗?

#include <iostream>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <ctime>
using namespace std;

typedef long long int64;
const int N = 100000, X = 2000000001;
const int Q = 100000, PQ = 20;
struct Point 
    int x, y, idx;
    Point(int _x = 0, int _y = 0, int _idx = 0) 
        x = _x;
        y = _y;
        idx = _idx;
    
 arr_x[N], arr_y[N];
struct VLineSegment 
    int x, y1, y2, idx, sign;
    VLineSegment(int _x = 0, int _y1 = 0, int _y2 = 0, int _sign = 1, int _idx = 0) 
        x = _x;
        y1 = _y1;
        y2 = _y2;
        sign = _sign;
        idx = _idx;
    
    bool operator<(const VLineSegment& v) const 
        return x < v.x;
    
 segs[Q * PQ];

struct TreeNode 
    int idx1, idx2, cnt;
    TreeNode *left, *right;
    TreeNode()  left = right = 0; cnt = 0; 
    ~TreeNode()  if(left) delete left; if(right) delete right; 

    void update_stat() 
        cnt = left->cnt + right->cnt;
    

    void build(Point* arr, int from, int to, bool empty) 
        idx1 = from;
        idx2 = to;
        if(from == to) 
            if(!empty) 
                cnt = 1;
             else  
                cnt = 0;
            
         else 
            left = new TreeNode();
            right = new TreeNode();
            int mid = (from + to) / 2;
            left->build(arr, from, mid, empty);
            right->build(arr, mid + 1, to, empty);
            update_stat();
        
    
    void update(Point& p, bool add) 
        if(p.idx >= idx1 && p.idx <= idx2) 
            if(idx1 != idx2) 
                left->update(p, add);
                right->update(p, add);
                update_stat();
             else 
                if(add) 
                    cnt = 1;
                 else 
                    cnt = 0;
                
            
        
    
    int query(int ya, int yb) 
        int y1 = arr_y[idx1].y, y2 = arr_y[idx2].y;
        if(ya <= y1 && y2 <= yb) 
            return cnt;
         else if(max(ya, y1) <= min(yb, y2)) 
            return left->query(ya, yb) + right->query(ya, yb);
        
        return 0;
    
;

bool cmp_x(const Point& a, const Point& b) 
    return a.x < b.x;

bool cmp_y(const Point& a, const Point& b) 
    return a.y < b.y;


void calc_ys(int x1, int y1, int x2, int y2, int x3, int sign, int& ya, int& yb) 
    if(x2 < x3) 
        yb = 2 * y2 - sign;
     else 
        yb = 2 * y2 + sign;
    
    if(x2 < x1) 
        ya = 2 * y1 + sign;
     else 
        ya = 2 * y1 - sign;
    


bool process_polygon(int* x, int* y, int cnt, int &idx, int i) 
    for(int j = 0; j < cnt; j ++) 
        //cerr << x[(j + 1) % cnt] - x[j] << "," << y[(j + 1) % cnt] - y[j] << endl;
        if(x[j] == x[(j + 1) % cnt]) 
            int _x, y1, y2, sign;
            if(y[j] < y[(j + 1) % cnt]) 
                _x = x[j] * 2 - 1;
                sign = -1;
                calc_ys(x[(j + cnt - 1) % cnt], y[j], x[j], y[(j + 1) % cnt], x[(j + 2) % cnt], sign, y1, y2);
             else 
                _x = x[j] * 2 + 1;
                sign = 1;
                calc_ys(x[(j + 2) % cnt], y[(j + 2) % cnt], x[j], y[j], x[(j + cnt - 1) % cnt], sign, y1, y2);
            
            segs[idx++] = VLineSegment(_x, y1, y2, sign, i);
        
    

int results[Q];

int n, q, c;

int main() 
    int cl = clock();
    cin >> n >> q;
    for(int i = 0; i < n; i ++) 
        cin >> arr_y[i].x >> arr_y[i].y;
        arr_y[i].x *= 2;
        arr_y[i].y *= 2;
    
    int idx = 0, cnt, x[PQ], y[PQ];
    for(int i = 0; i < q; i ++) 
        cin >> cnt;
        for(int j = 0; j < cnt; j ++) cin >> x[j] >> y[j];
        process_polygon(x, y, cnt, idx, i);
    
    sort(segs, segs + idx);
    memset(results, 0, sizeof results);
    sort(arr_y, arr_y + n, cmp_y);
    for(int i = 0; i < n; i ++) 
        arr_y[i].idx = i;
        arr_x[i] = arr_y[i];
    
    sort(arr_x, arr_x + n, cmp_x);
    TreeNode tleft;
    tleft.build(arr_y, 0, n - 1, true);
    for(int i = 0, j = 0; i < idx; i ++) 
        for(; j < n && arr_x[j].x <= segs[i].x; j ++) 
            tleft.update(arr_x[j], true);
        
        int qcnt = tleft.query(segs[i].y1, segs[i].y2);
        //cerr << segs[i].x * 0.5 << ", " << segs[i].y1 * 0.5 << ", " << segs[i].y2 * 0.5 << " = " << qcnt << " * " << segs[i].sign << endl;
        results[segs[i].idx] += qcnt * segs[i].sign;
    
    for(int i = 0; i < q; i ++) 
        cout << results[i] << endl;
    
    cerr << (clock() - cl) * 0.001 << endl;
    return 0;

【讨论】:

以上是关于点在边平行于轴的多边形内的主要内容,如果未能解决你的问题,请参考以下文章

ZOJ 1081 Points Within | 判断点在多边形内

从抛物线的焦点发出的光线经反射后平行于轴的证明

点在多边形内判断

如何判断一个点在多边形内

如何使用谷歌地图检测一个点在多边形内?

如何判断点在一个区域内?用户绘制区域(射线法)判断点在多边形区域,报警区域