简体   繁体   English

2D最近邻搜索

[英]2D Nearest Neighbor Search

Starting from the green square, I want an efficient algorithm to find the nearest 3 x 3 window with no red squares, only blue squares. 从绿色方块开始,我想要一个有效的算法来找到最近的3 x 3窗口,没有红色方块,只有蓝色方块。 The algorithm doesn't need to find exactly the closest 3 x 3 window, but it should find a 3 x 3 all-blue window close to the green square (assuming one exists). 该算法不需要找到最接近的3 x 3窗口,但它应该找到靠近绿色方块的3 x 3全蓝窗口(假设存在一个)。 I thought about implementing this as a recursive breadth-first search, but that solution would involve re-checking the same square many times. 我考虑将其实现为递归广度优先搜索,但该解决方案将涉及多次重新检查同一个方块。 Posting this to see if someone knows of a more efficient solution. 发布此信息以查看是否有人知道更有效的解决方案。 Cost to check a given square is constant and cheap, but I want to minimize the execution time of the algorithm as much as possible (practical application of this will involve finding a 3x3 "clear" / all-blue window within a much larger 2D search area). 检查给定方块的成本是恒定且廉价的,但我希望尽可能地减少算法的执行时间(实际应用这将涉及在更大的2D搜索中找到3x3“清除”/全蓝窗口区域)。

在此输入图像描述

Here's an example solution, but I don't think it's optimal. 这是一个示例解决方案,但我不认为它是最佳的。 It's actually a depth-first search that I will have to restructure to convert to a breadth-first, but I need to think a bit more about how to do that (one way would be to make each point an object that expands to neighboring points, then iterate multiple times across those points to children, visit those children before allowing those children to generate more children). 它实际上是一个深度优先搜索,我将不得不重组以转换为广度优先,但我需要更多地思考如何做到这一点(一种方法是使每个点成为扩展到相邻点的对象,然后在这些点上多次迭代给孩子,在允许这些孩子生成更多孩子之前访问这些孩子)。 Point is that I think there's a more efficient and common way to do this so I'm trying to avoid reinventing the wheel. 重点是,我认为这是一种更有效和常用的方法,所以我试图避免重新发明轮子。

public class Search2D {
    private TreeSet<Point> centerpointscheckedsofar;

    private Point Search(Point centerpoint) {
        if(centerpointscheckedsofar.contains(centerpoint)) {
            return null;
        }
        if(isWithinBounds(centerpoint)) {
            if(checkCenterPoint(centerpoint)) {
                centerpointscheckedsofar.add(centerpoint);
                return null;
            }
            Point result = Search(getPoint(-1, -1, centerpoint));
            if(result != null) return result;
            result = Search(getPoint(-1, 0, centerpoint));
            if(result != null) return result;
            result = Search(getPoint(-1, 1, centerpoint));
            if(result != null) return result;
            result = Search(getPoint(0, -1, centerpoint));
            if(result != null) return result;
            result = Search(getPoint(0, 1, centerpoint));
            if(result != null) return result;
            result = Search(getPoint(1, -1, centerpoint));
            if(result != null) return result;
            result = Search(getPoint(1, 0, centerpoint));
            if(result != null) return result;
            result = Search(getPoint(1, 1, centerpoint));
            if(result != null) return result;
        }
        return null;
    }

    private Point getPoint(int x, int y, Point centerpoint) {
        return new Point(centerpoint.x + x, centerpoint.y + y);
    }

    private boolean checkCenterPoint(Point centerpoint) {
        //check here to see if point is valid
        return false;
    }

    private boolean isWithinBounds(Point startPoint) {
        //check here to see if point and all neighboring points of 3 x 3 window falls within bounds
        return false;
    }
}

UPDATE: Distance measure is not that important, but for simplicity, let's minimize Manhattan distance. 更新:距离测量并不重要,但为简单起见,让我们最小化曼哈顿距离。

Here's a better algorithm that does not use recursion and will be guaranteed to find the closest solution (or one of the closest solutions if there is a tie). 这是一个更好的算法,它不使用递归,并且可以保证找到最接近的解决方案(如果存在平局,则可以找到最接近的解决方案之一)。 It needs a grid greater than 5 x 5 to work properly, but if you want to search a grid smaller than that, there's probably a more efficient algorithm that can be used. 它需要一个大于5 x 5的网格才能正常工作,但是如果你想搜索一个小于它的网格,可能会有一个更有效的算法可以使用。 Assumes lowest x-index is 0 and lowest y-index is also 0. 假设最低x-index为0,最低y-index也为0。

import java.awt.Point;

public class Search2D_v2 {
    private boolean[][] bitgrid;

    public Search2D_v2() {
        bitgrid = new boolean[20][20];
    }

    public Point search(int centerx, int centery, int maxx, int maxy, int maxsearchsteps) { 
        //check starting point first, if it works, we're done
        if(checkPoint(centerx, centery)) {
            return new Point(centerx, centery);
        }

        int westbound = centerx-1;
        boolean keepgoingwest = true;
        int eastbound = centerx+1;
        boolean keepgoingeast = true;
        int southbound = centery-1;
        boolean keepgoingsouth = true;
        int northbound = centery+1;
        boolean keepgoingnorth = true;

        //stay within bounds, may move initial search square by 1 east and 1 west
        if(westbound <= 0) {
            eastbound = 3;
            westbound = 1;
        }
        if(eastbound >= maxx) {
            eastbound = maxx - 1;
            westbound = maxx - 3;
        }
        if(southbound == 0) {
            northbound = 3;
            southbound = 1;
        }
        if(northbound == maxy) {
            northbound = maxy - 1;
            southbound = maxy - 3;
        }

        //always search boundary, we've already searched inside the boundary on previous iterations, expand boundary by 1 step / square for each iteration
        for(int i = 0; i < maxsearchsteps && (keepgoingwest || keepgoingeast || keepgoingsouth || keepgoingnorth); i++) {
            //search top row
            if(keepgoingnorth) { //if we have already hit the north bound, stop searching the top row
                for(int x = westbound; x <= eastbound; x++) {
                    if(checkPoint(x, northbound)) {
                        return new Point(x, northbound);
                    }
                }
            }

            //search bottom row
            if(keepgoingsouth) {
                for(int x = westbound; x <= eastbound; x++) {
                    if(checkPoint(x, southbound)) {
                        return new Point(x, southbound);
                    }
                }
            }

            //search westbound
            if(keepgoingwest) {
                for(int y = southbound; y <= northbound; y++) {
                    if(checkPoint(westbound, northbound)) {
                        return new Point(westbound, y);
                    }
                }
            }

            //search eastbound
            if(keepgoingeast) {
                for(int y = southbound; y <= northbound; y++) {
                    if(checkPoint(eastbound, northbound)) {
                        return new Point(eastbound, y);
                    }
                }
            }

            //expand search area by one square on each side
            if(westbound - 2 >= 0) {
                westbound--;
            }
            else {
                keepgoingwest = false;
            }

            if(eastbound + 2 <= maxx) {
                eastbound++;
            }
            else {
                keepgoingeast = false;
            }

            if(southbound - 2 >= 0) {
                southbound--;
            }
            else {
                keepgoingsouth = false;
            }

            if(northbound + 2 <= maxy) {
                northbound++;
            }
            else {
                keepgoingnorth = false;
            }
        }
        return null; //failed to find a point
    }

    private boolean checkPoint(int centerx, int centery) {
        return !bitgrid[centerx][centery] && //center
                    !bitgrid[centerx-1][centery-1] && //left lower
                    !bitgrid[centerx-1][centery] && //left middle
                    !bitgrid[centerx-1][centery+1] && //left upper
                    !bitgrid[centerx][centery-1] && //middle lower
                    !bitgrid[centerx][centery+1] && //middle upper
                    !bitgrid[centerx+1][centery-1] && //right lower
                    !bitgrid[centerx+1][centery] && //right middle
                    !bitgrid[centerx+1][centery+1]; //right upper
    }
}

