A *寻路和大量指针问题?

时间:2011-11-26 05:37:04

标签: c++ path-finding a-star

我知道A *算法实现问题在stackoverflow上比较常见(我经历了很多其他帖子)。在过去的几天里,我一直在努力实现一个简单的C ++ A *系统。我只允许在四个方向上移动(没有对角线检查),所以这应该是一个特别简单的任务(这也是为什么我只有一个启发式作为我的成本)。此外,我已逐步完成代码,对于所有起始位置,对象能够成功找到目标的有效路径。然而,问题是我无法退回父指针并存储路径/移动序列。我正在使用引用,所以我不太确定为什么指针赋值存在问题。我在一些简短的示例路径中查看了代码在纸上所做的“思考”,我不明白我做错了什么。循环遍历父指针,最终应该为NULL无限打印目标的posY。

这是我的头文件:

    //to access a priority queue's underlying container, you must extend functionality
    template <class T, class S, class C>
        S& Container(std::priority_queue<T, S, C>& q) {
            struct HackedQueue : private std::priority_queue<T, S, C> {
                static S& Container(std::priority_queue<T, S, C>& q) {
                    return q.*&HackedQueue::c;
                }
            };
        return HackedQueue::Container(q);
    }
    //different enough from a "Grid" to be a new object
    typedef struct Node{
        int cost, posX, posY;
        Node* parent;
        Node(int cost = 0, int posX = 0, int posY = 0, Node* parent = 0) : cost(cost), posX(posX), posY(posY), parent(parent) {}
        //overloading operators will make the cpp file easier to read
        bool operator==(const Node& rhs){
            return (posX == rhs.posX && posY == rhs.posY);
        }
        bool operator<(const Node& rhs){
            return (posX == rhs.posX && posY == rhs.posY && cost < rhs.cost);
        }
    } Node;
    typedef struct NodeCompare{
        //compare n1 against a node n2
        bool operator()(const Node& n1, const Node& n2){
            //if n2 has a lower cost, it has a higher priority
            return n2.cost < n1.cost;
        }
    } NodeCompare;
    //a list is essentially a linked list, a vector is a robust array; if you do not need random access, lists are faster than vectors
    //we need random access for the priority queue, because it will constantly be resorted
    bool PathSearch(std::list<Node>& path, const World& World, const WorldObj& obj, const Node& target); //path is our output list
    void NodeSuccessors(std::priority_queue<Node, std::vector<Node>, NodeCompare>& open, std::list<Node>& closed, Node& parentNode, const World& World, const Node& target);
    int Heuristic(int posX, int posY, const Node& target);
}

而且,这是我的实施:

