C 中的探路者

A* Pathfinder in C++

本文关键字:探路者      更新时间:2023-10-16

我正在尝试编写一个*探路者学习C 并在特定的错误中挣扎。

这些是程序的功能/假设:

  • 该路径包含无法传递的"墙"
  • 所有路径的成本相同(1(
  • 允许对角线运动
  • 只有一个目标

这是我的代码:

#include <algorithm>
#include <iostream>
#include <vector>
#include <tuple>
#include <cmath>
#include <set>

using Graph = std::vector<std::vector<int>>;
using Position = std::tuple<int, int>;
class Node;
using Nodes = std::vector<Node>;

constexpr int WALL_BLOCK = 1;
constexpr int USER_BLOCK = 3;
constexpr int GOAL_BLOCK = 2;

class Node {
    private:
        int row;
        int column;
        Node* parent = nullptr;
        int f = 0;
        int g = 0;
        int h = 0;
    public:
        Node() : row(-1), column(-1) {}
        Node(int row, int column) : row(row), column(column)  {}
        Node(int row, int column, Node *parent) : row(row), column(column) {
            if (this->parent != nullptr && *this->parent == *this) {
                throw "Node cannot be parented to itself";
            }
            this->parent = parent;
        }
        ~Node() {}
        Node* get_parent() const { return this->parent; }
        int get_f() const { return this->f; }
        int get_g() const { return this->g; }
        int get_h() const { return this->h; }
        Position get_position() const { return std::make_tuple(row, column); }
        void set_f(int f) { this->f = f; }
        void set_g(int g) { this->g = g; }
        void set_h(int h) { this->h = h; }
        bool operator==(Node const &node) const {
            return this->row == node.row && this->column == node.column;
        }
        bool operator<(Node const &node) const {
            return this->row < node.row && this->column < node.column;
        }
        friend std::ostream& operator<<(std::ostream& os, Node const &node);
};

std::ostream& operator<<(std::ostream& os, Node const &node) {
    auto row = node.row;
    auto column = node.column;
    os << "Node(("
        << row << ", "
        << column << "), "
        << node.get_g() << ", "
        << node.get_h() << ", "
        ;
    Node* parent = node.get_parent();
    if (parent != nullptr) {
        os << parent;
    } else {
        os << "nullptr";
    }
    os << ")";
    return os;
}

inline bool is_walkable(Node const &node, Graph const &graph) {
    int column;
    int row;
    std::tie(row, column) = node.get_position();
    return graph[row][column] != WALL_BLOCK;
}

Position get_first_index(Graph const &graph, int block);

Position get_end(Graph const &graph) {
    return get_first_index(graph, GOAL_BLOCK);
}

Position get_first_index(Graph const &graph, int block) {
    for (int row = 0, max = graph.size(); row < max; ++row) {
        auto line = graph[row];
        auto found = std::find(line.begin(), line.end(), block);
        if (found != line.end()) {
            return std::make_tuple(row, found - line.begin());
        }
    }
    return {-1, -1};
}

inline int get_h(Node const &node, Node const &reference) {
    auto node_position = node.get_position();
    auto reference_position = reference.get_position();
    auto node_position_row = std::get<0>(node_position);
    auto node_position_column = std::get<1>(node_position);
    auto reference_position_row = std::get<0>(reference_position);
    auto reference_position_column = std::get<1>(reference_position);
    return (
        std::pow((node_position_row - reference_position_row), 2) +
        std::pow((node_position_column - reference_position_column), 2)
    );
}

Position get_start(Graph const &graph) {
    return get_first_index(graph, USER_BLOCK);
}

Nodes get_children(Node &node, Graph const &graph) {
    Nodes children;
    int row;
    int column;
    std::tie(row, column) = node.get_position();
    for (int row_offset = -1; row_offset < 2; ++row_offset) {
        for (int column_offset = -1; column_offset < 2; ++column_offset) {
            if (row_offset == 0 and column_offset == 0) {
                // (0, 0) will always be `node`. We can't let `node` be a child
                // of itself so we have to `continue` here
                //
                continue;
            }
            Graph::size_type node_row = row + row_offset;
            Graph::size_type node_column = column + column_offset;
            if (node_row >= graph.size()) {
                continue;
            }
            if (node_column >= graph[node_row].size()) {
                continue;
            }
            children.push_back({
                static_cast<int>(node_row),
                static_cast<int>(node_column),
                &node
            });
        }
    }
    return children;
}

Nodes trace(Node const &node) {
    Node* parent = node.get_parent();
    Nodes path;
    std::set<Node> seen;
    while (parent != nullptr) {
        auto parent_node = *parent;
        if (std::find(seen.begin(), seen.end(), parent_node) != seen.end()) {
            // If this happens, `parent` is already in `path`. To avoid
            // a cyclic loop from happening, we will break out, instead.
            //
            break;
        }
        seen.insert(parent_node);
        path.push_back(parent_node);
        parent = parent->get_parent();
    }
    return path;
}

Nodes a_star(Graph const &graph, Node const &user, Node const &goal) {
    Nodes open_list {user};
    Nodes closed_list;
    while (open_list.size() != 0) {
        Node current_node = open_list[0];
        unsigned int current_index = 0;
        for (int index = 0, max = open_list.size(); index < max; ++index) {
            auto node = open_list[index];
            if (node.get_f() < current_node.get_f()) {
                current_node = node;
                current_index = index;
            }
        }
        if (current_node == goal) {
            auto path = trace(current_node);
            std::reverse(path.begin(), path.end());
            return path;
        }
        open_list.erase(open_list.begin() + current_index);
        closed_list.push_back(current_node);
        auto children = get_children(current_node, graph);
        for (auto &child : children) {
            if (std::find(closed_list.begin(), closed_list.end(), child) != closed_list.end()) {
                continue;
            }
            if (!is_walkable(child, graph)) {
                continue;
            }
            child.set_g(child.get_parent()->get_g() + 1);
            child.set_h(get_h(child, goal));
            child.set_f(child.get_g() + child.get_h());
            bool add = true;
            for (auto const &open : open_list) {
                if (child == open && child.get_g() > open.get_g()) {
                    add = false;
                    break;
                }
            }
            if (add) {
                open_list.push_back(child);
            }
        }
    }
    return {};
}

int main() {
    Graph graph = {
        {0, 0, 0, 0, 1, 0, 0},
        {0, 3, 0, 0, 1, 0, 2},
        {0, 0, 0, 0, 1, 0, 0},
        {0, 0, 0, 0, 1, 0, 0},
        {0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 1, 0, 0},
        {0, 0, 0, 0, 1, 0, 0},
    };
    int start_row = 0;
    int start_column = 0;
    std::tie(start_row, start_column) = get_start(graph);
    int end_row = 0;
    int end_column = 0;
    std::tie(end_row, end_column) = get_end(graph);
    auto user = Node(start_row, start_column);
    auto goal = Node(end_row, end_column);
    std::cout << user << std::endl;
    std::cout << goal << std::endl;
    auto nodes = a_star(graph, user, goal);
    std::cout << "Path: [";
    for (auto const &node : nodes) {
        std::cout << "("
            << std::get<0>(node.get_position())
            << ", "
            << std::get<1>(node.get_position())
            << "), ";
    }
    std::cout << "]" << std::endl;
    return 0;
}

当我在计算机上运行此功能时,我会得到此输出:

Node((1, 1), 0, 0, nullptr)
Node((1, 6), 0, 0, nullptr)
Path: [(1, 6), ]

Path:应该返回一个从(1, 1)到目标的完整列表,(1, 6),而不是[(1, 1), (1, 2), (2, 3), (3, 3), (4, 4), (3, 5), (2, 6), (1, 6), ]。我们有[(1, 6), ]。我认为这样做的原因是因为current_node包含自己作为父母。因此,trace提早断裂,以避免循环循环。这就是为什么输出中只有一个。奇怪的是,任何节点都会自身作为父母,因为如果发生这种情况,则构造函数会引发异常。

实际上在我的计算机上,每个节点的每个父母都是相同的地址,0x7fffffff9140,不应该发生。

我唯一的猜测是current_node是在当时的循环中初始化的,因此即使节点位置/f/g/h正在发生变化,但内存中的指针从未做到。因此,所有节点最终都会得到相同的父母。

可能是问题也可能不是问题,但无论哪种方式,我都不确定如何修复它。任何意见将是有益的。谢谢!

这个构造函数的主体完全混乱,您正在从成员this->parent读取,该构建器始终将其初始化为nullptr( brace-or-equal-intialializer (。

 Node(int row, int column, Node *parent) : row(row), column(column)
 {
    if (this->parent != nullptr && *this->parent == *this) {
        throw "Node cannot be parented to itself";
    }
    this->parent = parent;
}

第二个主要问题,这是通过对对象current_node的参考,其寿命不足:

auto children = get_children(current_node, graph);

当您从列表中读取任何"孩子"时,其parent成员指向的节点已经被销毁。

closed_list中的副本将生存,但由于std::vector的迭代器无效规则,也不能用来存储parent指针。如果将closed_list更改为std::list<Node>,则元素地址将是稳定的。

但是当a_star返回所有指向的对象时,无论如何指向parent指示器都会死亡。trace(current_node)产生了一组节点的集合,其parent成员指向集合外。

您最好的选择可能是在整个graph实例上进行足够的节点对象,使用索引代替指针作为父级,然后更新parent成员,而不是在构造过程中进行设置。与指针不同,索引值无论复制数据结构多少次,索引值仍然有意义。

(也是字符串文字使异常对象变得不好(

相关文章: