Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/285.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 维基百科A*寻路算法需要很多时间_C#_.net_Optimization_Path Finding_A Star - Fatal编程技术网

C# 维基百科A*寻路算法需要很多时间

C# 维基百科A*寻路算法需要很多时间,c#,.net,optimization,path-finding,a-star,C#,.net,Optimization,Path Finding,A Star,我已经成功地在C#中实现了一个*寻路,但是它非常慢,我不明白为什么。我甚至试着不排序openNodes列表,但它仍然是一样的 地图是80x80,有10-11个节点 我从这里拿走了伪代码 这是我的实现: public static List<PGNode> Pathfind(PGMap mMap, PGNode mStart, PGNode mEnd) { mMap.ClearNodes(); mMap.GetTile(mStart.X, m

我已经成功地在C#中实现了一个*寻路,但是它非常慢,我不明白为什么。我甚至试着不排序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;
    }

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

内存消耗是什么样的?下载红门工具。使用Performance Profiler查看在哪里花费的时间最多,使用Memory Profiler确定是否存在内存泄漏或对象处理速度不够快的问题

正如@Rodrigo所指出的,你可能需要处理一张大地图。嵌套循环永远不会被期望执行。

您可以将其与(或仅利用)A*实现进行比较,具体如下:

QuickGraph.Algorithms.ShortestPath.AstarShortTestPathAlgorithm
一些注意事项:

列表
未针对删除第一个元素进行优化。最好是按相反的顺序排序,并取最后一个元素。或者使用
堆栈
队列

列表。删除(当前)
效率极低。您已经知道要删除的索引,不要在整个列表中搜索该元素

通过在正确的位置插入新节点来保持
openNodes
的排序应该比不断地重新调用整个列表快得多。跳过列表排序会删除重要的不变量,从而破坏整个算法。您需要加快排序速度,而不是跳过它

您在
closedNodes
上执行的主要操作是状态测试,
closedNodes.Contains()
。使用为此而优化的数据结构,例如
Set
。或者更好的方法是,在每个节点中放置一个关闭的标志字段,并在每次传递的开始处清除它们。这比在每次迭代中通过
closedNodes
进行线性搜索要快得多

最初不应在
solutionNodes
中放入任何内容,在遍历路径的最后一个循环中,
mEnd
mStart
都将被添加


相邻节点
可以是
IEnumerable
而不是
列表
,因为您永远不需要一次完整的列表。使用
foreach
也会比按索引枚举列表稍微快一些。

您可以这样计算遍历节点成本:

cost = current.G + 10;
var closed = new HashSet<Node>();
var queue = new PriorityQueue<double, Path<Node>>();
queue.Enqueue(0, new Path<Node>(start));
while (!queue.IsEmpty)
{
    var path = queue.Dequeue();
    if (closed.Contains(path.LastStep)) continue;
    if (path.LastStep.Equals(destination)) return path;
    closed.Add(path.LastStep);
    foreach(Node n in path.LastStep.Neighbours)
    {
        double d = distance(path.LastStep, n);
        var newPath = path.AddStep(n, d);
        queue.Enqueue(newPath.TotalCost + estimate(n), newPath);
    }
}
然而,对于启发式,你有一个简单的距离。为什么不在这里使用相同的距离?根据节点当前的距离,您的启发式可能太低

另一个可能错误的“详细信息:
current.GetNeighborNodes
。这是如何实现的?它是否返回相同位置的相同节点,以便共享不同路径上的相同节点,还是始终使用new分配新节点?

首先,使用探查器。使用工具告诉你什么是慢的;慢的东西往往令人惊讶。并使用调试器。制作一个包含五个节点的地图,当它试图找到路径时,逐行遍历代码的每一行。有什么意外的事情发生吗?如果是这样,弄清楚发生了什么


第二,撇开你的性能问题不谈,我认为你也有一个正确性问题。你能解释一下为什么你认为曼哈顿距离是一个合理的启发吗

(对于那些不熟悉该指标的读者来说,“曼哈顿距离”或“出租车距离”是指如果你居住在一个网格上的城市,你必须行驶的两点之间的距离。也就是说,要向东北方向行驶14英里,你必须向北行驶10英里,然后向东行驶10英里,总共行驶20英里。)

A*算法的正确性要求启发式总是低估两个点之间移动所需的实际距离。如果图中有任何“对角捷径”街道,则曼哈顿距离高估了这些路径上的距离,因此算法不一定会找到最短路径

你为什么不使用欧几里德距离作为你的启发

您是否尝试过使用“始终为零”作为启发式的算法?这肯定是低估了。(这样做可以实现Dijkstra算法。)

第三,在这个实现中,您似乎做了很多排序工作。当然,您可以使用优先级队列;这比分类便宜得多

第四,我的博客上有一个C#3中A*的实现,欢迎大家使用;无明示或暗示的担保,使用风险自负

我的代码非常简单;我的实现中的算法如下所示:

cost = current.G + 10;
var closed = new HashSet<Node>();
var queue = new PriorityQueue<double, Path<Node>>();
queue.Enqueue(0, new Path<Node>(start));
while (!queue.IsEmpty)
{
    var path = queue.Dequeue();
    if (closed.Contains(path.LastStep)) continue;
    if (path.LastStep.Equals(destination)) return path;
    closed.Add(path.LastStep);
    foreach(Node n in path.LastStep.Neighbours)
    {
        double d = distance(path.LastStep, n);
        var newPath = path.AddStep(n, d);
        queue.Enqueue(newPath.TotalCost + estimate(n), newPath);
    }
}
var closed=newhashset();
var queue=new PriorityQueue();
排队(0,新路径(开始));
而(!queue.IsEmpty)
{
var path=queue.Dequeue();
如果(closed.Contains(path.LastStep))继续;
if(path.LastStep.Equals(destination))返回路径;
closed.Add(path.LastStep);
foreach(path.LastStep.neights中的节点n)
{
双d=距离(路径最后一步,n);
var newPath=path.AddStep(n,d);
排队(newPath.TotalCost+estimate(n),newPath);
}
}
其思想是我们保持路径的优先级队列;也就是说,路径队列始终能够以最小的距离告诉您到目前为止的路径。然后我们检查是否到达目的地;如果是这样,我们就完了。如果没有,那么我们根据它们(低估)到目标的距离将一组新路径排队


第五,维基百科中的伪代码可以改进。事实上,我的实际代码在许多方面比伪代码更容易理解,这表明伪代码中可能有太多的细节。

您是否使用栅格作为地形表示