bool PathSearch(std::list<Node>& path, const World& World, const WorldObj& obj, const Node& target){
    std::priority_queue<Node, std::vector<Node>, NodeCompare> open;
    std::list<Node> closed;
    //put our starting position into our queue of nodes to inspect
    Node start(Heuristic(obj.GetposX(), obj.GetposY(), target), obj.GetposX(), obj.GetposY());
    open.push(start);
    while(!open.empty()){
        Node bestNode = open.top(); //has the lowest score by definition
        open.pop();
        if(bestNode == target){ //made it to the target
            Node* ptrNode = &bestNode;
            while(ptrNode){ //this is where we would reconstruct the path
                std::cout<<ptrNode->posY<<std::endl;
                ptrNode = ptrNode->parent;
            }
            return 1;
        }
        NodeSuccessors(open, closed, bestNode, World, target); //look at the Node's neighbors
        closed.push_back(bestNode);
    }
    return 0; //no path found
}
//check for towers and check for boundaries; if they exist, skip the creation of that node
//pathfinding works for up, down, left, right, but not diagonal movement
void NodeSuccessors(std::priority_queue<Node, std::vector<Node>, NodeCompare>& open, std::list<Node>& closed, Node& parentNode, const World& World, const Node& target){
    std::vector<Node>& openVec = Container(open);
    int offsetX, offsetY;
    bool skip;
    for(int i = 0; i < 4; ++i){
        offsetX = 0; offsetY = 0;
        skip = 0;
        if(i == 0) offsetY = -30; //up
        else if(i == 1) offsetY = 30; //down
        else if(i == 2) offsetX = -30; //left
        else if(i == 3) offsetX = 30; //right
        //add check for out of boundaries or in an occupied space
        Node newNode(parentNode.cost + Heuristic((parentNode.posX + offsetX), (parentNode.posY + offsetY), target), parentNode.posX + offsetX, parentNode.posY + offsetY, &parentNode);
        //if the Node already exists on open or closed with a lower score, we can skip to the next neighbor
        //if the Node already exists on open or closed with a higher score, then we need to remove it
        //the erasing is somewhat expensive, but simplifies the implementation
        for(std::list<Node>::iterator itr = closed.begin(); itr != closed.end(); ++itr){
            if(*itr < newNode){
                skip = 1;
                break;
            }
            else if(*itr == newNode){
                closed.erase(itr);
                break;
            }
        }
        if(skip) continue;
        for(std::vector<Node>::iterator itr = openVec.begin(); itr != openVec.end(); ++itr){
            if(*itr < newNode){
                skip = 1;
                break;
            }
            else if(*itr == newNode){
                openVec.erase(itr);
                break;
            }
        }
        if(skip) continue;
        open.push(newNode);
    }
}
int Heuristic(int posX, int posY, const Node& target){
    return abs(posX - target.posX) + abs(posY - target.posY);
}

通过检查,关闭和打开包含正确的结果。所以,我确信我正在用我的指针做一些非常愚蠢的事情。任何帮助将非常感谢。 :)

2 个答案:

答案 0 :(得分:2)

问题是您在PathSearch()的堆栈上创建了一个实例,并将其地址传递给NodeSuccessors()

这与您的问题无关,但您的算法存在性能问题。优先级队列是一个很好的选择,因为优先级队列在寻找具有最低分数的节点时具有O(1)并且在插入节点时具有O(log(n))。但是,您的算法在查找打开和关闭的节点时有O(n)。如果你还维护节点的顺序,那么你可以在O(log(n))中找到一个节点,性能会好得多。


我不记得确切,但这是我用过的简短结构。

struct status {}; // represents the position, less-than comparable

struct node {
    status s;
    cost g;
    cost h;
    status parent;

    cost score() const { return g + h; }
};

struct node_comparator {
    bool operator(const node& x, const node& y) const { return x.score() < y.score(); }
};

typedef std::multiset<node, node_comparator> open_set;
// should be multiset since two or more nodes can have the same score

typedef std::map<Status, typename open_set::iterator> open_map;
  • 插入:O(log(n))
    • 将节点插入open_set
    • 将返回的迭代器插入open_map
  • 查找得分最低的节点:O(log(n))
    • 从open_set
    • 弹出第一个节点
    • 从open_map中弹出相应的状态
  • 更新 - 如果邻居在open_map中,则:O(log(n))
    • 使用open_map
    • 中的迭代器从open_set弹出节点
    • 更新费用
    • 插入open_set
    • 更新open_map以指向重新插入的迭代器

插入或删除时会动态分配大量元素。为容器采用池分配器可能会有所帮助。

答案 1 :(得分:2)

其他人已经提到了可能存在的问题,但我会更详细地解释。

当您在堆栈上创建对象(不使用new)并将它们放入容器时,您将创建原始对象的副本。因此,您对原始指针的任何指针都会指向原始的,这些指针在某个时刻被销毁,并且不会指向容器中的副本。另外,在容器中获取对象的地址并不是真的可以保存,因为一些容器可以在添加或删除元素时移动对象。

有两种方法可以解决这个问题。

  1. 不要使用指针,而是使用索引值,例如向量中的位置或带有std :: map等容器的某个键值。
  2. 使用new分配所有对象并将指针存储在容器中。 (可能会给内存管理带来一些麻烦。)
  3. BTW引用基本上都是指针,并且具有相同的问题。