如果您想按顺序遍历树并返回一个列表,这意味着我们的函数inorder必须具有'a tree -> 'a list的类型。
let rec inorder t =
match t with
| Empty -> []
| Node (v, l, r) -> List.append (inorder l) (v :: (inorder r)) (* ! *)
但是List.append 在尾部位置,而不是inorder。另一个问题是我们有两次调用inorder。如果我们将inorder l 放在尾部位置,inorder r 就不可能处于尾部位置 - 反之亦然。
解决此问题的一种巧妙方法是延续传递样式。我们将上面的函数转换为一个辅助函数,并为我们的继续添加一个额外的参数,return
(* convert to helper function, add an extra parameter *)
let rec loop t return =
match t with
| Empty -> ...
| Node (v, l, r) -> ...
延续代表“下一步该做什么”,因此我们不能直接从我们的函数中发送值,而是必须将它们交给延续。这意味着对于Empty 案例,我们将return [] - 而不是简单的[]
let rec loop t return =
match t with
| Empty -> return []
| Node (v, l, r) -> ...
对于Node (v, l, r) 的情况,现在我们有了一个额外的参数,我们可以编写自己的延续来通知loop 下一步该做什么。所以要构造我们的排序列表,我们需要loop l,然后是loop r(反之亦然),然后我们可以append他们。我们将像这样编写我们的程序。
let rec loop t return =
match t with
| Empty -> return []
| Node (v, l, r) ->
loop l ... (* build the left_result *)
loop r ... (* build the right_result *)
return (List.append left_result (v :: right_result))
在下一步中,我们将为延续填写实际的 lambda 语法。
let rec loop t return =
match t with
| Empty -> return []
| Node (v, l, r) ->
loop l (fun left ->
loop r (fun right ->
return (List.append left (v :: right))))
最后,我们定义inorder,它是对loop 的调用,默认延续identity。
let identity x =
x
let inorder t =
let rec loop t return =
match t with
| Empty -> return []
| Node (v, l, r) ->
loop r (fun right ->
loop l (fun left ->
return (List.append left (v :: right))))
in
loop t identity
如您所见,loop r (fun right -> ...) 位于 Node 分支的尾部。 loop l (fun left -> ...) 位于第一个延续的尾部位置。而List.append ... 在第二个延续的尾部位置。如果List.append 是一个尾递归过程,inorder 将不会增长堆栈。
注意使用List.append 对于大树来说可能是一个代价高昂的选择。我们的函数每Node 调用一次。你能想出避免它的方法吗?这个练习留给读者。