3

Java でパスファインダーを作成しましたが、ほとんどの場合、うまく機能します。しかし、私はそれがうまくいかないシナリオを見つけました。私が使用するヒューリスティックは一貫しているはずであり、一貫したヒューリスティックとは、アルゴリズムが常に展開するノードに最も近いルートを見つける必要があることを意味します。

問題の写真は次のとおりです。

開始ノードは緑で、数字は特定の各ノードから赤で表されるゴールまでのパスの長さを表しています。

私のヒューリスティッククラス:

package heuristics;
import pathcomponents.Direction;


public class Heuristics {

    public static int DO(int x1, int y1, int x2, int y2) {
        int dx = Math.abs(x1 - x2);
        int dy = Math.abs(y1 - y2);
        int D, O;
        if(dx > dy) {
            D = dy;
            O = dx - D;
        }
        else {
            D = dx;
            O = dy - D;
        }
        return D*Direction.D_COST + O*Direction.O_COST;
    }

}

方向.D_COST = 14、方向.O_COST = 10

ヒューリスティックは次の値を返します: 対角距離*14 + 直交距離*10。

アルゴリズム:

package pathfinders;


import java.util.LinkedList;
import pathcomponents.Direction;
import pathcomponents.Node;
import pathcomponents.Path;
import heuristics.Heuristics;


public class ProxyAStar {

    static private boolean contains(LinkedList<Node> list, int x, int y) {
        for(Node n : list)
            if(n.getX() == x && n.getY() == y) return true;
        return false;
    }

    public static Path buildPath(Node lcnode) {
        int cost = lcnode.getG();
        LinkedList<Direction> path = new LinkedList<Direction>();
        while(lcnode != lcnode.getParent()) {
            int dx = lcnode.getX() - lcnode.getParent().getX();
            int dy = lcnode.getY() - lcnode.getParent().getY();
            path.add(new Direction(dx, dy));
            lcnode = lcnode.getParent();
        }
        return new Path(path, cost);
    }

    public static Path search(boolean[][] map, int sx, int sy, int gx, int gy) {
        LinkedList<Node> openList   = new LinkedList<Node>();
        LinkedList<Node> closedList = new LinkedList<Node>();
        openList.add(new Node(sx, sy, 0, Heuristics.DO(sx, sy, gx, gy), null));
        while(!openList.isEmpty()) {
            Node lcnode = openList.peekFirst();
            for(Node n : openList) {
                if(n.getCost() < lcnode.getCost()) {
                    lcnode = n;
                }
            }
            int x = lcnode.getX();
            int y = lcnode.getY();
            if(x == gx && y == gy) {
                return buildPath(lcnode);
            }
            closedList.add (lcnode);
            openList.remove(lcnode);
            for(int i = -1; i <= 1; ++i) {
                for(int j = -1; j <= 1; ++j) {
                    int cx = x + i;
                    int cy = y + j;
                    if((i == 0 && j == 0) || map[cx][cy] == false)continue;
                    if(!contains(openList,cx,cy) && !contains(closedList,cx,cy)){
                        openList.add(new Node(cx, cy, lcnode.getG() + Heuristics.DO(x, y, cx, cy), Heuristics.DO(cx, cy, gx, gy), lcnode));
                    }
                }
            }
        }
        Node lcnode = closedList.peekFirst();
        for(Node n : closedList) {
            if(n.getH() < lcnode.getH()) {
                lcnode = n;
            }
        }
        openList   = null;
        closedList = null;
        return search(map, sx, sy, lcnode.getX(), lcnode.getY());
    }
}

クラス ノードには通常の G、H、F コストと親参照があります。コンストラクターが親パラメーターとして null を受け取ると、それ自体が親になります。buildPath 関数で「lcnode == lcnode.getParent()」という条件が満たされると、パス構築ループが停止するのはそのためです。これは、展開された最初のノードがそれ自体の親であるためです。パスは、x 座標と y 座標で構成される方向ピースで構成され、それぞれが -1、0、または 1 のいずれかです。この理由は、パスが相対座標によってゴールにつながるようにしたかったからです。マップの境界チェックはありません。これは意図的なものです。これを、境界ノードを歩行不能にすることで置き換えます。

他の写真:

今回はうまくいきます。違いは、次のように最小コストの閉じたノードを検索するため、最後の colsed ノードの周りにノードを展開する順序に関係しています。

for(Node n : openList) {
    if(n.getCost() < lcnode.getCost()) {
        lcnode = n;
    }
}

不等式を「<=」に変更すると、最初の図の問題が修正され、2 番目の図が台無しになります。

