给定一组有序的2D像素位置(相邻或相邻对角线),形成一条没有重复的完整路径,如何确定周长为像素组的多边形的最大线性尺寸? (其中GLD是集合中任何一对点的最大线性距离)
就我的目的而言,明显的O(n ^ 2)解决方案对于数千个点的数字可能不够快。是否有良好的启发式或查找方法使时间复杂度更接近O(n)或O(log(n))?
答案 0 :(得分:18)
一种简单的方法是首先找到点的凸包,这可以在很多方面在O(n log n)时间内完成。 [我喜欢Graham scan(请参阅animation),但incremental算法也很受欢迎,others也是如此,尽管有些人采用more time。
然后你可以从凸包上的任意两个点(比如x和y)开始找到最远的一对(直径),顺时针移动y直到距离x最远,然后移动x,再次移动y等等你可以证明这整件事只需要O(n)时间(摊销)。所以它总共是O(n log n)+ O(n)= O(n log n),如果你使用礼品包装作为你的凸包算法,它可能是O(nh)。正如您所提到的,这个想法被称为rotating calipers。
这是code by David Eppstein(计算几何研究员;另见他的Python Algorithms and Data Structures以供将来参考)。
所有这些都不是很难编码(最多应该是一百行;在上面的Python代码中小于50),但在你这样做之前 - 你应该首先考虑你是否真的需要它。如果,如你所说,你只有“数千个点”,那么平凡的O(n ^ 2)算法(比较所有对)将在不到一秒的时间内以任何合理的编程语言运行。即使有一百万点,它也不应该超过一个小时。 : - )
您应该选择有效的最简单算法。
答案 1 :(得分:2)
在此页面上:
它表明您可以在O(n)中确定凸多边形的最大直径。我只需要先将我的点集转换为凸多边形(可能使用格雷厄姆扫描)。
以下是我为计算凸包而遇到的一些C#代码:
答案 2 :(得分:2)
我将Python代码移植到C#。它似乎有效。
using System;
using System.Collections.Generic;
using System.Drawing;
// Based on code here:
// http://code.activestate.com/recipes/117225/
// Jared Updike ported it to C# 3 December 2008
public class Convexhull
{
// given a polygon formed by pts, return the subset of those points
// that form the convex hull of the polygon
// for integer Point structs, not float/PointF
public static Point[] ConvexHull(Point[] pts)
{
PointF[] mpts = FromPoints(pts);
PointF[] result = ConvexHull(mpts);
int n = result.Length;
Point[] ret = new Point[n];
for (int i = 0; i < n; i++)
ret[i] = new Point((int)result[i].X, (int)result[i].Y);
return ret;
}
// given a polygon formed by pts, return the subset of those points
// that form the convex hull of the polygon
public static PointF[] ConvexHull(PointF[] pts)
{
PointF[][] l_u = ConvexHull_LU(pts);
PointF[] lower = l_u[0];
PointF[] upper = l_u[1];
// Join the lower and upper hull
int nl = lower.Length;
int nu = upper.Length;
PointF[] result = new PointF[nl + nu];
for (int i = 0; i < nl; i++)
result[i] = lower[i];
for (int i = 0; i < nu; i++)
result[i + nl] = upper[i];
return result;
}
// returns the two points that form the diameter of the polygon formed by points pts
// takes and returns integer Point structs, not PointF
public static Point[] Diameter(Point[] pts)
{
PointF[] fpts = FromPoints(pts);
PointF[] maxPair = Diameter(fpts);
return new Point[] { new Point((int)maxPair[0].X, (int)maxPair[0].Y), new Point((int)maxPair[1].X, (int)maxPair[1].Y) };
}
// returns the two points that form the diameter of the polygon formed by points pts
public static PointF[] Diameter(PointF[] pts)
{
IEnumerable<Pair> pairs = RotatingCalipers(pts);
double max2 = Double.NegativeInfinity;
Pair maxPair = null;
foreach (Pair pair in pairs)
{
PointF p = pair.a;
PointF q = pair.b;
double dx = p.X - q.X;
double dy = p.Y - q.Y;
double dist2 = dx * dx + dy * dy;
if (dist2 > max2)
{
maxPair = pair;
max2 = dist2;
}
}
// return Math.Sqrt(max2);
return new PointF[] { maxPair.a, maxPair.b };
}
private static PointF[] FromPoints(Point[] pts)
{
int n = pts.Length;
PointF[] mpts = new PointF[n];
for (int i = 0; i < n; i++)
mpts[i] = new PointF(pts[i].X, pts[i].Y);
return mpts;
}
private static double Orientation(PointF p, PointF q, PointF r)
{
return (q.Y - p.Y) * (r.X - p.X) - (q.X - p.X) * (r.Y - p.Y);
}
private static void Pop<T>(List<T> l)
{
int n = l.Count;
l.RemoveAt(n - 1);
}
private static T At<T>(List<T> l, int index)
{
int n = l.Count;
if (index < 0)
return l[n + index];
return l[index];
}
private static PointF[][] ConvexHull_LU(PointF[] arr_pts)
{
List<PointF> u = new List<PointF>();
List<PointF> l = new List<PointF>();
List<PointF> pts = new List<PointF>(arr_pts.Length);
pts.AddRange(arr_pts);
pts.Sort(Compare);
foreach (PointF p in pts)
{
while (u.Count > 1 && Orientation(At(u, -2), At(u, -1), p) <= 0) Pop(u);
while (l.Count > 1 && Orientation(At(l, -2), At(l, -1), p) >= 0) Pop(l);
u.Add(p);
l.Add(p);
}
return new PointF[][] { l.ToArray(), u.ToArray() };
}
private class Pair
{
public PointF a, b;
public Pair(PointF a, PointF b)
{
this.a = a;
this.b = b;
}
}
private static IEnumerable<Pair> RotatingCalipers(PointF[] pts)
{
PointF[][] l_u = ConvexHull_LU(pts);
PointF[] lower = l_u[0];
PointF[] upper = l_u[1];
int i = 0;
int j = lower.Length - 1;
while (i < upper.Length - 1 || j > 0)
{
yield return new Pair(upper[i], lower[j]);
if (i == upper.Length - 1) j--;
else if (j == 0) i += 1;
else if ((upper[i + 1].Y - upper[i].Y) * (lower[j].X - lower[j - 1].X) >
(lower[j].Y - lower[j - 1].Y) * (upper[i + 1].X - upper[i].X))
i++;
else
j--;
}
}
private static int Compare(PointF a, PointF b)
{
if (a.X < b.X)
{
return -1;
}
else if (a.X == b.X)
{
if (a.Y < b.Y)
return -1;
else if (a.Y == b.Y)
return 0;
}
return 1;
}
}
答案 3 :(得分:0)
你可以绘制一个比多边形大的圆圈并慢慢收缩它,检查你是否已经相交了任何一点。然后你的直径就是你要找的数字。 不确定这是否是一个好方法,它听起来介于O(n)和O(n ^ 2)
之间答案 4 :(得分:0)
我的袖手旁观解决方案是尝试二进制分区方法,在中间绘制一条线,并检查该线中间的所有点的距离。 这将为您提供2个可能非常远的点数。然后检查这两者的距离并重复上述距离检查。重复这个过程一段时间。
我的直觉说这是一个n log n启发式,可以让你非常接近。