【问题标题】:Find Nearest Point Pair (Divide & Conquer), Issue when have many of same x coordinates查找最近点对(分而治之),当有许多相同的 x 坐标时发出问题
【发布时间】:2022-01-25 05:30:21
【问题描述】:

我正在尝试开发分治算法来查找点数组中最近的 2 个点。

我发现当输入有几个点具有相同的 x 坐标时。然后,如果其中 2 个在 Section1 和 Section2 之间划分,我会得到不正确的结果。我在这里显示一个最小的输入只是为了显示问题。 (以及更大的输入以表明问题也存在于更大的输入中)。我担心的是当我有许多具有相同 x 坐标的点时。

我想到了一个解决方案。如果我扩展 Point2D 类并添加一个字段来存储 pointsSortedOnX 数组中的点索引,那么我可以从这个类创建 pointsSortedOnY 数组。现在,当我通过pointsSortedOnY 数组而不是检查该点是否等于或小于midX 时,我可以检查它是否小于或等于midpointsSortedOnX 索引。 我只是想知道是否有更好的方法。

输入(最小):

double[][] p = {
        {25.6, 67.9},
        {25.6, 8.65},
        {32.35, 81.26},
        {25.32, 67.15},
}

输入(显示问题也在于较大的输入):

double[][] p = {
        {25.6, 67.9},
        {25.6, 8.65},
        {32.35, 81.26},
        {25.32, 67.15},

        {25.6, 60.95},
        {25.6, 56.79},
        {25.6, 78.49},
        {25.6, 6.23},
        {25.6, 7.9},
        {25.6, 95.9},
        {25.6, 35.9},
        {25.6, 10.9},
    }

代码:

    /** Return the distance of the closest PointPair of points */
public static PointPair getClosestPair(double[][] points) {
    int size = points.length;
    pointsSortedX = new Point2D.Double[size];
    pointsSortedY = new Point2D.Double[size];
    for (int i = 0; i < size; i++) {
        pointsSortedX[i] = new Point2D.Double(points[i][0], points[i][1]);
        pointsSortedY[i] = new Point2D.Double(points[i][0], points[i][1]);
    
    }
    Arrays.sort(pointsSortedX, (e1, e2) -> 
        (e1.getX() == e2.getX() ? (e1.getY() > e2.getY() ? 1 : (e1.getY() < e2.getY() ? -1 : 0)) : (e1.getX() > e2.getX() ? 1 : -1)));
    Arrays.sort(pointsSortedY, (e1, e2) -> 
        (e1.getY() > e2.getY() ? 1 : (e1.getY() < e2.getY() ? -1 : 0)));
        
    return findNearstPair(pointsSortedX, 0, size - 1, pointsSortedY, 1);
}

    /** Return the distance of the closest PointPair of points
 * in pointsOrderedOnX[low..high]. This is a recursive
 * method. pointsOrderedOnX and pointsOrderedOnY are
 * not changed in the subsequent recursive calls.
*/
public static PointPair findNearstPair(Point2D[] pointsOrderedOnX, int low, int high, Point2D[] pointsOrderedOnY) {
    if (low == high)
        return new PointPair(pointsOrderedOnX[low], pointsOrderedOnX[high], Double.MAX_VALUE);
    else if ((high - low) == 1)
        return new PointPair(pointsOrderedOnX[low], pointsOrderedOnX[high], pointsOrderedOnX[low].distance(pointsOrderedOnX[high]));
    else{
        // Divide
        int mid = (high - low) / 2;

        PointPair leftPair = findNearstPair(pointsOrderedOnX, low, low + mid, pointsOrderedOnY);
        PointPair rightPair = findNearstPair(pointsOrderedOnX, low + mid + 1, high, pointsOrderedOnY);
        
        // Merge the right & left
        shortestPair = leftPair.distance > rightPair.distance ? rightPair : leftPair;
        System.out.println("shortest "  + shortestPair);
        
        // Find points in pointsOrderedOnY that belong in stripL & stripR
        // this any x that is less then d away from the middle of
        // the 2 sections (between mid & mid + 1)
        double minXinS1 = pointsOrderedOnX[low].getX();
        // <= midX is S1(left side) > midX is S2(right side)
        double midX = pointsOrderedOnX[low + mid].getX();
        double maxXinS2 = pointsOrderedOnX[high].getX();

        List<Integer> stripL = new ArrayList<Integer>();
        List<Integer> stripR = new ArrayList<Integer>();
        for(int p = 0; p < pointsOrderedOnY.length; p++){
            double pX = pointsOrderedOnY[p].getX();
            
            double pY =pointsOrderedOnY[p].getY();
            
            // is in S1 & less then distance away from middle
            // >= minXinS1 & <= midX & >= midX - d = stripL
            if(pX <= midX && pX >= minXinS1 && pX >= midX - shortestPair.distance){
                stripL.add(p);
            }
            //is in S2 & less then distance away from middle
            // > midX && <= maxXinS2 & > midX & < midX + d
            else if(pX > midX && pX <= maxXinS2 && pX < midX + shortestPair.distance){
                stripR.add(p);
            }
        }
        
        // For each point in stripL find the points in stripR that are less then
        // distance away (max 6 points in stripR)
        int stripRIndx = 0; // 
        for(int p = 0; p < stripL.size(); p++){
            double pY = pointsOrderedOnY[stripL.get(p)].getY();
            
            // Skip points in stripR that are below pY - distance
            while(stripRIndx < stripR.size() && pointsOrderedOnY[stripR.get(stripRIndx)].getY() 
                            <= pY - shortestPair.distance)
                stripRIndx++;
                
            // Check points in stripR that ate within pY +/- distance
            int tempStripRIndex = stripRIndx;
            while(tempStripRIndex < stripR.size() && pointsOrderedOnY[stripR.get(tempStripRIndex)].getY()
                            <= pY + shortestPair.distance){
                if(pointsOrderedOnY[stripL.get(p)].distance(pointsOrderedOnY[stripR.get(tempStripRIndex)]) < shortestPair.distance){
                    shortestPair.p1 = pointsOrderedOnY[stripL.get(p)];
                    shortestPair.p2 = pointsOrderedOnY[stripR.get(tempStripRIndex)];
                    shortestPair.distance = pointsOrderedOnY[stripL.get(p)].distance(pointsOrderedOnY[stripR.get(tempStripRIndex)]);
                }
                tempStripRIndex++;
            }
        }
        return shortestPair;
    }
}

class PointPair {
Point2D p1;
Point2D p2;
double distance = Double.MAX_VALUE;

PointPair(){}

PointPair(Point2D p1, Point2D p2, double distance) {
    this.p1 = p1;
    this.p2 = p2;
    this.distance = distance;
}

@Override
public String toString(){
    return p1 + ":" + p2 + " distance = " + distance;
}

编辑 我见过this question。我正在寻找我的代码中的错误以及如何解决它。我正在尝试实现与那里提到的相同的想法。

【问题讨论】:

  • 这能回答你的问题吗? Find two closest points in 2D distribution
  • @Progman 第一个答案是我不熟悉的 C++。第二个答案是指*。我现在谈到我的问题中提到的分而治之。我的问题在于他的实施。如果您有机会,请告诉我我在答案中实施它的方式是否正确。它似乎并不比蛮力实施更快。

标签: java nearest-neighbor divide-and-conquer


【解决方案1】:

在进一步分析我的代码后,这是我想出的。

代码:

    public static PointPair findNearstPair(Point2D[] pointsOrderedOnX, int low, int high, Point2D[] pointsOrderedOnY) {
    PointPair shortestPair;

    if (low == high)
        return new PointPair(pointsOrderedOnX[low], pointsOrderedOnX[high], Double.MAX_VALUE);
    else if ((high - low) == 1)
        return new PointPair(pointsOrderedOnX[low], pointsOrderedOnX[high], pointsOrderedOnX[low].distance(pointsOrderedOnX[high]));

    // Divide
    int mid = (high - low) / 2;
    PointPair leftPair = findNearstPair(pointsOrderedOnX, low, low + mid, pointsOrderedOnY);
    PointPair rightPair = findNearstPair(pointsOrderedOnX, low + mid + 1, high, pointsOrderedOnY);

    // Merge the right & left
    shortestPair = leftPair.distance > rightPair.distance ? rightPair : leftPair;

    /* Find points in pointsOrderedOnY that belong in stripL & stripR
      * this any x that is less then d away from the middle of
      * the 2 sections (between mid & mid + 1)
      */
    double midX = pointsOrderedOnX[low + mid].getX();
    double midY = pointsOrderedOnX[low + mid].getY();

    double leftS1 = pointsOrderedOnX[low].getX();
    double leftStripL = Math.max(leftS1, midX - shortestPair.distance);
    double rightS2 = pointsOrderedOnX[high].getX();
    double rightStripR = Math.min(rightS2, midX + shortestPair.distance);

    List<Integer> stripL = new ArrayList<Integer>();
    List<Integer> stripR = new ArrayList<Integer>();
    for (int p = 0; p < pointsOrderedOnY.length; p++) {
        double pX = pointsOrderedOnY[p].getX();
        double pY = pointsOrderedOnY[p].getY();

        if (pX >= leftStripL && pX <= rightStripR) { // in current section(S1 & S2)
            if (pX == midX) {
                if (pointsOrderedOnX[mid + low] == pointsOrderedOnY[p]) {
                    stripL.add(p);
                } else if (pY == midY) {
                    shortestPair.p1 = pointsOrderedOnX[mid + low];
                    shortestPair.p2 = pointsOrderedOnY[p];
                    shortestPair.distance = shortestPair.p1.distance(shortestPair.p2);
                    return shortestPair;
                } else if (pY > midY) {
                    stripR.add(p);
                } else {
                    stripL.add(p);
                }
            } else if (pX > midX) {
                stripR.add(p);
            } else {
                stripL.add(p);
            }
        }
    }

即使输入中有许多项目在 S1 和 S2 之间划分,这也有效。

这段代码的主要区别在于,当我找到一个 x 坐标与mid 的 x 坐标相同的点时,我会检查 y 坐标。如果它小于mid 的 y 坐标,则它进入stripL。如果更大,则进入stripR。如果它相等,那么我检查它是否与mid 相同,然后它在stripL 中,如果不是,则意味着我们有 2 个具有相同坐标的点,这将是最接近的 2 个点,所以我返回这对。

【讨论】:

    最近更新 更多