【问题标题】:Problems with pathfinding. Weird paths being returned寻路问题。奇怪的路径被返回
【发布时间】:2014-07-18 14:56:21
【问题描述】:

我一直在阅读一些文章来学习 A* 寻路,并且我能够基于它制作一个测试程序,但是我制作的程序给出了奇怪的路径,这不是到目标节点的最短路径。

在图片中,绿色方块单元代表起始节点,红色单元代表目标节点,蓝色单元是不可通行的瓷砖(墙),紫色单元代表从起始节点到目标节点的路径

http://imgur.com/5dJEfYc

http://imgur.com/lHfXEyW

如果有人能找到寻路源代码的问题,我将不胜感激。想知道是什么导致它表现得很奇怪,我已经筋疲力尽了。

它允许偷工减料和对角线

package com.streak324.pathfinding;

import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;

import com.badlogic.gdx.utils.Array;

public class PathFinder {

    private boolean foundTarget;
    private int width, height;

    //list of nodes that leads from starting node to target node is stored here
    private Array<PathNode> path;

    //all nodes stored in this array
    private PathNode[][] nodes;

    private PriorityQueue<PathNode> open;

    private HashSet<PathNode> closed;

    //nodes that must be referenced
    private PathNode start, target, current;

    //how far the current node can reach for other nodes from its own position
    private int range = 1;

    public PathFinder(int width, int height, boolean map[][]) {
        this.width = width;
        this.height = height;
        nodes = new PathNode[width][height];

        for(int i=0; i<width; i++) {
            for(int j=0; j<height; j++) {
                nodes[i][j] = new PathNode(i, j);
                //if wall tile is spotted, mark the node unwalkable
                if(map[i][j] != true) { nodes[i][j].passable = false; }
            }
        }

        open = new PriorityQueue<PathNode>(new CostComparator());
        closed = new HashSet<PathNode>();
    }

    public Array<PathNode> getPath(int sx, int sy ,int tx, int ty) {
        path = new Array<PathNode>();
        open.clear();
        closed.clear();

        start = nodes[sx][sy];
        start.movementCost = 0;
        addToOpenList(start);

        target = nodes[tx][ty];

        while(foundTarget != true) {
            if(open.size() == 0) { return null; }

            current = open.poll();
            addToClosedList(current);

            checkNeighbors(current);
        }

        traceBack();

        return path;
    }
    // makes its way back adding the parent node until start
    private void traceBack() {
        while(current != start) {
            path.add(current);
            current = current.parent;
        }
    }

    //checks for nodes within certain range
    private void checkNeighbors(PathNode node) {
        //continues loop if i or j goes out of bounds of nodes array
        for(int i = node.x - range; i <= (node.x + range); i++) {

            if(i >= width || i < 0) { continue; }
            for(int j = node.y - range; j <= (node.y + range); j++) {

                if( j >= height || j < 0) { continue; }

                if((i == node.x && j == node.y) )  { continue; }

                PathNode neighbor = nodes[i][j];

                identifyNode(neighbor);

            }
        }
    }

    //if node is not on open list, add node and calculate it
    private void identifyNode(PathNode node) {
        if(!node.passable || closed.contains(node) ) return;

        if(node == target) {
            foundTarget = true;
            System.out.println("Target Found: " + node.x + ", " + node.y);
            return;
        }
        else if(!open.contains(node)) {
            addToOpenList(node);
            calcHeuristic(node);
            updateNode(node, current);
        }
        else {
            checkForReparenting(node);
        }
    }

    //is the movement cost less to go from the current node?
    private void checkForReparenting(PathNode node) {
        float cost = node.movementCost;
        float reCost = calcMovementCost(node, current);

        if(reCost < cost) {
            System.out.println("Reparenting");
            updateNode(node, current);
        }
    }

    //updates parent and cost
    private void updateNode(PathNode child, PathNode parent) {
        child.parent = parent;
        child.movementCost = calcMovementCost(child, parent);       
        child.totalCost = child.movementCost + child.heuristic;
    }

    private float calcMovementCost(PathNode n1, PathNode n2) {
        float dx = n1.x - n2.x;
        float dy = n1.y - n2.y;
        return (float) Math.sqrt( (dx*dx + dy*dy) ) + n2.movementCost;
    }

    private float calcHeuristic(PathNode node) {
        float dx = node.x - target.x;
        float dy = node.y - target.y;
        return (float) Math.sqrt( (dx*dx + dy*dy) );
    }

    private void addToOpenList(PathNode node) {
        if(!open.contains(node) && !closed.contains(node)) {
            open.add(node);
        }
    }

    private void addToClosedList(PathNode node) {
        if(!closed.contains(node)) {
            closed.add(node);
        }
    }

    public class PathNode {
        public int x, y;
        public PathNode parent;
        //g, h and f
        public float movementCost, heuristic, totalCost;
        public boolean passable;

        public PathNode(int x, int y) {
            this.x = x;
            this.y = y;
            passable = true;
        }

    }

    private class CostComparator implements Comparator<PathNode> {

        @Override
        public int compare(PathNode a, PathNode b) {

            if(a.totalCost < b.totalCost) return 1;
            else return -1;
        }

    }

}

