维基百科A *寻路算法需要大量时间


9

我已经在C#中成功实现了A *寻路,但是速度很慢,而且我不明白为什么。我什至尝试不对openNodes列表进行排序,但仍然相同。

映射为80x80,并且有10-11个节点。

我从维基百科这里获取了伪代码

这是我的实现:

 public static List<PGNode> Pathfind(PGMap mMap, PGNode mStart, PGNode mEnd)
    {
        mMap.ClearNodes();

        mMap.GetTile(mStart.X, mStart.Y).Value = 0;
        mMap.GetTile(mEnd.X, mEnd.Y).Value = 0;

        List<PGNode> openNodes = new List<PGNode>();
        List<PGNode> closedNodes = new List<PGNode>();
        List<PGNode> solutionNodes = new List<PGNode>();

        mStart.G = 0;
        mStart.H = GetManhattanHeuristic(mStart, mEnd);

        solutionNodes.Add(mStart);
        solutionNodes.Add(mEnd);

        openNodes.Add(mStart); // 1) Add the starting square (or node) to the open list.

        while (openNodes.Count > 0) // 2) Repeat the following:
        {
            openNodes.Sort((p1, p2) => p1.F.CompareTo(p2.F));

            PGNode current = openNodes[0]; // a) We refer to this as the current square.)

            if (current == mEnd)
            {
                while (current != null)
                {
                    solutionNodes.Add(current);
                    current = current.Parent;
                }

                return solutionNodes;
            }

            openNodes.Remove(current);
            closedNodes.Add(current); // b) Switch it to the closed list.

            List<PGNode> neighborNodes = current.GetNeighborNodes();
            double cost = 0;
            bool isCostBetter = false;

            for (int i = 0; i < neighborNodes.Count; i++)
            {
                PGNode neighbor = neighborNodes[i];
                cost = current.G + 10;
                isCostBetter = false;

                if (neighbor.Passable == false || closedNodes.Contains(neighbor))
                    continue; // If it is not walkable or if it is on the closed list, ignore it.

                if (openNodes.Contains(neighbor) == false)
                {
                    openNodes.Add(neighbor); // If it isn’t on the open list, add it to the open list.
                    isCostBetter = true;
                }
                else if (cost < neighbor.G)
                {
                    isCostBetter = true;
                }

                if (isCostBetter)
                {
                    neighbor.Parent = current; //  Make the current square the parent of this square. 
                    neighbor.G = cost;
                    neighbor.H = GetManhattanHeuristic(current, neighbor);
                }
            }
        }

        return null;
    }

这是我正在使用的启发式方法:

    private static double GetManhattanHeuristic(PGNode mStart, PGNode mEnd)
    {
        return Math.Abs(mStart.X - mEnd.X) + Math.Abs(mStart.Y - mEnd.Y);
    }

我究竟做错了什么?我整天都在看相同的代码。


2
如果不使用启发式方法,通常在遍历更多节点之前需要更长的时间,直到找到终点为止。另外,请尝试使用仍在排序的排序列表(最好是排序集合,这样您就不必检查列表中是否存在项目,您只需添加即可)
Elva

Answers:


10

我看到三件事,一错,二可疑。

1)您在每次迭代中进行排序。别。使用优先级队列,或者至少进行线性搜索以找到最小值。您实际上并不需要始终对整个列表进行排序!

2)openNodes.Contains()可能很慢(不确定C#的List的细节,但我敢打赌它会进行线性搜索)。您可以在每个节点上添加一个标志,并在O(1)中执行此操作。

3)GetNeighborNodes()可能很慢。


2
2)是的,Contains()会非常慢。与其将所有节点存储在列表中,不如使用Dictionary <int,PGNode>。然后,您获得O(1)查找时间,并且仍可以迭代列出列表。如果节点具有id字段,请将该字段用作键,否则PGNode.GetHashCode()将起作用。
宽大处理

2
@Leniency:字典<PGNode,PGNode>会更好吗?两个对象可能具有相同的哈希码,但不相等。“因此,此方法的默认实现不得用作用于哈希目的的唯一对象标识符。” msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx-.NET 3.5提供了更好的HashSet-msdn.microsoft.com/en-us/library/bb359438.aspx

好点,忘记了HashSet。
宽大处理

9

除了已经指出应该使用优先级堆的观点之外,您还误解了启发式方法。你有

如果(isCostBetter)
{
    ...
    neighbor.H = GetManhattanHeuristic(当前,邻居);
}
但是,试探法应该是到目的地的距离的估计。首次添加邻居时,应设置一次:
如果(openNodes.Contains(neighbor)== false)
{
    N = GetHeuristic(neighbor,mEnd);
    ...
}

另外,您可以通过过滤中的不可逾越节点来简化A * GetNeighbourNodes()


+1,我专注于算法复杂性,完全错过了启发式算法的错误用法!
ggambett 2011年

4

元答案:您永远不应该只花一天时间盯着代码寻找性能问题。与探查器一起使用五分钟将向您确切显示瓶颈所在。您可以下载大多数分析器的免费跟踪,并在几分钟之内将其连接到您的应用程序。


3

比较不同节点的F时,不清楚要比较的内容。F是否将属性定义为G + H?它应该是。(旁白:这是为什么统一访问原则被废话的示例。)

但是,更重要的是,您正在每帧对节点重新排序。A *要求使用优先级队列,该队列允许-O(lg n)-排序地插入单个元素,以及一个集合,该集合允许快速检查封闭的节点。编写算法后,您将获得O(n lg n)插入+排序,这会将运行时间提高到无用的比例。

(如果C#具有良好的排序算法,您可能会获得O(n)插入+排序。它仍然太多了。请使用真正的优先级队列。)


2

http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html

  • 在一个极端情况下,如果h(n)为0,则只有g(n)起作用,并且A *变成Dijkstra的算法,可以保证找到最短的路径。
  • 如果h(n)始终低于(或等于)从n到目标的成本,则A *被保证找到最短路径。h(n)越低,则节点A *扩展越多,使其速度越慢。
  • 如果h(n)恰好等于从n移至目标的成本,则A *将仅遵循最佳路径,而不会扩展其他任何东西,因此非常快。尽管您无法在所有情况下都做到这一点,但在某些特殊情况下可以做到准确。很高兴知道,给定完美的信息,A *的行为也会很完美。
  • 如果h(n)有时大于从n移到目标的成本,则不能保证A *找到最短的路径,但它可以运行得更快。
  • 在另一个极端,如果h(n)相对于g(n)很高,则只有h(n)起作用,A *变成最佳优先搜索。

您正在使用“曼哈顿距离”。这几乎总是一种不好的启发。此外,通过从链接页面查看该信息,您可以猜测您的启发式方法比实际费用要低。


-1,问题不是启发式,而是实现。

2

除了其他最重要的答案(无疑比这个建议更重要)之外,另一个优化是将封闭的“列表”更改为某种哈希表。您不需要将其成为有序集合,只需能够快速添加值并快速查看它们是否存在于集合中即可。


1

您的成本和启发式需要建立关系。应该指出,H是在两个不同的位置计算出来的,但从未访问过。


这假定该属性的实现不正确,这是可能的,因为未显示其定义,但是代码还有两个直接的问题。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.