2D最近邻搜索

4
从绿色正方形开始,我希望有一个高效的算法来找到最近的3x3窗口,其中没有红色正方形,只有蓝色正方形。该算法不需要精确地找到最接近的3x3窗口,但它应该找到一个靠近绿色正方形的3x3全蓝窗口(假设存在这样的窗口)。我考虑将其实现为递归广度优先搜索,但该解决方案会涉及多次重新检查同一正方形。发布此内容以查看是否有人知道更有效的解决方案。检查给定正方形的成本是恒定且廉价的,但我希望尽可能减少算法的执行时间(将此实际应用于在更大的2D搜索区域内找到3x3“清除”/全蓝窗口)。

enter image description here

这是一个示例解决方案,但我认为它并不是最优的。实际上,这是一种深度优先搜索,我需要重新构建它以转换为广度优先搜索,但我需要再考虑一下如何做到这一点(一种方法是将每个点作为一个对象扩展到相邻点,然后多次迭代这些点到子节点,访问这些子节点,然后允许这些子节点生成更多的子节点)。重点是我认为有更有效和常见的方法来做到这一点,所以我试图避免重复造轮子。

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;
    }
}

更新: 距离度量并不是很重要,但为了简单起见,让我们最小化曼哈顿距离。

这是一个更好的算法,它不使用递归,并且保证能够找到最接近的解(如果有多个相同的解,则找到其中一个)。它需要一个大于5 x 5的网格才能正常工作,但如果您想搜索比那小的网格,则可能可以使用更有效的算法。假设最低的x索引为0,最低的y索引也为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
    }
}

请提供您已经尝试过的代码。 - Sphinx
首先,选择一个简单且正确工作的解决方案。然后进行性能优化。你有什么假设,平均有多少个位置是红色的?距离是如何测量的?使用a²+b²=c²还是曼哈顿距离?你有什么自己的想法来提高性能吗?你听说过for循环吗? - user unknown
你如何定义“最近”?是指到正方形中心点的最短欧几里得距离吗? - Imran
另外,相对于您查询的次数,您的单元格更改频率有多高?如果不是很频繁,那么我们可以考虑一些预处理。 - Imran
这是一个简单的问题。假设您的绿色正方形是正方形的中心(因为它被3x3正方形包围)。运行“迭代加深深度优先搜索”,直到找到结束角落或找到蓝色正方形。 - Luai Ghunim
4个回答

1

一个简单的建议是标记您已经检查过的所有单元格。这样,您就不必多次检查单元格。

递归肯定比基于迭代的方法需要更多的时间,因为每次进行新调用时它将创建一个新的堆栈。如果您正在尝试找到最接近的点,请优先使用BFS而不是DFS。

我还建议快速搜索“Flood Fill Algorithm”。


1
你可以从起始像素开始向外螺旋扩散。每当遇到未检查过的像素p时,检查p周围3x3的环境。
对于环境中的每个红色像素r,将r的3x3环境设置为已检查。
如果环境中没有红色像素,则找到了解决方案。

1
你试图在更一般的意义上找到一种对数组进行形态滤波的方法。
我们可以将该过滤器定义为一个3x3的滑动窗口,将窗口中心设置为窗口内数组元素的总和。蓝色方块表示为1,红色方块表示为0。
在此情况下,您正在尝试找到总和值为9的最近元素。
请注意,解决此问题的一种方法是将3x3窗口滑动到数组上以覆盖所有可能的位置。在这种情况下,您将查看9*宽度*高度个元素。然后,您可以使用广度优先搜索找到最近的总和值为9,最多需要进行宽度*高度次检查。因此,您的算法的朴素时间与10*宽度*高度成正比。
您可以通过确保您的过滤器只需要查看每个聚焦单元格的一个值而不是9个来减少这个时间。为此,请生成一个总面积表。现在,您的时间与2*宽度*高度成正比。

An example of a summed-area table

一个累加面积表的示例

你可能能够使这个过程更快。每次找到一个值为9的时候,将其与此时绿色单元格的位置进行比较。如果大多数单元格不是9,则可以将时间减少到与宽度*高度成比例。

Hensley等人(2005)的论文《快速生成累加面积表及其应用》解释了如何使用图形硬件在O(log n)的时间内生成累加面积表。因此,真正减少运行时间是有可能的。 Nehab等人(2011)的论文《GPU高效递归滤波和累加面积表》也许也会有用(源代码):他们的工作表明对于像你这样的小窗口,直接方法可能是最有效的。


1
我认为最简单的方法是使用稍作修改的广度优先搜索。
如果我们谈论曼哈顿距离,那么每个方格最多会有4个相邻方格。在每一步中,我们检查相邻方格数量是否等于3(第四个相邻方格是我们所来自的方格)。如果是这样,我们就会检查对角线。否则 - 继续搜索。
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));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接