没有 cmets http://pastebin.com/rSv7pUrB

我猜测优先级队列对元素进行排序的方式有问题,或者我可能没有正确计算 totalCost、motionCost 和启发式变量,但我认为没有任何问题。

非常感谢有人可以为我指出可能的问题或解决方案的正确方向

【问题讨论】:

    标签: java libgdx path-finding a-star


    【解决方案1】:

    您的代码有几个问题:

    1. 你从来没有真正使用过启发式。以下语句(对calcHeuristic 的唯一调用)只是“将结果丢弃”。

      calcHeuristic(node);
      

      仅此一项不可能是这里的错误,因为猜测到目标的距离为0 是一个有效的可接受的启发式方法。然而,算法会以这种方式退化(我认为是 Dijkstra 算法)。

    2. 您永远不会更新节点在优先级队列中的位置。这意味着更新了totalDistance 的节点将永远不会在优先队列中向上移动,即使它的totalCost 变得小于另一个节点的totalCost。您必须删除节点并再次添加它才能使用PriorityQueue:

      open.remove(node);
      // ... update totalDistance
      open.add(node);
      
    3. 对于一般 A*,您终止得太早(但是这不会成为问题,因为 totalDistance 等于实际距离,对于目标 IF 的扩展邻居,您使用启发式;这里的实际距离与sqrt(2) 或1 不同)。通常,最后一步的距离启发式可能是任意错误的(这里很糟糕,请参见 (1.)),并且您只能确定您找到了真正的解决方案,如果您将算法运行到可以扩展的点目标节点。

    【讨论】:

    • 谢谢。我更正了您指出的错误,并且还对比较器类进行了更改。而不是 if(a.totalCost
    【解决方案2】:

    估计 Streak324:

    尽管您的 A* 实现现在工作正常,但我建议您在 Internet 上快速搜索 java 搜索库。您的代码将看起来更加简单、可扩展和模块化,并且实现非常高效且经过良好测试。这将是您使用 Hipster 的代码:

    //HERE YOU DEFINE THE SEARCH PROBLEM
    // The maze is a 2D map, where each tile defined by 2D coordinates x and y
    // can be empty or occupied by an obstacle. We have to define de transition
    // function that tells the algorithm which are the available movements from
    // a concrete tile point.
    SearchProblem p = ProblemBuilder.create()
       .initialState(origin)
       .defineProblemWithoutActions()
       .useTransitionFunction(new StateTransitionFunction<Point>() {
          @Override
          public Iterable<Point> successorsOf(Point state) {
             // The transition function returns a collection of transitions.
             // A transition is basically a class Transition with two attributes:
             // source point (from) and destination point (to). Our source point
             // is the current point argument. We have to compute which are the
             // available movements (destination points) from the current point.
             // Class Maze has a helper method that tell us the empty points
             // (where we can move) available:
             //TODO: FILL WITH YOUR CODE GENERATING THE NEIGHBORS, FILTERING
             //THOSE WHICH ARE NOT ACCESIBLE DUE TO OBSTACLES IN YOUR MAP
            return [...]
          }
       })
       .useCostFunction(new CostFunction<Void, Point, Double>() {
          // We know now how to move (transitions) from each tile. We need to define the cost
          // of each movement. A diagonal movement (for example, from (0,0) to (1,1)) is longer
          // than a top/down/left/right movement. Although this is straightforward, if you don't
          // know why, read this http://www.policyalmanac.org/games/aStarTutorial.htm.
          // For this purpose, we define a CostFunction that computes the cost of each movement.
          // The CostFunction is an interface with two generic types: S - the state, and T - the cost
          // type. We use Points as states in this problem, and for example doubles to compute the distances:
          @Override
          public Double evaluate(Transition<Void, Point> transition) {
             Point source = transition.getFromState();
             Point destination = transition.getState();
             // The distance from the source to de destination is the euclidean
             // distance between these two points http://en.wikipedia.org/wiki/Euclidean_distance
             return source.distance(destination);
          }
       })
       .build();
    
    //HERE YOU INSTANTIATE THE ALGORITHM AND EXECUTE THE SEARCH
    //MazeSearch.printSearch(Hipster.createAStar(p).iterator(), maze);
    System.out.println(Hipster.createAStar(p).search(goal));
    

    如您所见,您只需要定义要在搜索问题中使用的组件,然后执行算法。图书馆将为您完成其余的操作。

    此外,该库是开源的并获得 Apache2 许可。您可以访问几个示例,这些示例将帮助您开始使用该库。

    在您的情况下,当您使用自定义 2D 网格时,您唯一需要调整的是转换功能,该功能会检查您的网格以过滤那些由于障碍物而无法访问的邻居。

    使用此实现的直接好处是,除了代码的可伸缩性和模块化之外,避免实例化路径中的所有节点,因为库会为您动态地执行此操作,从而减少内存并提高性能(特别是在某些情况下)巨大的网格)。

    希望我的回答对你有帮助,

    【讨论】:

      猜你喜欢
      • 2012-10-08
      • 2012-02-19
      • 2012-10-16
      • 2013-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多