【问题标题】:Detect square in a List of Points在点列表中检测正方形
【发布时间】:2016-12-17 14:57:55
【问题描述】:

我想测试 Point 对象列表中是否有正方形。

这是我的 Point 类:

class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int distanceSquare(Point q) {
        return (x - q.getX()) * (x - q.getX()) +
                (y - q.getY()) * (y - q.getY());
    }
}

我可以测试四个点是否是正方形:

static boolean isSquare(Point p1, Point p2, Point p3, Point p4) {
        int d2 = p1.distanceSquare(p2);  // from p1 to p2
        int d3 = p1.distanceSquare(p3);  // from p1 to p3
        int d4 = p1.distanceSquare(p4);  // from p1 to p4

        if (d2 == d3 && 2 * d2 == d4) {
            int d = p2.distanceSquare(p4);
            return (d == p3.distanceSquare(p4) && d == d2);
        }

        if (d3 == d4 && 2 * d3 == d2) {
            int d = p2.distanceSquare(p3);
            return (d == p2.distanceSquare(p4) && d == d3);
        }
        if (d2 == d4 && 2 * d2 == d3) {
            int d = p2.distanceSquare(p3);
            return (d == p3.distanceSquare(p4) && d == d2);
        }

        return false;
    }

但我没有找到在 Point 列表中搜索正方形的最佳方法。

你能帮帮我吗!!!

【问题讨论】:

  • 构成正方形的点在列表中是否连续?

标签: java algorithm point


【解决方案1】:

取所有点对;为每一对计算它的角度、长度和中点。这将花费 O(n^2) 时间。
对于具有相同中点和相同长度的每组对按角度对这些对进行排序,这将花费总 O(n^2*log(n)) 时间。这将帮助您找到具有相同中点(即正方形!)的两条相同长度的正交对角线。
总算法时间复杂度为 O(n^2*log(n))。

【讨论】:

  • 这将帮助您找到两个具有相同中点的相同长度的正交对角线 - 您的意思是您可以在 lg(n) 中为每一对?你能详细说明一下吗?对我来说,它看起来像 lg(n^2)。
  • @Yola - 只需同时在此排序列表中移动两个指针即可搜索 delta = pi/2 的两个值。该操作具有线性时间复杂度
  • 啊,抱歉,我错过了关于具有相同中点和相同长度的对集合的另一部分。虽然不清楚如何获得这样的集合,但这个操作的复杂性是什么?如果你只有一个这样的集合(圆圈上的点),那么排序将花费 O(n^2*lg(n^2)),对吧?
  • @Yola - 对,但是 O(n^2*lg(n^2)) = O(n^2*lg(n))
  • @EricDuminil - (Math.atan2(y2-y1,x2-x1)+Math.PI)%Math.PI
【解决方案2】:

更新为还检测到旋转方块

我喜欢上面 Egor Skriptunoff 的回答,但让我试着给出 另一个答案。我认为复杂度只有 O(N^2)。

算法

对于任意一对点 (P0, P1)(它们有 N^2 个),找出 向量 V01 从 P0 到 P1,然后将该向量旋转 90 度(变成V12)。把它加到P1,看看能不能找到一个点 那里? (这可以通过 hashmap 查找来完成——见下文)如果是这样 那么你就有了 P2,然后继续这个过程。

将矢量再旋转 90 度(变为 V23),将其添加到 P2,看看你能不能找到一个点? (同样,通过哈希图查找)
如果是这样,那么你有 P3,并继续该过程。

将矢量再旋转 90 度(变为 V34)。添加它 到P3,看看能不能找到点那里? (同样,通过哈希图查找)。 如果是这样,还要检查这个点 P4 是否与 P0 相同。如果是这样,那么 您刚刚检测到一个正方形。

下图说明了这个想法。

数据结构

如果正方形是可旋转的,那么 Point 的 x 和 y 坐标 必须是浮点数(双精度),不能是整数。因为 大量计算会给你无理数(例如 sqrt(2))

但是,双精度表示不能精确为整数, 所以我们必须小心 将两个足够接近的双精度数视为同一个双精度数 价值。在我的代码中,我使用 epsilon 作为我们允许的容差 为“近似等价”。我选择 1E-3 作为 epsilon。

public class Point2 {
    private double x;
    private double y;
    private final double eps = 1E-3;    
    public double getEps() {
        return eps;
    }

然后在所有关于equals()hashCode() 的计算中, 确保使用 x 和 y 的“捕捉”值,而不是它们的原始值 双重表示。 (图形上你可以想象这就像你的 图形编辑器为您提供“对齐网格”功能,网格大小 是ε)。你也需要小心,在双重表示 有正0和负0,你也应该把它们当作 相同的值。

类似这样的:

public long getXsnapped() {
    return Math.round(this.getX()/this.getEps());
}   
public long getYsnapped() {
    return Math.round(this.getY()/this.getEps());
}   
@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    long temp;
    temp = Double.doubleToLongBits(eps);
    result = prime * result + (int) (temp ^ (temp >>> 32));
    if (Math.abs(this.getX())>this.getEps()) {
        // include X only if it is larger than eps
        temp = this.getXsnapped();
        result = prime * result + (int) (temp ^ (temp >>> 32));
    }
    if (Math.abs(this.getY())>this.getEps()) {
        temp = this.getYsnapped();
        result = prime * result + (int) (temp ^ (temp >>> 32));
    }
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Point2 other = (Point2) obj;
    if (Double.doubleToLongBits(eps) != Double.doubleToLongBits(other.eps))
        return false;
    boolean answer = true;
    if (Math.abs(this.getX())>this.getEps()
            || Math.abs(other.getX())>this.getEps()) {
        // compare value and sign only if X of both points are larger than eps
        if (this.getXsnapped()!= other.getXsnapped())
            answer = false;         
    }
    if (Math.abs(this.getY())>this.getEps()
            || Math.abs(other.getY())>this.getEps()) {
        // compare value and sign only if Y of both points are larger than eps
        if (this.getYsnapped()!= other.getYsnapped())
            answer &= false;
    }
    boolean isDebug = false;
    Util.debugPrint(isDebug, "p1 %s; p2 %s: %b (eps is %.9f)%n"
        , this, other, answer, this.getEps());
    return answer;
}

此外,每个方格有四个点。您可以在其中添加规则 您的程序说这四个点必须按照中使用的顺序排列 具有设定角度关系的算法(P0->P1->P2->P3) (见上面的算法)。但是,您还需要注意 给定相同的四个点有四个选项可供选择 起点。所以你的 Square 目标代码必须处理 以下由这四个点指定的正方形等效:

P0->P1->P2->P3
P1->P2->P3->P0 
P2->P3->P0->P1
P3->P0->P1->P2

这可以在 Square 对象的构造函数中完成,并且总是 通过“规范化”输入 选择具有一定航海特征的点 作为起点(例如,选择具有最低 x 的点,并且 如果它的 x 值与另一个点相关,则选择最低的点 x 和较小的 y 对中)

测试输入 1

-1.4142,    9.8995
-5.6569,   15.5563
1.4142,    9.8995
-1.4142,   14.1421
-2.1213,   14.8492
1.4142,   14.1421
0.0000,   15.5563
-2.1213,   17.6777
7.0711,   11.3137
5.6569,   12.7279
4.2426,   14.1421
6.3640,   10.6066
7.0711,   14.1421
5.6569,   15.5563
1.4142,   19.7990
7.7782,   14.8492

测试结果 1

===== Given a set of following points from file src\detectSquare\inputSet1_45_1_1_0_0.txt =====
1: Point2 [x=-1.4142, y=9.8995]
2: Point2 [x=-5.6569, y=15.5563]
3: Point2 [x=1.4142, y=9.8995]
4: Point2 [x=-1.4142, y=14.1421]
5: Point2 [x=-2.1213, y=14.8492]
6: Point2 [x=1.4142, y=14.1421]
7: Point2 [x=0.0000, y=15.5563]
8: Point2 [x=-2.1213, y=17.6777]
9: Point2 [x=7.0711, y=11.3137]
10: Point2 [x=5.6569, y=12.7279]
11: Point2 [x=4.2426, y=14.1421]
12: Point2 [x=6.3640, y=10.6066]
13: Point2 [x=7.0711, y=14.1421]
14: Point2 [x=5.6569, y=15.5563]
15: Point2 [x=1.4142, y=19.7990]
16: Point2 [x=7.7782, y=14.8492]
===== The following squares have been found =====
1: SquareRotatable [points=[Point2 [x=4.2427, y=14.1421], Point2 [x=5.6569, y=12.7279], Point2 [x=7.0711, y=14.1421], Point2 [x=5.6569, y=15.5563]]]

测试输入 2

0.0000,    0.0000
-0.7071,    0.7071
-1.4142,    1.4142
0.7071,    0.7071
0.0000,    1.4142
-0.7071,    2.1213
1.4142,    1.4142
0.7071,    2.1213
0.0000,    2.8284

测试结果2

===== Given a set of following points from file src\detectSquare\inputSet2_45_0_0_0_0.txt =====
1: Point2 [x=0.0000, y=0.0000]
2: Point2 [x=-0.7071, y=0.7071]
3: Point2 [x=-1.4142, y=1.4142]
4: Point2 [x=0.7071, y=0.7071]
5: Point2 [x=0.0000, y=1.4142]
6: Point2 [x=-0.7071, y=2.1213]
7: Point2 [x=1.4142, y=1.4142]
8: Point2 [x=0.7071, y=2.1213]
9: Point2 [x=0.0000, y=2.8284]
===== The following squares have been found =====
1: SquareRotatable [points=[Point2 [x=-1.4142, y=1.4142], Point2 [x=0.0000, y=0.0000], Point2 [x=1.4142, y=1.4142], Point2 [x=0.0000, y=2.8284]]]
2: SquareRotatable [points=[Point2 [x=-0.7071, y=0.7071], Point2 [x=0.7071, y=0.7071], Point2 [x=0.7071, y=2.1213], Point2 [x=-0.7071, y=2.1213]]]
3: SquareRotatable [points=[Point2 [x=-1.4142, y=1.4142], Point2 [x=-0.7071, y=0.7071], Point2 [x=0.0000, y=1.4142], Point2 [x=-0.7071, y=2.1213]]]
4: SquareRotatable [points=[Point2 [x=-0.7071, y=2.1213], Point2 [x=0.0000, y=1.4142], Point2 [x=0.7071, y=2.1213], Point2 [x=-0.0000, y=2.8284]]]
5: SquareRotatable [points=[Point2 [x=-0.7071, y=0.7071], Point2 [x=0.0000, y=0.0000], Point2 [x=0.7071, y=0.7071], Point2 [x=0.0000, y=1.4142]]]
6: SquareRotatable [points=[Point2 [x=0.0000, y=1.4142], Point2 [x=0.7071, y=0.7071], Point2 [x=1.4142, y=1.4142], Point2 [x=0.7071, y=2.1213]]]

JUnit 测试程序测试Point2#getXsnapped()(仅限片段)

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

public class Point2Test {
    private List<Point2> points = new ArrayList<>();

    @Before
    public void setUp() throws Exception {
        points.add(new Point2(0.49999999f, 0));
        points.add(new Point2(0.50000001f, 0));
    }
    ...

    @Test
    public void testGetXsnapped() {
        System.out.format("testing xSnapped of two points: %s and %s%n"
                , points.get(0), points.get(1));
        System.out.format("and they are %d and %d respectively%n"
                , points.get(0).getXsnapped()
                , points.get(1).getXsnapped());
        System.out.format("(Note: epsilon is %f)%n"
                , points.get(0).getEps());

        assertEquals(points.get(0).getXsnapped()
                    , points.get(1).getXsnapped());
    }
}

JUnit 测试的输出

testing xSnapped of two points: Point2 [x=0.5000, y=0.0000] and Point2 [x=0.5000, y=0.0000]
and they are 500 and 500 respectively
(Note: epsilon is 0.001000)

警告

Eric Duminil 正确地指出可能存在 2个点任意靠近,仍然被捕捉到网格上的不同点。

在我的脑海中,我不知道如何解决这个问题。建议 欢迎光临!

例如

@Before
public void setUp() throws Exception {
    Point2 dummy = new Point2(0, 0);    // just to get epsilon
    points.add(new Point2(dummy.getEps()*0.5, 0));
    points.add(new Point2(dummy.getEps()*0.49999999999999, 0));
}

使用这些添加的调试代码:

public long getXsnapped() {
    boolean isDebug = true;
    String _ = "    "; // indent
    double X = this.getX();
    Util.debugPrint(isDebug, _ + "X is %E (long bits is 0x%x)%n"
                                , X, Double.doubleToLongBits(X));
    double eps = this.getEps();
    Util.debugPrint(isDebug, _ + "eps is %E%n", eps);
    double fraction = X/eps;
    Util.debugPrint(isDebug, _ + "fraction is %E (long bits is 0x%x)%n"
                                , fraction, Double.doubleToLongBits(fraction));
    long answer = Math.round(fraction); 
    Util.debugPrint(isDebug, _ + "xSnapped is %d%n", answer);
    return answer;
}   

Util.debugPrint():

public static void debugPrint(boolean isDebug, String format, Object... args) {
    if (!isDebug) {
        return; // don't print
    }
    System.out.format(format, args);
}

我会得到以下输出——这两点被认为是不同的!

testing xSnapped of two points: Point2 [x=0.0005, y=0.0000] and Point2 [x=0.0005, y=0.0000]
    X is 5.000000E-04 (long bits is 0x3f40624dd2f1a9fc)
    eps is 1.000000E-03
    fraction is 5.000000E-01 (long bits is 0x3fe0000000000000)
    xSnapped is 1
    X is 5.000000E-04 (long bits is 0x3f40624dd2f1a9a0)
    eps is 1.000000E-03
    fraction is 5.000000E-01 (long bits is 0x3fdfffffffffff4c)
    xSnapped is 0
and they are 1 and 0 respectively
(Note: epsilon is 0.001000)
delta between the two x (as double) is 9.974660E-18
    X is 5.000000E-04 (long bits is 0x3f40624dd2f1a9fc)
    eps is 1.000000E-03
    fraction is 5.000000E-01 (long bits is 0x3fe0000000000000)
    xSnapped is 1
    X is 5.000000E-04 (long bits is 0x3f40624dd2f1a9a0)
    eps is 1.000000E-03
    fraction is 5.000000E-01 (long bits is 0x3fdfffffffffff4c)
    xSnapped is 0

【讨论】:

  • 对不起,没有。那是个很好的观点。我会看看我是否可以稍后提供更新的答案。
  • 感谢您指出正方形可能必须是可旋转的要求。我已经更新了上面的答案。
  • 对不起,你看过我的回答了吗?你的提议几乎是一样的。
  • @leeyuiwah :对不起,我选错了例子。 a=eps*0.5;b=eps*0.49999999999999;ab1E-17 不同,但不会捕捉到同一点。我认为这就是您的O(n**2) 的来源。你的算法会输出假阴性。
【解决方案3】:

这里是O(n^2 lg(n))-时间和O(n)-空间算法。如果您将按角度对顶点进行预排序,那么对于每一对 {A,B},您都可以找到角度 OC 和 OD。

compute angle for every point -- O(n)
sort point by angles -- O(n*lg(n))
for each pair -- O(n^2*lg(n))
    if (B.x > A.x) && (B.y > A.y)
        compute positions of C and D
        compute angles of C and D
        for C and D
            if there is a pair of points with same angles and same positions
                return true
return false

如何求C的坐标:

Xc = Xb - (Yb - Ya)
Yc = Yb + (Xb - Xa)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-05
    • 1970-01-01
    • 2021-01-26
    • 1970-01-01
    • 2013-11-12
    • 2011-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多