问题如下所述:Knight's Shortest Path Chess Question。我尝试使用直观的递归方法解决它,如下所示:
let is_in_list x s = List.exists (fun y -> if y = x then true else false) s;; (* find out if an element belongs to a list *)
let next_step n = [n+10;n-6;n+6;n-10;n+17;n-17;n+15;n-15];;
let rec legal_next_step = function
|[]->[]
|x::s->
if x<0 or x>63 then legal_next_step s
else x::legal_next_step s;;
let find_next_step n = legal_next_step (next_step n);; (* here is to find all the valid next moves and put them into a list*)
let rec min s =
let rec loop result = function
|[]->result;
|x::s->
if x < result then loop x s
else loop result s in
loop (List.hd s) s;;
let rec find_shortest_path n m =
if is_in_list m (next_step n) then 1
else
1 + min (List.map (fun x -> find_shortest_path x m) (find_next_step n)) ;;
这会导致“堆栈溢出”问题。这是为什么?我的程序是否在堆栈中创建了太多的递归调用层?或者我的代码出现了严重问题。
答案 0 :(得分:9)
当你写
时,你必须明白List.map (fun x -> find_shortest_path x m) (find_next_step n))
你将从所有可能的邻居计算所有“来自这里的最短路径” - 然后计算所有这些结果的最小值。
因此代码中存在无限循环:如果从位置A
开始并尝试计算其邻居B
之一的最短路径,则find_shortest_path
从{如果他的第一步是回到B
,那么{1}}将不可避免地试图看看这条路有多长。因此,在所有其他可能尝试的移动中,您将计算循环A
的“长度”,即无限循环。
有几种方法可以避免这个问题(这与OCaml编程本身无关,这是程序逻辑中的错误,可以在任何语言中显示):
使用breadth-first search代替深度优先搜索,以便逐步探索给定长度的所有路径,停在您找到的最小获胜路径上;如果你愿意,这对应于并行探索所有路径,所以只要你在尝试搜索整个无限路径之前停止(因为你找到了另一个解决方案),就不会有无限路径
标记您已经访问过的地方,以免“返回”(这绝不是到达目的地的最短路径)
如果你使用深度优先搜索,这很微妙,因为这些标记必须是搜索的本地标记(你不能简单地改变布尔矩阵);例如,您可以向A-B-A-B-A-B...
函数添加int list
参数,这些参数将是当前探索路径的一部分;在尝试计算可能的邻居的最短路径之前,请检查它是否在此列表中。对于更高效的东西,您可以使用集合(find_shortest_path
)(对数,而不是线性,成员资格测试),或使用可变布尔矩阵,在选择不同路径时,您需要小心回溯状态更改。
如果你使用广度优先搜索,你可以使用“你已经访问过的地方”的全局布尔矩阵,因为你同时探索到达给定长度的所有路径;所以如果你遇到一个已被标记为已访问的地方,你知道另一条路径在较早的时间内访问过它,所以它已经超前于你了,没有必要尝试从那里获得最短的路径。
所以简短的回答是:你应该学习广度优先搜索。
答案 1 :(得分:2)
嗯,下一次法律行动计算对我来说是错误的。如果我在右下方(比方说7),我不能移动到方形1.这不应该导致循环,但是,它应该得到错误的答案。
我的猜测是,你只是关注一些非常漫长的失败路径。我认为你需要先进行广度优先搜索而不是深度搜索。实质上,您保留一组可到达的正方形,通过每一步的一个移动推进每个当前可到达的点。您的代码总是尝试从每个新位置到达目的地,因此(可能)遵循许多长路径。