【问题标题】:Finding the squares in a plane given n points在给定n个点的平面中找到正方形
【发布时间】:2023-03-22 08:06:01
【问题描述】:

给定平面上的n个点,可以形成多少个正方形...??

我通过计算每 2 个点之间的距离来尝试这个,然后对它们进行排序,并在验证点和斜率后寻找具有四个或更多相等距离的点中的正方形。

但这看起来是一种非常复杂的方法。任何其他想法...??

我认为用于检查等距线段的动态编程可能有效......但无法完全正确地理解这个想法......

有更好的想法吗???

P.S:正方形可以是任何方式。它们可以重叠,有共同的一面,一个正方形内另一个......

如果可能,请提供示例代码来执行上述操作...

【问题讨论】:

  • 坐标是整数还是浮点数?
  • 正方形是由线段而不是点组成的,所以这个问题有点意思——你是什么意思?有多少个正方形在点集中有一些/所有顶点?有多少个正方形包含一组不同的点?有多少个正方形可以覆盖这些点?

标签: c algorithm


【解决方案1】:

d[i][j] = distances between points i and j。我们对函数count(i, j) 感兴趣,它尽可能快地返回我们可以使用点ij 绘制的正方形的数量。

基本上,count(i, j) 必须找到两个点 xy 使得 d[i][j] = d[x][y] 并检查这四个点是否真的定义了一个正方形。

平均而言,您可以使用hash table 解决O(n^2) 中的问题。让H[x] = list of all points (p, q) that have d[p][q] = x.

现在,对于每对点 (i, j)count(i, j) 将不得不迭代 H[ d[i][j] ] 并计算该列表中与点 ij 形成正方形的点。

这在实践中应该运行得非常快,而且我认为它不会比O(n^3) 更糟(我什至不确定它会变得那么糟糕)。

【讨论】:

  • 请注意,d[i][j] = d[x][y] and d[i][x] = d[j][y] or d[i][y] = d[j][x] 条件并不能保证它是正方形。您仍然应该检查它是否存在。
【解决方案2】:

我有一个O(N^2)时间,O(N)空间解:

假设给定的点是对象Point的数组,每个Point都有x,y。

  1. 首先遍历数组并将每个项目添加到 HashSet 中:此操作消除重复并为我们提供 O(1) 访问时间。整个过程耗时O(N)
  2. 用数学,说顶点A,B,C,D可以形成一个正方形,AC已知并且它是对角线,那么对应的B,D是唯一的。我们可以编写一个函数来计算它。这个过程是 O(1) 时间
  3. 现在让我们回到我们的话题。编写一个 for-i-loop 和一个 for-j-inner-loop。说 input[i] 和 input[j] 形成一条对角线,在集合中找到它的对角线是否存在:如果存在,counter ++;这个过程需要 O(N^2) 时间。

我的 C# 代码:

    public int SquareCount(Point[] input)
    {
        int count = 0;

        HashSet<Point> set = new HashSet<Point>();
        foreach (var point in input)
            set.Add(point);

        for (int i = 0; i < input.Length; i++)
        {
            for (int j = 0; j < input.Length; j++)
            {
                if (i == j)
                    continue;
                //For each Point i, Point j, check if b&d exist in set.
                Point[] DiagVertex = GetRestPints(input[i], input[j]);
                if (set.Contains(DiagVertex[0]) && set.Contains(DiagVertex[1]))
                {
                    count++;
                }
            }
        }
        return count;

    }

    public Point[] GetRestPints(Point a, Point c)
    {
        Point[] res = new Point[2];

        int midX = (a.x + c.y) / 2;
        int midY = (a.y + c.y) / 2;

        int Ax = a.x - midX;
        int Ay = a.y - midY;
        int bX = midX - Ay;
        int bY = midY + Ax;
        Point b = new Point(bX,bY);

        int cX =  (c.x - midX);
        int cY =  (c.y - midY);
        int dX = midX - cY;
        int dY = midY + cX;
        Point d = new Point(dX,dY);

        res[0] = b;
        res[1] = d;
        return res;
    }

【讨论】:

  • 您如何计算 B 点和 D 点?另外,midX 是(a.x+c.x)/2
  • qr.ae/pGI3tR - 猜猜这解释了GetRestPints()的上述算法。
  • 我们不会重复计算吗?
【解决方案3】:

这个问题可以在O(n^1.5)时间用O(n)空间解决。

基本思想是按 X 或 Y 坐标对点进行分组,注意避免组太大。详情见论文Finding squares and rectangles in sets of points。该论文还涵盖了许多其他情况(允许旋转正方形、允许矩形以及在更高维度上工作)。

我在下面解释了他们的二维轴对齐正方形查找算法。请注意,我将他们的树集更改为哈希集,这就是为什么我给出的时间限制不是O(n^1.5 log(n))

  1. 制作所有点的哈希集。可以用来快速检查点是否存在的东西。

  2. 按 X 坐标对点进行分组。打破任何超过sqrt(n) 点的组,并通过它们的 Y 坐标重新组合那些现在空闲的点。这保证了组最多有sqrt(n)并且保证对于每个方格都有一个组有两个方格的角点。

  3. 1234563跟踪你找到了多少。注意重复(两个相反的点是否也在一个组中?)。

