寻路问题。返回奇怪的路径

时间:2014-07-18 14:56:21

标签: java libgdx path-finding a-star

我一直在阅读一些文章来学习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;
        }

    }

}

没有评论 http://pastebin.com/rSv7pUrB

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

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

2 个答案:

答案 0 :(得分:1)

您的代码存在以下问题:

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

    calcHeuristic(node);
    

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

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

    执行此操作
    open.remove(node);
    // ... update totalDistance
    open.add(node);
    
  3. 你对于一般A *来说太早终止(但这不是问题,因为totalDistance等于实际距离,对于目标的扩展邻居 IF 你使用启发式;这里距离实际距离因sqrt(2)1而异。一般来说,最后一步的距离启发式可能是任意不好的(这里很糟糕,请参阅(1.))并且你只能确定找到了真正的解决方案,如果你将算法运行到扩展的程度目标节点。

答案 1 :(得分:0)

Estimated 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网格时,您唯一需要调整的是过渡功能,它会检查您的网格以过滤因障碍而无法访问的邻居。

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

我希望我的答案有所帮助,