【问题标题】:How could revise the recursive algorithm to find the shortest path?如何修改递归算法以找到最短路径?
【发布时间】:2013-07-25 10:38:21
【问题描述】:

https://vimeo.com/70999946

我实现了递归寻路算法。这种递归算法基于连接在一起的一组预先设置的节点来工作。每个节点都有四个包含进一步方向的指针:Top、Button、Left 和 Right。递归算法只是简单地遍历每个节点,并一个一个地寻找这四个方向中的每一个,以到达其最终目的地;举例说明,考虑以下 7 个节点:A、B、C、D、E、F、G、H。

    A (Button->D, Right->B)
    B (Right->C, Left->B)
    C (Left->B)
    D (Button->G, Right->E, Top->A)
    E (Right->F, Left->D)
    F (Left->E)
    G (Right->H, Top->D)
    H (Left->G)

这些节点在整体视图中会显示如下图。

A—B—C
|
D—E—F
|
G—H

在这个例子中,假设 walker 开始的节点是节点 A,并且想去节点 H 作为它的最终目的地。节点A按顺序查看自己的Right、Button、Left和Top;它的右边指向节点B,因此他选择去节点B;相同模式的节点 B 选择向右走,节点 C。当步行者到达节点 C 时;由于它的 Right、Top 和 Button 被阻塞,节点 C 恢复到节点 B。节点 B 也恢复到节点 A。walker 再次回到起点。然后节点A根据顺序转到它的按钮节点;这意味着它去节点 D。节点 D 去它的右边节点 E,然后是节点 F。因为节点 F 被阻塞;它返回到节点 E 和节点 D。之后,节点 D 根据 walker 顺序选择去其按钮节点 G。从那里节点 G 到节点 H。最后,步行者到达其最终目的地。

Pseudocode: Recursive Path Finding Algorithm
ArrayList findPath(GameObject currentPoint , GameObject targetPoint , ArrayList InputArrayList)
{
1-Duplicate InputArrayList as tempArrayList

2-If the currentPoint equals to target Point return inputArrayList
//*** End Condition found target

3-If the Right side of the currentPoint is empty goto step 4
3.1- Add currentPoint to tempArrayList
//*** Call Right
3.2- tempArrayList = findPath(currentpoint.Right, targetPoint, tempArrayList);
3.3- If tempArrayList is not null return tempArrayList
4-If the Button side of the currentPoint is empty goto step 5
4.1- Add currentPoint to tempArrayList
//*** Call Button
4.2- tempArrayList = findPath(currentpoint.Button, targetPoint, tempArrayList);
4.3- If tempArrayList is not null return tempArrayList
5-If the Left side of the currentPoint is empty goto step 6
5.1- Add currentPoint to tempArrayList
//*** Call Left
5.2- tempArrayList = findPath(currentpoint.Left, targetPoint, tempArrayList);
5.3- If tempArrayList is not null return tempArrayList
6-If the Top side of the currentPoint is empty goto step 7
6.1- Add currentPoint to tempArrayList
//*** Call Top
6.2- tempArrayList = findPath(currentpoint.Top, targetPoint, tempArrayList);
6.3- If tempArrayList is not null return tempArrayList
7-Return null;
//*** End Condition does not found target
}

注意:实际代码是C#,你可以从link下载。

案例研究中出现的问题: 如您理解此代码;它有一个弱点,可以说明它;考虑以下节点的整体视图,假设起始节点为节点 A,最终目的地为节点 H。

A—B—C
|
D—E—F—I
|   | |
G—H—J—K

虽然最好的路径解是(A, D, G, H),但是解释的递归寻路算法找到(A, D, E, F, I, K, J, H)作为它的解;这真的看起来机器人是一个愚蠢的机器人:D!

图 1:递归寻路算法

图 2:具有学习能力的递归寻路算法

我通过添加节点的学习能力解决了这个问题。你可以从这个link 看到问题的细节。但是,我想知道是否有人可以修改递归算法以找到最短路径。

谢谢,

【问题讨论】:

  • +1 精彩演示 :)
  • 所有递归需要工作的是实际存储最短路径。现在它会根据您的缠绕顺序(右、左、上、按钮(下?))返回第一个有效的目标路径。在找到target 时不返回路径,而是将currentPath + target 与类成员shortestPath 进行比较,如果更短,则存储为新的shortestPath。也就是说,杰弗里的回答方式更具概括性。
  • 谢谢@Kay,实际上我在我的博客上写了这个解释,然后在这里过去了:D
  • @Jerdak 感谢您特别纠正我 Button :)) 。你是对的,目前,递归算法返回第一个可能的解决方案。但是,我在这里提出问题的意思是,如何修改递归算法以找到所有可能的解决方案,无论是否有目标设备限制(iPad)。

标签: c# recursion artificial-intelligence unity3d path-finding


【解决方案1】:

为什么不简单地将其与DijkstraA* search 进行比较?

请注意,使用递归而不是循环,您可能会在 1025 次递归时获得 StackOverflow。