余談ですが、A* を少し拡張して、パスがない場合に、クローズド リストから H コストが最も低いノードを取得し、そのノードを対象として別の検索を実行するようにしました。このようにして、現在ゴール ノードへのパスがない場合でも、ゴールに近づくことができます。

ここに画像の説明を入力

すべてのクラスを含めたわけではありませんが、他のクラスはこの問題とは無関係だと思います。何か不明な点がある場合は、質問を長くしすぎて読みたくありませんでした。

私の知る限り、理論では、一貫したヒューリスティックが最適なコストでノードの拡張を保証することが示されているため、不正確さを修正する方法をまだ理解できませんでした。コードに間違いはありましたか?そうでない場合、どうすれば問題を解決できますか?


編集:明確にするために、不足しているコードのいくつかを含めました:

クラスの方向性:

package pathcomponents;
public class Direction {
    public static final int D_COST = 14;
    public static final int O_COST = 10;
    public static int signum(int n) {
        return (n < 0) ? -1 : ((n == 0) ? 0 : 1);
    }
    private final int x, y;
    public Direction(int x, int y) {
        this.x = signum(x);
        this.y = signum(y);
    }
    public Direction(Direction source) {
        this.x = source.x;
        this.y = source.y;
    }
    public int getX() {return x;}
    public int getY() {return y;}
}

クラス ノード:

package pathcomponents;
public class Node {
    private final int    x, y;
    private       int       G;
    private final int       H;
    private       int       F;
    private       Node parent;
    public Node(int x, int y, int G, int H, Node parent) {
        this.x      =                                x;
        this.y      =                                y;
        this.G      =                                G;
        this.H      =                                H;
        this.F      =                            G + H;
        this.parent = (parent == null) ? this : parent;
    }
    public int  getX()      {return      x;}
    public int  getY()      {return      y;}
    public int  getG()      {return      G;}
    public int  getH()      {return      H;}
    public int  getCost()   {return      F;}
    public Node getParent() {return parent;}
    public void setParent(Node parent, int G) {
        this.parent = parent;
        this.G      =      G;
        F           = G +  H; 
    }
}
4

2 に答える 2

1

これは、ウィキペディアからの A* の疑似コードで、一貫したヒューリスティックを使用しています。

1. while openset is not empty
2.     current := the node in openset having the lowest f_score[] value
3.     if current = goal
4.        return reconstruct_path(came_from, goal)
5. 
6.     remove current from openset
7.     add current to closedset
8.     for each neighbor in neighbor_nodes(current)
9.        tentative_g_score := g_score[current] + dist_between(current,neighbor)
10.       if neighbor in closedset and tentative_g_score >= g_score[neighbor]
11.          continue
12.
13.       if neighbor not in openset or tentative_g_score < g_score[neighbor] 
14.          came_from[neighbor] := current
15.          g_score[neighbor] := tentative_g_score
16.          f_score[neighbor] := g_score[neighbor] + heuristic_cost_estimate(neighbor, goal)
17.          if neighbor not in openset
18.             add neighbor to openset
19. 
20. return failure

重要な行は 13 です。現在のノードの隣接ノードがすでにオープンセットにある場合は、その g-score と親ノードを更新する必要がある場合があります。

これは、一貫性のあるヒューリスティックと一貫性のないヒューリスティックの両方に当てはまります。一貫したヒューリスティックが提供するのは、このチェックをスキップする機能ではなく、既に展開されているノード(つまり、クローズド セットにあるノード) を再キューイングする必要がない機能です。


LinkedList<Node> openList   = new LinkedList<Node>();

openListPriorityQueueによって注文する必要がありますg(x) + h(x)。そうすれば、はるかに優れたパフォーマンスが得られます。

余談ですが、A* を少し拡張して、パスがない場合に、クローズド リストから H コストが最も低いノードを取得し、そのノードを対象として別の検索を実行するようにしました。

2 回目の検索を実行する必要はありません。到達不能な目的地に最も近い場所を見つけるために AStar を微調整する を参照してください。

于 2013-09-25T16:46:18.717 に答える
1

あなたのヒューリスティックが許容できると確信していますか? 私はコードを見つけました:

if(dx > dy) {
    D = dy;
    O = dx - D;
} else {
    D = dx;
    O = dy - D;
}
return D*Direction.D_COST + O*Direction.O_COST;

驚くべき。

特に、dx = 100、dy = 1 とします。実際の距離は 1000.05 であり (あなたが行ったように、各正方形の長さの単位が 10 であることを考慮して)、ヒューリスティックは 1004 と推定します。つまり、許容されません (最短経路を取得したい)。

于 2013-09-25T16:30:42.057 に答える