A simple advice would be to mark all the cells you have checked. 一个简单的建议是标记您检查过的所有单元格。 That way you won't have to check the cells multiple times. 这样您就不必多次检查单元格。

Recursion will definitely take more time than an iteration based approach since it will create a new stack each time you make a new call. 递归肯定比基于迭代的方法花费更多时间,因为每次进行新调用时它都会创建一个新堆栈。 If you are trying to find the closest one, prefer BFS over DFS. 如果您正在尝试找到最接近的一个,则更喜欢BFS而不是DFS。

I would also suggest making a quick internet research for "Flood Fill Algorithm". 我还建议对“洪水填充算法”进行快速的互联网研究。

You could spiral outwards from your starting pixel. 你可以从你的起始像素向外螺旋。 Whenever you encounter a pixel p that has not been checked, examine the 3x3 environment around p. 每当遇到未经检查的像素p时,请检查p周围的3x3环境。

For each red pixel r in the environment set the 3x3 environment of r to checked. 对于环境中的每个红色像素r,设置r的3x3环境进行检查。

If there was no red pixel in the environment you found a solution. 如果环境中没有红色像素,则找到解决方案。

What you're trying to find in a more general sense is a kind of morphological filter of your array. 你想要在更一般意义上找到的是你的数组的一种形态滤波器。

We can define the filter as a 3x3 sliding window which sets the center of the window to the sum of the array elements within the window. 我们可以将过滤器定义为3x3滑动窗口,该窗口将窗口的中心设置为窗口内数组元素的总和。 Let blue squares be represented by 1 and red squares be represented by 0. 设蓝色方块用1表示,红色方块用0表示。

In this situation, you're trying to find the closest element with a sum value of 9. 在这种情况下,您正在尝试找到总和值为9的最近元素。

Note that one way of solving this problem is slide a 3x3 window across your array so that it covers all possible locations. 请注意,解决此问题的一种方法是在阵列中滑动3x3窗口,以便覆盖所有可能的位置。 In this case, you would look at 9*width*height elements. 在这种情况下,您将查看9 * width * height元素。 You could then find the nearest sum value of 9 using a breadth-first search in, at most, width*height checks. 然后,您可以使用广度优先搜索最多宽度*高度检查找到最接近的总和值9。 So the naive time of your algorithm is proportional to 10*width*height 因此,算法的幼稚时间与10 *宽*高度成正比

You can reduce this by ensuring that your filter only has to look at one value per focal cell, rather than 9. To do so, generate a summed-area table . 您可以通过确保过滤器只需要查看每个焦点单元格的一个值而不是9来减少这种情况。为此,生成一个求和区域表 Now your time is proportional to 2*width*height . 现在你的时间与2 *宽*高度成正比。

求和区域表的示例

An example of a summed-area table 求和区域表的示例

You can might be able to make this faster. 你可以更快地做到这一点。 Each time you find a value of 9, compare it against the location of your green cell at that moment. 每次找到值9时,将其与当时绿色单元格的位置进行比较。 If most cells are not 9s, this reduces your time to some proportional to width*height . 如果大多数单元格不是9s,则会缩短与宽度*高度成比例的时间。

Hensley et al. 亨斯利等人。 (2005)'s paper Fast Summed-Area Table Generation and its Applications explains how to use graphics hardware to generate the summed-area table in O(log n) time. (2005)的论文快速求和区域表生成及其应用说明了如何使用图形硬件在O(log n)时间内生成求和区域表。 So it's possible to really reduce run-times on this. 因此,可以真正减少运行时间。 Nehab et al. Nehab等人。 (2011)'s paper GPU-efficient recursive filtering and summed-area tables might also be useful ( source code ): their work suggests that for small windows, such as yours, the direct approach may be most efficient. (2011)的纸张GPU高效的递归过滤和求和区域表也可能有用( 源代码 ):他们的工作表明,对于小窗口,如你的,直接方法可能是最有效的。

I think the easiest way is to use a slightly modified breadth-first search. 我认为最简单的方法是使用略微修改的广度优先搜索。

If we talk about Manhattan distance, then each square will have maximum 4 neighbors. 如果我们谈论曼哈顿距离,那么每个广场将有最多4个邻居。 On each step we check if the number of neighbors is equal to 3 (the fourth neighbor is a square we came from). 在每一步,我们检查邻居的数量是否等于3(第四个邻居是我们来自的方格)。 If so, we check diagonals. 如果是这样,我们检查对角线。 Else - continue search. 否则 - 继续搜索。

public class Field3x3 {

    private static class Point {
        int x, y, distance;
        Point previous;
        public Point(int x, int y) {
            this.x = x;
            this.y = y;
            this.distance = 0;
            this.previous = this;
        }
        public Point(int x, int y, Point previous) {
            this.x = x;
            this.y = y;
            this.previous = previous;
            this.distance = previous.distance + 1;
        }
        @Override
        public String toString() {
            return "{x: " + x +", y: " + y + ", distance:" + distance +'}';
        }
    }

    private static Point traverse(int[][] field, int x, int y) {
        int i = 0;
        Queue<Point> q = new LinkedList<>();
        q.add(new Point(x, y));
        while (!q.isEmpty()) {
            Point p = q.remove();
            System.out.print(i++ + ". current: " + p);
            if (field[p.y][p.x] == 1) {
                field[p.y][p.x] = 2;
                List<Point> neighbors = getNeighbors(p, field);
                System.out.println(", neighbors: " + neighbors);
                if (neighbors.size() == 3 && checkDiagonals(p, field)) return p;
                for (Point neighbor : neighbors) {
                    if (field[neighbor.y][neighbor.x] == 1) {
                        q.add(neighbor);
                    }
                }
            } else System.out.println(", already visited");
        }
        return null;
    }

    private static boolean checkDiagonals(Point p, int[][] field) {
        return field[p.y - 1][p.x - 1] > 0 && field[p.y + 1][p.x - 1] > 0
                && field[p.y - 1][p.x + 1] > 0 && field[p.y + 1][p.x + 1] > 0;
    }

    private static List<Point> getNeighbors(Point p, int[][] field) {
        List<Point> neighbors = new ArrayList<>();
        if (p.y > 0 && field[p.y - 1][p.x] > 0 && p.y <= p.previous.y)
            neighbors.add(new Point(p.x, p.y - 1, p));
        if (p.y < field.length - 1 && field[p.y + 1][p.x] > 0 && p.y >= p.previous.y)
            neighbors.add(new Point(p.x, p.y + 1, p));
        if (p.x > 0 && field[p.y][p.x - 1] > 0 && p.x <= p.previous.x)
            neighbors.add(new Point(p.x - 1, p.y, p));
        if (p.x < field[p.y].length - 1 && field[p.y][p.x + 1] > 0 && p.x >= p.previous.x)
            neighbors.add(new Point(p.x + 1, p.y, p));
        return neighbors;
    }

    public static void main(String[] args){
        int[][] field = {{1,0,0,1,1,0,1,1,1},
                         {1,1,1,1,1,1,1,0,1},
                         {1,1,1,0,1,0,1,1,1},
                         {0,1,1,1,1,1,1,1,0},
                         {1,1,1,0,0,1,1,1,0},
                         {1,0,1,1,1,1,0,1,0},
                         {1,1,1,1,0,1,1,1,0},
                         {1,1,1,0,1,1,1,1,0},
                         {1,1,1,1,0,1,1,1,0}};
        System.out.println("Answer: " + traverse(field, 1, 2));
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM