草庐IT

c++ - 在 A* 遍历后从 map 中移除产生最佳路径的障碍

coder 2023-11-16 原文

我使用自己的 A* 实现遍历了一个 16x16 的迷宫。

一切顺利。然而,在遍历之后,我想找出哪堵墙会给我最佳替代路径。除了移除每个 block 并在迷宫上重新运行 A*,还有什么更聪明、更优雅的解决方案?

我想给每个墙节点(被 A* 忽略)一个暂定的 F 值,并更改节点结构以也有一个 n 大小的 node *tentative_parent 列表,其中 n 是迷宫中的墙数。这可行吗?

最佳答案

当您将一个节点添加到要考虑的节点列表时,还要添加一个标志,说明通过该节点的路径是否已经穿过墙。

possibleNode.heuristic = currentNode.distance + heuristicEstimate(possibleNode)
possibleNode.goneThroughWall = currentNode.goneThroughWall || possibleNode.isAWall
allPossiblePaths.insert(possibleNode) // Priority queue sorted on Node.heuristic

然后在考虑来自节点的可能路径时,如果它没有穿过墙,则将相邻的墙视为公平路径。

foreach neighborNode in neighbors(someNode)
    if !(someNode.goneThroughWall && neighborNode.isAWall)
        addToPossiblePaths(neighborNode)

您已经必须保持从起始节点到正在处理的当前节点的距离,并且它会使用您已有的内容。

完整的概念证明:

我们看到 operator== 被定义为还考虑路径是否已经碰壁。这允许我们在需要时考虑 a 节点两次,当我们在封闭集中查看是否已经遇到该节点时。下面源代码中示例迷宫的中央走廊就是这种情况。

#ifdef I_AM_ALLOWED_TO_GO_THROUGH_A_WALL 标记的代码部分显示了增强普通 A* 算法以使其能够穿过一堵墙所需的部分。

#include <cassert>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <set>
#include <vector>

#define I_AM_ALLOWED_TO_GO_THROUGH_A_WALL

const int width  = 5;
const int height = 5;

// Define maze
const int maze[height][width] =
  { { 0, 1, 1, 0, 0 },
    { 0, 1, 0, 1, 0 },
    { 0, 1, 0, 1, 0 },
    { 0, 1, 0, 1, 0 },
    { 0, 0, 0, 0, 0 } };

// Square represents the actual structure of the maze
// Here, we only care about where it is, and whether it is a wall
struct Square {
  Square(int _x, int _y)
    : x(_x), y(_y) {}

  bool operator==(const Square& rhs) const {
    return x == rhs.x && y == rhs.y;
  }

  bool isAWall() const {
    return maze[y][x];
  }

  int distance(const Square& goal) const {
    return std::abs(x - goal.x) + std::abs(y - goal.y);
  }

  int x;
  int y;
};

// Define start and end of maze
const Square goalSquare(3, 0);
const Square startSquare(0, 0);

// A PathNode describes the A* algorithm's path through the maze
// It keeps track of its associated Square
//                   its previous PathNode in the path
//                   the length of the path up to the current PathNode
//                   whether the path so far has goneThroughWall
struct PathNode {
  explicit PathNode(const Square& s, int length = 0, const PathNode* _prev = NULL)
    : square(s),
      prev(_prev),
      pathLength(length) {
    heuristic = pathLength + square.distance(goalSquare);

#ifdef I_AM_ALLOWED_TO_GO_THROUGH_A_WALL
    if(prev)
      goneThroughWall = prev->goneThroughWall || square.isAWall();
    else
      goneThroughWall = false;

    // Sanity check, these should be the same
    assert(goneThroughWall == hasGoneThroughAWall());
#endif
  }

  bool operator<(const PathNode& rhs) const {
    return heuristic < rhs.heuristic;
  }

  // This is very important. When examining the closed set to see
  // if we've already evaulated this node, we want to differentiate
  // from whether we got to that node using a path through a wall.
  //
  // This is especially important in the given example maze.
  // Without this differentiation, paths going up column 3 will hit
  // old, closed paths going through the walls in column 2, and not
  // find the best path.
  bool operator==(const PathNode& rhs) const {
    return square == rhs.square
#ifdef I_AM_ALLOWED_TO_GO_THROUGH_A_WALL
      && goneThroughWall == rhs.goneThroughWall
#endif
      ;
  }

  bool weakEquals(const PathNode& rhs) const {
    return square == rhs.square;
  }

  bool weakEquals(const Square& rhs) const {
    return square == rhs;
  }

  // Sanity checker
  bool hasGoneThroughAWall() const {
    if(square.isAWall()) return true;

    const PathNode* p = prev;
    while(p) {
      if(p->square.isAWall())
        return true;
      p = p->prev;
    }

    return false;
  }

  Square square;
  const PathNode* prev;
  int heuristic, pathLength;
#ifdef I_AM_ALLOWED_TO_GO_THROUGH_A_WALL
  bool goneThroughWall;
#endif
};

std::ostream& operator<<(std::ostream& ostr, const PathNode& p) {
  ostr << "(" << p.square.x << ", " << p.square.y << ")";
  if(p.square.isAWall())
    ostr << " <- Wall!";
  return ostr;
}

std::vector<Square> getNeighbors(const Square& s) {
  std::vector<Square> neighbors;

  if(s.x > 0)
    neighbors.push_back(Square(s.x - 1, s.y));
  if(s.x < width - 1)
    neighbors.push_back(Square(s.x + 1, s.y));
  if(s.y > 0)
    neighbors.push_back(Square(s.x, s.y - 1));
  if(s.y < height - 1)
    neighbors.push_back(Square(s.x, s.y + 1));

  return neighbors;
}

void printList(const PathNode* p, int i = 0) {
  if(p) {
    printList(p->prev, i + 1);
    std::cout << *p << std::endl;
  } else {
    std::cout << "Length: " << i << std::endl;
  }
}

typedef std::multiset<PathNode> Set;

int main(int argc, char** argv) {
  // Print out maze
  for(int j = 0; j < height; ++j) {
    for(int i = 0; i < width; ++i) {
      std::cout << " " << maze[j][i];
    }
    std::cout << std::endl;
  }
  std::cout << std::endl;

  Set closedSet;
  Set openSet;
  openSet.insert(PathNode(startSquare)); // Start node (defined at line ~42)

  int processedCount = 0;
  while(!openSet.empty()) {
    Set::iterator currentNode = openSet.begin();
    ++processedCount;

    // We've found the goal, so print solution.
    if(currentNode->weakEquals(goalSquare)) {
      std::cout << "Processed nodes: " << processedCount << std::endl;
      printList(&*currentNode);
      return 0;
    }

    {
      // Move from open set to closed set
      Set::iterator del = currentNode;
      currentNode = closedSet.insert(*currentNode);
      openSet.erase(del);
    }

    std::vector<Square> neighbors = getNeighbors(currentNode->square);
    for(unsigned int i = 0; i < neighbors.size(); ++i) {
      PathNode currentNeighbor(neighbors[i], currentNode->pathLength + 1, &*currentNode);

      // Skip if it is 2nd wall
      if(
#ifdef I_AM_ALLOWED_TO_GO_THROUGH_A_WALL
        currentNode->goneThroughWall &&
#endif
        currentNeighbor.square.isAWall()
      )
        continue;

      // Skip if it is already in the closed set
      // Note: Using std::find here instead of std::multiset::find because
      // std::multiset::find uses the definition !(a < b) && !(b < a) for
      // searching. I want it to use my overloaded a == b.
      if(find(closedSet.begin(), closedSet.end(), currentNeighbor) != closedSet.end())
        continue;

      // Skip if we were just there
      const PathNode* p = currentNode->prev;
      if(p && p->weakEquals(currentNeighbor))
        continue;

      // See if there is a better alternative in the open set
      // Note: Using std::find here instead of std::multiset::find. See above.
      Set::iterator contender = find(openSet.begin(), openSet.end(), currentNeighbor);
      if(contender == openSet.end())
        openSet.insert(currentNeighbor);
      else if(currentNeighbor.pathLength < contender->pathLength) {
        // We've found a better alternative, so replace
        openSet.erase(contender);
        openSet.insert(currentNeighbor);
      }
    }
  }

  std::cout << "Failure." << std::endl;
  return 1;
}

关于c++ - 在 A* 遍历后从 map 中移除产生最佳路径的障碍,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2489672/

有关c++ - 在 A* 遍历后从 map 中移除产生最佳路径的障碍的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  5. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  6. ruby-on-rails - Rails - 使用/自定义 URL : '/dashboard' 指定根路径 - 2

    如何使此根路径转到:“/dashboard”而不仅仅是http://example.com?root:to=>'dashboard#index',:constraints=>lambda{|req|!req.session[:user_id].blank?} 最佳答案 您可以通过以下方式实现:root:to=>redirect('/dashboard')match'/dashboard',:to=>"dashboard#index",:constraints=>lambda{|req|!req.session[:user_id].b

  7. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  8. ruby - Chef Ruby 遍历 .erb 模板文件中的属性 - 2

    所以这可能有点令人困惑,但请耐心等待。简而言之,我想遍历具有特定键值的所有属性,然后如果值不为空,则将它们插入到模板中。这是我的代码:属性:#===DefaultfileConfigurations#default['elasticsearch']['default']['ES_USER']=''default['elasticsearch']['default']['ES_GROUP']=''default['elasticsearch']['default']['ES_HEAP_SIZE']=''default['elasticsearch']['default']['MAX_OP

  9. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  10. ruby - 如何根据长度将路径数组转换为嵌套数组或散列 - 2

    我需要根据字符串路径的长度将字符串路径数组转换为符号、哈希和数组的数组给定以下数组:array=["info","services","about/company","about/history/part1","about/history/part2"]我想生成以下输出,对不同级别进行分组,根据级别的结构混合使用符号和对象。产生以下输出:[:info,:services,about:[:company,history:[:part1,:part2]]]#altsyntax[:info,:services,{:about=>[:company,{:history=>[:part1,:pa

随机推荐