【讨论】:

  • 感谢 Dijkstra 和 A* 搜索。正如你所说,实现这样的算法真的很重要。我的算法非常简单和轻便。它基于先到先服务;基于订单的任何可用方向;步行者选择了该方向的节点进行进一步调查。我想为它找到任何轻量级和递归解决方案。
  • 我真正的意思是:任何递归代码都可以重写为循环代码,而无需更改实际算法。所以它的行为完全一样,只是它在递归超过 1024 层时不会引发堆栈溢出。
  • ...在 1024 次递归调用中,堆栈将是几千字节。我非常怀疑你会从中得到一个stackoverflow。
  • @GeoffreyDeSmet 你是对的,即使我用这个简单的算法也遇到过几次堆栈溢出。我的节点总数约为 100。如果您目前注意到我的“找到的结束条件目标”位于第二行。起初我把它放在第 7 行,然后将未找到的目标移到第 8 行。但是该算法面临堆栈溢出错误。总之,我可以说根本没有机会为这个项目实施 Dijkstra 和 A* 搜索。再次感谢。
  • @Daniel 您可以实现 Dijkstra 而不会遇到 stackoverflow 问题。创建一个未处理的REcurrsionsQueue,然后不调用递归方法,而是将一个项目添加到该队列中。然后简单地做一个while循环,直到队列为空。
【解决方案2】:

你正在做一个depth-first search,而你想做的是一个breadth-first search。后者需要queue 而不是递归。 wiki 页面很好地解释了如何实现它,所以我不会在这里重复。

从那里开始,实现A* 不会有太多工作,这应该会加快您的结果。这将需要一个优先级队列而不是队列; C# 在基础库中没有优先级队列,但幸运的是,我是专门用于寻路的optimized C# priority-queue implementation 的作者。

另外,既然你提到了 Unity,我会指出有专门为 Unity 构建的 a number of pathfinding libraries。这可能是最安全的路线,因为在电子游戏中高效的寻路并非易事。

【讨论】:

  • 哇,谢谢伙计,解释的算法正是深度优先搜索。我真的需要考虑您的解决方案,我在这里唯一了解的是我对这里代表的 4 种不同类型的算法一无所知:D !!!!? (Dijkstra、A* 搜索、深度优先搜索、广度优先搜索)
  • 不能递归写广度优先搜索对吗?
  • @Danial:BFS、Dijkstra 和 A* 非常相似。有关更多信息,请参阅 thisthis 问题。回答您的第二个问题:您应该使用队列来实现 BFS。
  • 我看了一眼。我认为正如您提到的那样,BFS 是我想要的另一种解决方案。我认为如果 Dijkstra 和 A* 毫无疑问地实施,我将面临堆栈溢出问题?我说的对吗?
  • @Danial:不,请阅读我链接到的帖子。 BFS 和 Dijkstra 是一回事(至少在像你这样的未加权图表上)。 A* 只是对那些使用额外信息(“启发式”)以使算法更快的简单扩展。
【解决方案3】:

这是您需要的深度优先(更快编写)版本。我不推荐这个寻路答案。 BlueRaja 和 Geoffrey 的答案更通用、更稳定,并且在路径查找方面具有更好的算法。但我想回答 OP 的直接问题。

为了简化我的示例,边的成本/权重为 1。最短路径 == 到达目标的节点数量最少的路径(技术上 length == |path| - 1,因为我计算的是节点而不是边,但无论如何,这个想法是一样的)。这段代码不会处理循环,这就是为什么其他算法更好。

public class PathNode {
    public string Id;
    public PathNode[] Children = new PathNode[4];
}

public class PathFinder : MonoBehaviour {
public List<PathNode> shortestPath = null;

/// <summary>
/// Sets shortest path to `candidatePath` if `candidatePath` is shorter
/// or no shortest path yet.
/// </summary>
public void SetShortestPath(List<PathNode> path){
    if(shortestPath == null){
        shortestPath = new List<PathNode>(path);
        return;
    }
    if(path.Count < shortestPath.Count)
        shortestPath = new List<PathNode>(path);
    }

/// <summary>
/// depth-first shortest path
/// </summary>
public void FindShortestPath(PathNode target, List<PathNode> path){
    PathNode next = path[path.Count-1]; //get next node from path
    if(target == next){
        SetShortestPath(path);
        return;
    }
    // dfs - iterate children
    foreach (PathNode node in next.Children){
        if(node!=null){
            path.Add(node);             
            FindShortestPath(target,path);
            path.Remove(node);          
        }
    }
}
public PathNode ExampleEdgeCreation(){
    PathNode a = new PathNode{Id="A"};
    a.Children[(int)Direction.Left] = new PathNode{Id="B"};
    return a;
}
}

假设PathNode.Children[0] == PathNode.Children[Left] 等。我在代码中列举了它们,但我想保持这个例子的小规模。

【讨论】:

  • 感谢您修改算法,:)
猜你喜欢
  • 2014-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-12
  • 1970-01-01
  • 1970-01-01
  • 2019-03-27
相关资源
最近更新 更多