Устранение препятствия, которое дает лучший путь с карты после прохождения A * - PullRequest
8 голосов
/ 22 марта 2010

Я пересекаю лабиринт 16x16, используя свою собственную реализацию A *.

Все хорошо. Однако после прохождения я хотел бы узнать, какая стена даст мне альтернативный путь best . Помимо удаления каждого блока и повторного запуска A * в лабиринте, что является более умным и элегантным решением?

Я думал дать каждому стенному узлу (игнорируемому A *) предварительное F-значение и изменить структуру узла, чтобы также иметь список размером n node *tentative_parent, где n - количество стен в лабиринте , Может ли это быть жизнеспособным?

Ответы [ 2 ]

3 голосов
/ 22 марта 2010

Когда вы добавляете узел в список узлов для рассмотрения, также добавьте флаг, если путь через этот узел уже прошел через стену.

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== определен так, чтобы также учитывать, достиг ли путь еще стены. Это позволяет нам рассматривать узел дважды, если это необходимо, когда мы смотрим в закрытом множестве, чтобы увидеть, не встретили ли мы уже этот узел. Это случай в центральном коридоре в примере лабиринта из источника ниже.

Части кода, помеченные #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;
}
1 голос
/ 22 марта 2010

Поиск подходящих областей для удаления стены:

На всем протяжении вашего первоначального найденного пути A * сохраните предыдущее расстояние , и всякий раз, когда предыдущее расстояние больше текущего расстояния, отметьте предыдущий узел как имеющий потенциал для удаление стены.

Я утверждаю, что это отразит случаи наибольшего воздействия:

Пример 1:

    012
   *****
 0 *R*G*
 1 * * *
 2 * * *
 3 * * *
 4 * * *
 5 * * *
 6 *   *
   *****

Где:

R (0,0) - ваш бегун с видом кролика G (2,0) - цель

В этом случае мы начинаем с (0,0) с расстояния 2. Следующий доступный ход - (0,1) с расстоянием 2,23 (квадратный корень из 5). Ваше расстояние только выросло, так что ваше предыдущее местоположение могло разрушить стену.

Пример 2:

    0123456
   *********
 0 *R*     *
 1 ** *    *
 2 * * *   *
 3 *  * *  *
 4 *   * * *
 5 *    * **
 6 *     *G*
   *********

Начало: (0,0) Конец: (6,6) A * курс: (0,0), (1,1), (2,2), (3,3), (4,4), (5,5), (6,6) Расстояния: 8,5, 7,1, 5,7, 4,2, 2,8, 1,4 (или квадратный корень из 72, 50, 32, 18, 8, 2) Нет оптимальной стены для удаления.

Определение стены для удаления:

Проведите линию между вашим отмеченным узлом и вашим целевым узлом. Первая стена вдоль этой линии (ближайшая к отмеченному узлу) опускается вниз. Дайте ему немного пуха, чтобы позволить убрать стену по прямой диагонали, которая ударит только по углам. Сравните ваши альтернативные пути, чтобы найти самый короткий.

...