首页 文章

2D最近邻搜索

提问于
浏览
4

从绿色方块开始,我想要一个有效的算法来找到最近的3 x 3窗口,没有红色方块,只有蓝色方块 . 该算法不需要找到最接近的3 x 3窗口,但它应该找到靠近绿色方块的3 x 3全蓝窗口(假设存在一个) . 我考虑将其实现为递归广度优先搜索,但该解决方案将涉及多次重新检查同一个方块 . 发布此信息以查看是否有人知道更有效的解决方案 . 检查给定方块的成本是恒定且廉价的,但我希望尽可能地减少算法的执行时间(实际应用这将涉及在更大的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;
    }
}

UPDATE: 距离测量并不重要,但为简单起见,让我们最小化曼哈顿距离 .

这是一个更好的算法,它不使用递归,并且可以保证找到最接近的解决方案(如果存在平局,则可以找到最接近的解决方案之一) . 它需要一个大于5 x 5的网格才能正常工作,但是如果你想搜索一个小于它的网格,可能会有一个更有效的算法可以使用 . 假设最低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
    }
}

4 回答

  • 1

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

    递归肯定比基于迭代的方法花费更多的时间,因为每次进行新的调用时它都会创建一个新的堆栈 . 如果您正在尝试找到最接近的一个,则更喜欢BFS而不是DFS .

    我还建议对“洪水填充算法”进行快速的互联网研究 .

  • 1

    你可以从你的起始像素向外螺旋 . 每当遇到未经检查的像素p时,请检查p周围的3x3环境 .

    对于环境中的每个红色像素r,设置r的3x3环境进行检查 .

    如果环境中没有红色像素,则找到解决方案 .

  • 1

    你想要在更一般意义上找到的是一种阵列的形态滤波器 .

    我们可以将过滤器定义为3x3滑动窗口,该窗口将窗口的中心设置为窗口内数组元素的总和 . 设蓝色方块用1表示,红色方块用0表示 .

    在这种情况下,您正在尝试找到总和值为9的最近元素 .

    请注意,解决此问题的一种方法是在阵列中滑动3x3窗口,以便覆盖所有可能的位置 . 在这种情况下,您将看到9 * width * height元素 . 然后,您可以使用广度优先搜索最多宽度*高度检查找到最近的总和值9 . 因此,算法的幼稚时间与10 高度成正比

    您可以通过确保过滤器只需要查看每个焦点单元格的一个值而不是9来减少这种情况 . 为此,生成summed-area table . 现在你的时间与2 高度成正比 .

    An example of a summed-area table

    求和区域表的示例

    你可以更快地做到这一点 . 每次找到值9时,将其与当时绿色单元格的位置进行比较 . 如果大多数单元格不是9s,则会缩短与宽度*高度成比例的时间 .

    亨斯利等人 . (2005)的论文Fast Summed-Area Table Generation and its Applications解释了如何使用图形硬件在O(log n)时间内生成求和区域表 . 所以's possible to really reduce run-times on this. Nehab et al. (2011)'篇论文GPU-efficient recursive filtering and summed-area tables也可能有用(source code):他们的工作表明,对于像你这样的小窗口,直接方法可能是最有效的 .

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

相关问题