簡體   English   中英

2D最近鄰搜索

[英]2D Nearest Neighbor Search

從綠色方塊開始,我想要一個有效的算法來找到最近的3 x 3窗口,沒有紅色方塊,只有藍色方塊。 該算法不需要找到最接近的3 x 3窗口,但它應該找到靠近綠色方塊的3 x 3全藍窗口(假設存在一個)。 我考慮將其實現為遞歸廣度優先搜索,但該解決方案將涉及多次重新檢查同一個方塊。 發布此信息以查看是否有人知道更有效的解決方案。 檢查給定方塊的成本是恆定且廉價的,但我希望盡可能地減少算法的執行時間(實際應用這將涉及在更大的2D搜索中找到3x3“清除”/全藍窗口區域)。

在此輸入圖像描述

這是一個示例解決方案,但我不認為它是最佳的。 它實際上是一個深度優先搜索,我將不得不重組以轉換為廣度優先,但我需要更多地思考如何做到這一點(一種方法是使每個點成為擴展到相鄰點的對象,然后在這些點上多次迭代給孩子,在允許這些孩子生成更多孩子之前訪問這些孩子)。 重點是,我認為這是一種更有效和常用的方法,所以我試圖避免重新發明輪子。

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

一個簡單的建議是標記您檢查過的所有單元格。 這樣您就不必多次檢查單元格。

遞歸肯定比基於迭代的方法花費更多時間,因為每次進行新調用時它都會創建一個新堆棧。 如果您正在嘗試找到最接近的一個,則更喜歡BFS而不是DFS。

我還建議對“洪水填充算法”進行快速的互聯網研究。

你可以從你的起始像素向外螺旋。 每當遇到未經檢查的像素p時,請檢查p周圍的3x3環境。

對於環境中的每個紅色像素r,設置r的3x3環境進行檢查。

如果環境中沒有紅色像素,則找到解決方案。

你想要在更一般意義上找到的是你的數組的一種形態濾波器。

我們可以將過濾器定義為3x3滑動窗口,該窗口將窗口的中心設置為窗口內數組元素的總和。 設藍色方塊用1表示,紅色方塊用0表示。

在這種情況下,您正在嘗試找到總和值為9的最近元素。

請注意,解決此問題的一種方法是在陣列中滑動3x3窗口,以便覆蓋所有可能的位置。 在這種情況下,您將查看9 * width * height元素。 然后,您可以使用廣度優先搜索最多寬度*高度檢查找到最接近的總和值9。 因此,算法的幼稚時間與10 *寬*高度成正比

您可以通過確保過濾器只需要查看每個焦點單元格的一個值而不是9來減少這種情況。為此,生成一個求和區域表 現在你的時間與2 *寬*高度成正比。

求和區域表的示例

求和區域表的示例

你可以更快地做到這一點。 每次找到值9時,將其與當時綠色單元格的位置進行比較。 如果大多數單元格不是9s,則會縮短與寬度*高度成比例的時間。

亨斯利等人。 (2005)的論文快速求和區域表生成及其應用說明了如何使用圖形硬件在O(log n)時間內生成求和區域表。 因此,可以真正減少運行時間。 Nehab等人。 (2011)的紙張GPU高效的遞歸過濾和求和區域表也可能有用( 源代碼 ):他們的工作表明,對於小窗口,如你的,直接方法可能是最有效的。

我認為最簡單的方法是使用略微修改的廣度優先搜索。

如果我們談論曼哈頓距離,那么每個廣場將有最多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));
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM