[英]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.