为什么有效?好吧,唯一棘手的事情是重组。如果正方形的左列或右列位于不太大的组中,则在迭代该列组时会找到该正方形。否则两个它的左上角和右上角都会重新组合,放置到同一个行组中,当该行组被迭代时,将找到正方形。

【讨论】:

  • 你的第二步听起来有点混乱。我会这样说:按 X 坐标对一些点进行分组,按 Y 坐标对一些点进行分组,这样任何组都不会包含超过 sqrt(n) 个点。
  • @YechielLabunsliy 我同意当前的措辞有点尴尬,但您的措辞忽略了如何实际实现分组。它也错过了组必须满足的条件之一:对于每个正方形,必须有一个边的两个角点在同一个组中。
  • 您的编辑听起来比以前好多了。既然你的描述更好,也许更少的人会阅读原始论文
  • 1989 年 van Kreveld 和 de Berg 的技术报告(如上链接)后来由 Springer-Verlag (2005) 作为 Graphs And Computational Geometry 的一本书章节出版,第 341-355 页。标题是相同的,但一些参考文献(都是 1989 年之前的)包含在已发布的版本中(它们似乎在上面链接的 PDF 中丢失了)。请注意,作者只考虑了一个决策问题,点集中是否存在带顶点的正方形(或矩形)?
【解决方案4】:

对我来说,它看起来像 O(n^3)。一个简单的算法可能是这样的:

for each pair of points
    for each of 3 possible squares which might be formed from these two points
        test remaining points to see if they coincide with the other two vertices

【讨论】:

  • O(n) 中的测试如何?在我看来O(n^4),除非你有一个聪明的方法来阻止在O(n^2) 中进行测试。
  • IVlad:您只需要测试 n-2 个点中的每一个,看看它们是否与剩余的两个顶点中的任何一个重合。因此测试是 O(n)。
  • @Paul - 那么我猜你假设你已经预先计算了每对点之间的距离,对吗?您应该提到这一点,这对每个人来说可能都不是很明显。
  • 这里有一个优化:点可以通过基数排序在线性时间按坐标排序。然后测试变成了时间 O(log(n)) 的查找。因此,总时间为 O(n^2 * log(n))。
  • @IVlad - 如果您有 2 个点,则可以用数学方法计算其他 2 个点的可能位置。无需预先计算每对点之间的距离。
【解决方案5】:

只是一个想法:如果顶点 A 是正方形的一个角,那么在其他角处肯定有顶点 B、C、D,AB = AD 和 AC = sqrt(2)AB 并且 AC 必须平分 BD。假设每个顶点都有唯一的坐标,我认为你可以在 O(n^2) 中解决这个问题,哈希表键入(距离,角度)。

【讨论】:

    【解决方案6】:
    Runtime: O(nlog(n)^2), Space: θ(n), where n is the number of points.
    
    For each point p
    Add it to the existing arrays sorted in the x and y-axis respectively.
      For every pair of points that collide with p in the x and y-axis respectively
       If there exists another point on the opposite side of p, increment square count by one.
    

    直觉是计算一个新点创建了多少个正方形。所有正方形都是在创建第四个点时创建的。如果一个新点在相关轴上有任何碰撞点并且在另一侧存在完成该正方形的“第四”点,则它会创建一个新正方形。这会耗尽所有可能的不同方格。

    可以二进制插入数组,并且可以通过访问散列点坐标的哈希表来检查相反的点。

    此算法最适合稀疏点,因为要检查的碰撞点非常少。与最优的原因相反,密集平方点是最差的。

    可以通过跟踪轴阵列中的点是否在互补轴上发生碰撞来进一步优化该算法。

    【讨论】:

      【解决方案7】:

      这只是 Java 中的一个示例实现 - 欢迎任何 cmets。

      import java.util.Arrays;
      import java.util.NoSuchElementException;
      import java.util.Map;
      import java.util.HashMap;
      import java.util.List;
      import java.util.ArrayList;
      
      
      public class SweepingLine {
      
      
          public static void main(String[] args) {
              Point[] points = {
                  new Point(1,1),
                  new Point(1,4),
                  new Point(4,1),
                  new Point(4,4),
                  new Point(7,1),
                  new Point(7,4)
              };
              int max = Arrays.stream(points).mapToInt(p -> p.x).max().orElseThrow(NoSuchElementException::new);
              int count = countSquares(points, max);
              System.out.println(String.format("Found %d squares in %d x %d plane", count, max, max));
          }
      
          private static int countSquares(Point[] points, int max) {
              int count = 0;
              Map<Integer, List<Integer>> map = new HashMap<>();
      
              for (int x=0; x<max; x++) {
                  for (int y=0; y<max; y++) {
                      for(Point p: points) {
                          if (p.x == x && p.y == y) {
                              List<Integer> ys = map.computeIfAbsent(x, _u -> new ArrayList<Integer>());
                              ys.add(y);
                              Integer ley = null;
                              for (Integer ey: ys) {
                                  if (ley != null) {
                                      int d = ey - ley;
                                      for (Point p2: points) {
                                          if (x + d == p2.x && p2.y == ey){
                                              count++;
                                          }
                                      }
                                  }
                                  ley = ey;
                              }
                          }
                      }
                  }
              }
              return count;
          }
      
          private static class Point {
              public final int x;
              public final int y;
              public Point(int x, int y) {
                  this.x = x;
                  this.y = y;
              }
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-01-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多