树状结构中的递归

时间:2020-01-16 18:36:02

标签: algorithm recursion f# tree maze

作为一个学校项目,我必须递归地使用回溯方法在迷宫中找到求解路径,对于线性问题,我通常没有使用递归来求解算法的问题,但是当涉及到多个选择/路径时,我不知道如何只找到一种解决方案。

问题参数:

  • 以矩阵表示的迷宫,它具有多种到达同一终点的方式
  • 起点

使用的语言:

  • F#

代码输出:

████████████████████
█     █           ██
█ ███ ███ █████ █ ██
█   █   █   █   █ ██
█ █ █ █ █ █ █ ███ ██
█     █Sxxxx  █   ██
█ █████ █ █x███ █ ██
█     █   █xxx█   ██
█ █ █ ███████x██████
█ █ █   █   █x    ██
█ █ ███ █ █ █x███ ██
█   █   █ █  x█   ██
█ ███ ███ █ █x█ █ ██
█            x█   ██
███ █████████x███ ██
███ █     █xxx█ █ ██
███ █ █ █ █x███ █ ██
███   █ █xxx█     ██
█████████x██████████
█████████E██████████

#:墙壁
:路径
E:终点
S:起点

部分代码:

let rec dfs(x,y,path,visited) =
        let rec checkVisited point visited = 
            match visited with
            | [] -> false
            | (x,y)::xs -> if point = (x,y) then true else checkVisited point xs
        let adjacents = [(x,y+1);(x+1,y);(x,y-1);(x-1,y)]
        for point in adjacents do
            if point = this.endPoint then
                this.solutionPath <- path
            else
                if checkVisited point visited = false && this.checkPoint point && this.isWall point = false then
                    dfs(fst(point),snd(point),(path@[point]),(visited@[(x,y)]))

这是在迷宫中搜索解决方案的另一种方法(优化了动作)

let rec dfs(x,y,path) =
            // setting the point in the matrix visited (setting it to 'false')
            matrix.[y].[x] <- (fst(matrix.[y].[x]),false)
            // getting the adjacents of the point
            let adjacents = [(x,y+1);(x+1,y);(x,y-1);(x-1,y)]
            // iterate the adjacents
            for (x1,y1) in adjacents do
                // if the adjacent is the end point set the soultion path
                if (x1,y1) = this.endPoint then
                    this.solutionPath <- path
                else
                    // else check if the point is in the matrix and is not yet visited
                    if this.checkPoint(x1,y1) && snd(matrix.[y1].[x1]) <> false && this.isWall(x1,y1) = false then
                        // execute recursively the method in the point and add the current poisition to the path
                        dfs(x1,y1,((x1,y1)::path))
        dfs(x,y,[])

我做到了!如果您在执行此操作时遇到任何麻烦,我将为您提供帮助(甚至使用其他语言)!

1 个答案:

答案 0 :(得分:7)

您当前的方法看起来还可以。但是,由于您要进行深度优先搜索,所以您遇到的主要问题是,没有什么可以阻止您像尝试[(1,1);(1,2);(1,1);...]这样的无限长路径而陷入困境,而不是去寻找更有效率的路径。为避免这种情况,您可以扫描路径以查看建议的下一个点是否已存在(这在列表的长度上最多花费时间是线性的,这对于较小的问题大小可能是合适的),或者传递访问点集作为递归函数的附加参数(应该允许更快的成员资格查询)。

您遇到的另一个问题是,您没有任何方法可以合并不同分支的结果。一种简单的方法是将内部函数的返回类型更改为选项类型,并从顶部的Some(path)返回if并将其他内容重写为类似的内容

[x, y+1
 x, y-1
 x+1, y
 x-1, y]
|> List.tryPick (fun (x',y') -> if this.checkPoint(x',y') then 
                                    sol(x', y', (x,y)::path)
                                else None)

这是递归地依次尝试每个可能的方向,并返回第一个成功的方向。由于这是深度优先的搜索,因此不一定会返回最短路径。您还可以轻松地创建一个变体,该变体返回所有可能路径的列表,而不是使用选项(最大的更改是使用List.collect而不是List.tryPick),在这种情况下,您可以选择最短的解决方案如果需要,可以从列表中进行选择,尽管这样做会进行很多中间计算。

更复杂的更改是切换到广度优先搜索而不是深度优先搜索,这将使您很容易返回最短路径。从概念上讲,这是一种方法,以追踪到达所有“可见”点的最短路径(仅从[S,[]]开始,以及尚未探索其子项的一组点(再次以只要[S]),然后,只要有要探索的点,就收集他们所有的不同子​​级;对于每个尚不知道路径的子级,将其添加并放入下一组孩子们去探索。