【问题标题】:A* pathfinding - Slow with huge open mapsA* 寻路 - 巨大的开放地图缓慢
【发布时间】:2016-12-07 18:19:10
【问题描述】:

我已经实现了 A* 寻路,它完美地适用于较小的网格。但是,当地图变大并且不再是迷宫结构时,如下图所示,算法会变得越来越慢。

根据 A* 的定义,我使用的是开放列表和封闭列表。打开列表是使用std::set 实现的。封闭列表是使用 Qt 的QSet 实现的。 QSet 是 Qt 对 std::unordered_list 的实现。

在分析我的应用程序后,我注意到std::set 的树的重新平衡是最昂贵的操作。当在两个不同的地图中运行算法时,这一点很明显,下面显示的一个具有较大的开放列表大小,而另一个类似迷宫的地图具有较小的开放列表大小。

在类似迷宫的地图中,我的打开列表的大小会在 20 到 120 个节点之间波动。开放的地图慢慢增长到2000多个节点。

所以我的问题是,是否有任何方法可以减小打开列表的大小?

我尝试了以下方法:

  • 将打开列表更改为std::priority_queue:我无法执行此操作,因为我需要检查打开列表以查看它是否已包含该元素。如果我错了,请纠正我,但priority_queue不会遇到同样的重新平衡问题吗?

  • 使用更高的启发式权重:这并没有解决问题,打开列表中节点的数量级仍然相同。

  • 剪切打开列表中的节点:这会导致运行速度更快,但通常会导致找不到路径。最初我认为这会起作用,因为我只会修剪具有更高 F(启发式 + 移动)成本的值,这将变得无关紧要。这个假设被证明是错误的。

提前致谢。

EDIT1: 添加了一些代码进行说明。

std::shared_ptr<Node> Pathfinding::findPath(float heuristicWeight) {
    int i = 0;
    while (!m_sOpen.empty()) {
        ++i;
        std::shared_ptr<Node> current = *m_sOpen.begin();
        m_sOpen.erase(current);
        m_sClosed.insert(*current);
        if (updateNeighbours(current, heuristicWeight)) {
            return std::make_shared<Node>(*m_sClosed.find(*m_nEnd));
        }
        if (i % 100 == 0) {
            qInfo() << "Sizes: " << i << " open_size= " << m_sOpen.size() << " & closed_size= " << m_sClosed.size();
        }
    }
    return NULL;
}

bool Pathfinding::updateNeighbours(std::shared_ptr<Node> current, float heuristicWeight) {
    int maxRows = wm.getRows(); // Rows in map
    int maxCols = wm.getCols(); // Cols in map
    for (int x = clamp((current->getX()-1),0,maxCols-1); x <= clamp((current->getX()+1),0,maxCols-1); ++x) {
        for (int y = clamp((current->getY()-1),0,maxRows-1); y <= clamp((current->getY()+1),0,maxRows-1); ++y) {
            bool exists = false;
            Node n = Node(x,y); // Node to compare against and insert if nessecary.
            // Tile contains information about the location in the grid.
            Tile * t = wm.m_tTiles[(x)+(maxCols * y)].get();
            if (t->getValue() != INFINITY) { // Tile is not a wall.
                for (std::set<std::shared_ptr<Node>>::iterator it = m_sOpen.begin(); it != m_sOpen.end(); ++it) {
                    if (**it == n) {
                        exists = true;
                        if ((*it)->getF() > (current->getG() + moveCost(*it,current)) + (*it)->getH()) {
                            (*it)->setG(current->getG() + moveCost(*it,current));
                            (*it)->setParent(current);
                        }
                        break;
                    }
                }
                bool exists_closed = (m_sClosed.find(n) != m_sClosed.end());
                if (!exists && !exists_closed) {
                    std::shared_ptr<Node> sN = std::make_shared<Node>(n);
                    sN->setParent(current);
                    sN->setG(current->getG() + moveCost(sN,current));
                    sN->setH(manhattenCost(sN,m_nEnd)*heuristicWeight);
                    if (sN->getH() == 0) { m_sClosed.insert(*sN); return true; }
                    else m_sOpen.insert(sN);
                }

            }
        }
    }
    return false;
}

【问题讨论】:

  • 非常有趣,您能否在您的问题中发布一些代码,以便我们看看您的算法。这里没有代码的任何建议都只是理论上的解决方案。
  • 好的,我会用一些代码来编辑。
  • 跳转点搜索更适合开放空间。此外,您可以使用堆加上从坐标到堆中索引的映射作为打开列表,但是由于您必须重新实现堆,因此编写代码很烦人。
  • 在插入和擦除时重新平衡树通常会占用您在使用 std::set 和 std::map 的算法中的执行时间。
  • 完全正确,遗憾的是我无法避免插入。我想使用跳转点搜索来限制我的集合中的节点数量将是最好的解决方案。

标签: c++ algorithm qt dijkstra path-finding


【解决方案1】:

从std::set 切换到std::priority_queue。在将节点添加到队列之前,无需检查节点是否已经在开放集中。如果它已经存在,则不将其插入封闭集中会更便宜。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-24
    • 2018-11-01
    • 2011-11-02
    • 2022-11-17
    • 1970-01-01
    • 2021-05-20
    相关资源
    最近更新 更多