A* Поиск пути и множество проблем с указателями?

Я знаю, что проблемы реализации алгоритма 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);
}

При осмотре закрытые и открытые содержат правильные результаты. Итак, я уверен, что делаю что-то действительно глупое со своими указателями. Любая помощь будет очень высоко ценится. :)


person SDM    schedule 26.11.2011    source источник
comment
Интересно, сколько людей проберется через этот код?   -  person Mitch Wheat    schedule 26.11.2011
comment
Я решил опубликовать это только для справки, но суть проблемы заключается в том, что может привести к тому, что родительские указатели будут испорчены. Если я нахожусь в (300, 540) и цель (300, 570), я добавлю четыре узла: (300, 510), (270, 540), (330, 540) и (300, 570). Каждый будет указывать на (300, 540). Почему это сломается, если все, что я делаю, это устанавливаю их так, чтобы они указывали на родителя? Это кажется довольно простой задачей. :/   -  person SDM    schedule 26.11.2011
comment
Я предлагаю отладку с более коротким путем (например, длиной два). Таким образом, вы потратите меньше времени на пошаговое выполнение кода, который работает, и вам будет легче добраться до проблемы.   -  person Bill    schedule 26.11.2011
comment
Ага! Спасибо вам за помощь. Я чувствую себя довольно большим манекеном. Я полагаю, важность кадров стека теперь навсегда укоренилась в моей голове...   -  person SDM    schedule 26.11.2011


Ответы (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;
  • Insertion : O(log(n))
    • Insert a node to open_set
    • Вставьте возвращенный итератор в open_map
  • Finding the lowest score node : O(log(n))
    • Pop the first node from open_set
    • Вытащите соответствующий статус из open_map
  • Updating - If the neighbor is in open_map, : O(log(n))
    • Pop the node from open_set using the iterator in open_map
    • Обновить стоимость
    • Вставить в open_set
    • Обновите open_map, чтобы указать повторно вставленный итератор

Огромное количество элементов динамически выделяется при вставке или удалении. Может помочь внедрение распределителя пула для контейнеров.

person Inbae Jeong    schedule 26.11.2011
comment
Спасибо за помощь! Не могли бы вы, если не возражаете, помочь мне еще немного? Как я могу заставить мои открытые/закрытые контейнеры использовать преимущество в производительности во время обхода проверки условий? Я пользуюсь этим, когда открываю лучший узел, но я не вижу, как это можно использовать в NodeSuccessors. Какие предположения я могу сделать? - person SDM; 26.11.2011
comment
Хм, карта определенно выглядит как наиболее эффективная реализация. Я добавил в бинарный поиск открытый, но закрытый по-прежнему O (n). Я должен буду попробовать это - спасибо! - person SDM; 26.11.2011

Другие уже упоминали о вероятной проблеме, но я объясню более подробно.

Когда вы создаете объекты в стеке (не используя new) и помещаете их в контейнеры, вы создаете копии исходных объектов, которые вы создали. Таким образом, любые указатели, которые у вас были на оригинал, будут продолжать указывать на оригинал, который в какой-то момент будет уничтожен, и не будут указывать на копию в контейнере. Кроме того, на самом деле небезопасно брать адрес объекта в контейнере, поскольку некоторые контейнеры могут перемещать объекты при добавлении или удалении элементов.

Есть два подхода к решению этого.

  1. не используйте указатели, а используйте значения индекса, такие как позиция в векторе или какое-либо значение ключа с контейнером, таким как std::map.
  2. Выделите все объекты с помощью new и сохраните указатели в контейнерах. (Может вызвать головную боль при управлении памятью.)

Ссылки BTW в основном являются указателями и имеют те же проблемы.

person Eelke    schedule 26.11.2011
comment
Хотел бы я принять несколько ответов. В итоге я решил проблему несколько нестандартным способом. Вместо того, чтобы динамически выделять все, динамически выделяется только каждый parentNode. Затем я передаю этот указатель. Я также сохраняю каждый из динамически выделяемых родительских узлов в векторе. Это выполняется и освобождается в конце выполнения пространства имен. Это шатко, но утечек памяти нет. Я обязательно прогоню и перепишу все пространство имен, но я, безусловно, рад, что это работает. :) - person SDM; 26.11.2011