c# - 点在边平行于轴的多边形内

标签 c# java c++ python algorithm

我在 interviewstreet 上解决了 Polygon problem 问题,但似乎太慢了。问题的最佳解决方案是什么?

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 <= j < Mi,x[i][(j+1)%Mi] == x[i][j] 或 y[i][(j+1)%Mi] == y[ i][j] 但不是两者。 也保证多边形不自相交。

输出:

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

示例输入#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

enter image description here

示例输入#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

enter image description here

约束:

1 <= N <= 20,000 1 <= Q <= 20,000 4 <= 米 <= 20 每个坐标的值最多为 200,000

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

编辑:这是我的代码,但它是 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;
    }
}

最佳答案

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

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

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

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

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

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

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

关于c# - 点在边平行于轴的多边形内,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8783690/

相关文章:

java - 无法连接到 ssl 服务器收到致命警报 : certificate_unknown and ReadDataRecord(SSLSocketImpl

c++ - 如何将 CFG 转换为 C++ 代码

c# - 查找角色 Controller 下方的对象

c# - 取消任务并等待它完成

c# - c#中的参数无效异常

java - 如何向应用程序添加 onTouchEvent?

c# - 一对多返回空数组

java - 使用Java的JNI编写和调用Swift代码

c++ - 计算具有相同设置位数的下一个更高的数字?

c++ - float 的小数部分最多有多少位 10 位数字