【问题标题】:A* (A Star) - Algorithm | How to go back when there are walls?A* (A Star) - 算法 |有墙怎么回去?
【发布时间】:2021-02-03 17:03:20
【问题描述】:

我目前正在尝试实现 A*(A 星)- 算法。当我没有墙壁并且只需要在墙壁旁边时,我已经让它工作了。我现在的问题是,当我将起点放在墙内时,我的算法正在无限计算,因为我认为它不会倒退。你们能帮帮我吗?

这是来自节点类的代码:

int[][] mMap; // if there is a wall => 1

int[][] mAStarField;
ArrayList<AStarNode> mAStarPath;

public class AStarNode implements Comparable<AStarNode>{ 
  public int x; 
  public int y; 
  public float c;
  public AStarNode p; 
  public AStarNode(int x, int y, float c, AStarNode p) { 
    this.x = x; //X pos
    this.y = y; //Y pos
    this.c = c; //Cost to get to the node
    this.p = p; //Parent of the node
  }
  //override the compareTo method 
  public int compareTo(AStarNode node) 
  { 
    if (c == node.c) 
      return 0; 
    else if (c > node.c) 
      return 1; 
    else
      return -1; 
    } 
  
  
} 

public class Node { 
  public int x; 
  public int y; 
  public int z; 
  public int w; 
  public Node(int x, int y, int z, int w) { 
    this.x = x; 
    this.y = y;
    this.z = z; 
    this.w = w;
  }
}

这是我的 A* 算法的代码:

//Pathfinding with A*
//return path length
int updateAStar() {


  //Needed for drawing:
  //Array containing the distance to the start node (filled with max int at start)
  mAStarField = new int[mMap.length][mMap[0].length];
  //List containing the found path
  mAStarPath = new ArrayList<AStarNode>();

  for (int j = 0; j < mMap.length; j++) {
    for (int k = 0; k < mMap[0].length; k++) {
      mAStarField[j][k]=Integer.MAX_VALUE;
    }
  }

//AStarNode(x,y,c,w)
  //x X pos
  //y Y pos
  //c Cost to get to the node
  //p Parent of the node

  //List can be sorted expensive but simple by c value with
  //Collections.sort(openList); 
  ArrayList<AStarNode> openList = new  ArrayList<AStarNode>();
  ArrayList<AStarNode> closedList = new  ArrayList<AStarNode>();

  int dist = abs(mStartNode[0]-mEndNode[0])+abs(mStartNode[1]-mEndNode[1]);

  //If there is any target, that isn't on my field, add start node to list
  if (dist>0 && mMap[mStartNode[0]][mStartNode[1]] != 1 && mMap[mEndNode[0]][mEndNode[1]]!=1) {
    openList.add(new AStarNode(mStartNode[0], mStartNode[1], 0, null));
    mAStarField[mStartNode[0]][mStartNode[1]] = 0;
  }
  
// my code begins here (only everything from here on can be edited!)

  while(!openList.isEmpty())
  {
    Collections.sort(openList);
    AStarNode current = openList.get(0);
    
    if(current.x == mEndNode[0] && current.y == mEndNode[1])
    {
      return 1;
    }
    
    openList.remove(0);
    closedList.add(current);
    
    ArrayList<AStarNode> neighbors = new  ArrayList<AStarNode>();
    neighbors.add(new AStarNode(current.x - 1, current.y, current.c + 1, current));
    neighbors.add(new AStarNode(current.x + 1, current.y, current.c + 1, current));
    neighbors.add(new AStarNode(current.x, current.y - 1, current.c + 1, current));
    neighbors.add(new AStarNode(current.x, current.y + 1, current.c + 1, current));
    
    for(AStarNode n : neighbors)
    {
      if(n.x >= 0 && n.y >= 0 && n.x < mMap.length && n.y < mMap.length && mMap[n.x][n.y] != 1){
      
      float cost = estimateDistanceEnd(n.x, n.y);
      n.c = cost;
      
      if(closedList.contains(n) && cost >= n.c) continue;
      
      if(!openList.contains(n) || cost < n.c)
      {
        n.p = current;
        
        if(!openList.contains(n)){
          mAStarField[n.x][n.y] = (int) n.c;
          openList.add(n);
          Collections.sort(openList);
        }
      }
      }
    }
    
  }
  return -1;
}

int estimateDistanceEnd(int x, int y){
   return abs(x-mEndNode[0])+abs(y-mEndNode[1]); 
}

int estimateDistanceStart(AStarNode a){
   return abs(a.x-mStartNode[0])+abs(a.y-mStartNode[1]); 
}

int estimateDistance(AStarNode a, AStarNode b){
   return abs(a.x-b.x)+abs(a.y-b.y); 
}

A picture of my current path solving result

重要提示:我只能在我标记的区域内更改代码。

谢谢!

【问题讨论】:

  • it doesn't go backward 我认为在经典算法中,它会从堆栈中弹出值(先前的位置)以反转到先前的状态。如果你以不同的方式实现你的代码,你需要做一些等效的事情。
  • 使用整数数组来存储节点位置...我不赞成这个(我知道这不是你的事,别担心)。
  • 请帮我做这个测试:把这个单行语句 (if(closedList.contains(n) &amp;&amp; cost &gt;= n.c) continue;) 写成普通的 if 并在 if 内放置一个断点。如果我是对的,你将永远不会达到那个断点。通过在您的回复中标记我,让我知道测试结果。
  • @laancelot 我试过了,你是对的。它永远不会达到断点。但我不知道为什么。编辑:我认为这是因为我总是在创建一个新节点,因此列表永远不会包含新节点。但是我应该如何解决这个问题?

标签: java data-structures processing path-finding a-star


【解决方案1】:

我无法运行代码,但我对问题的根源有一点了解。看,A* 算法在寻找最佳路径时不会“往回走”。它通过计算成本较低的方式来解决它所评估的每个节点的最终路径,从而解决了寻路问题。它首先计算最简单的路线,然后如果它不起作用,它会扩大它的选项,直到它,呃,找到一条路 - 或者用完选项。

封闭列表的原则,避免对一个节点进行两次评估。正如您所猜测的,这里的问题是您在寻路算法的每次迭代中都为邻居创建新节点,从而使得正确使用封闭列表变得更加困难。

像自定义类这样的复杂对象可以通过 3 种方式进行比较:要么是同一个对象(它引用同一个指针(它是同一个 instance,它在计算机的内存)),或者无论指针指向的值都相同,或者您可以定义一个规则来比较它们。这些方法是:按引用比较、按值比较和运算符重载——尽管最后一个在 java 中是不可能的,但你可以编写一个方法来做同样的事情。

在执行closedList.contains(n) 时,您是通过引用进行比较(这是此类操作的默认设置)。由于所有节点都是动态创建的,即使它们的坐标相同,它们在内存中的地址也都不同,这就是为什么这个条件永远不会满足。

假设你不能弄乱你导师的代码,你仍然可以解决这个问题。你几乎第一次就做对了!事实上,有很多方法可以解决这个问题,由于我错过了一些上下文,我会在我建议的方法中相当简单:你将编写一个方法来从列表中获取特定节点(比如运算符重载我谈到了,但要付出最小的努力),从现在开始,我们将通过参考来工作。

首先,创建一个包含所有AStarNode 的主列表(如果您还没有,则使用该列表代替):

// my code begins here (only everything from here on can be edited!)
ArrayList<AStarNode> nodesList = new  ArrayList<AStarNode>();
for (int j = 0; j < mapWidth; j++) {
  for (int k = 0; k < mapHeight; k++) {
    nodesList.add(new AStarNode(j, k)); // I gimmicked the constructor for my own confort, you'll have to tweak this line so it fits in your code
  }
}

然后,为自己编写一个方法,该方法将根据给定的 xy 坐标从数组中返回一个节点:

AStarNode GetAStarNodeByPosition(int x, int y, ArrayList<AStarNode> list) {
  for (AStarNode m : list) {
    if (m.x == x && m.y == y) {
      return m;
    }
  }

  return null;
}

现在,您可以使用它们通过引用来比较所有节点。因此,现在您不再总是实例化新节点,而是始终通过引用从主列表中获取它们:

ArrayList<AStarNode> neighbors = new  ArrayList<AStarNode>();
neighbors.add(GetAStarNodeByPosition(current.x - 1, current.y, nodesList));
neighbors.add(GetAStarNodeByPosition(current.x + 1, current.y, nodesList));
neighbors.add(GetAStarNodeByPosition(current.x, current.y - 1, nodesList));
neighbors.add(GetAStarNodeByPosition(current.x, current.y + 1, nodesList));

另外,别忘了修正这一行:

//openList.add(new AStarNode(mStartNode[0], mStartNode[1], 0, null));
openList.add(GetAStarNodeByPosition(mStartNode[0], mStartNode[1], nodesList));

最后,如果您知道您的数组中可能有一些,请务必记住测试null。在这种情况下,如果您离迷宫边界太近,GetAStarNodeByPosition 方法可以返回 null。您可以修改添加到邻居列表的方式,以便其中没有 null,或者您可以在此行检查 null:

if(n != null && n.x >= 0 && n.y >= 0 && n.x < mMap.length && n.y < mMap.length && mMap[n.x][n.y] != 1){

老实说,我根本不会在数组中包含 null,如果您稍后再修改代码会更安全。

现在您的所有节点都将关联,您将能够克服障碍,这需要您的算法以比直线更聪明的方式进行搜索。

玩得开心!

【讨论】:

    猜你喜欢
    • 2011-03-27
    • 1970-01-01
    • 2012-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多