簡體   English   中英

尾遞歸函數,用於在Ocaml中查找樹的深度

[英]Tail recursive function to find depth of a tree in Ocaml

我有一個類型tree定義如下

type 'a tree = Leaf of 'a | Node of 'a * 'a tree * 'a tree ;;

我有一個函數來查找樹的深度如下

let rec depth = function 
    | Leaf x -> 0
    | Node(_,left,right) -> 1 + (max (depth left) (depth right))
;;

這個函數不是尾遞歸的。 有沒有辦法讓我以尾遞歸的方式編寫這個函數?

您可以通過將功能轉換為CPS(延續傳遞風格)來輕松完成此操作。 這個想法是,不是調用depth left ,然后根據這個結果計算東西,而是調用depth left (fun dleft -> ...) ,其中第二個參數是“一旦結果( dleft )可用就要計算什么”。

let depth tree =
  let rec depth tree k = match tree with
    | Leaf x -> k 0
    | Node(_,left,right) ->
      depth left (fun dleft ->
        depth right (fun dright ->
          k (1 + (max dleft dright))))
  in depth tree (fun d -> d)

這是一個眾所周知的技巧,可以使任何函數尾遞歸。 Voilà,這是尾巴。

袋中的下一個眾所周知的技巧是“去功能化”CPS結果。 作為函數的continuation( (fun dleft -> ...)部分)的表示是整潔的,但您可能希望看到它看起來像數據。 因此,我們用數據類型的具體構造函數替換每個閉包,捕獲其中使用的自由變量。

在這里,我們有三個封延續: (fun dleft -> depth right (fun dright -> k ...))其中只有重用的環境變量rightk(fun dright -> ...)其中重用k和現在可用的左結果dleft ,和(fun d -> d) ,初始計算,不捕獲任何東西。

type ('a, 'b) cont =
  | Kleft of 'a tree * ('a, 'b) cont (* right and k *)
  | Kright of 'b * ('a, 'b) cont     (* dleft and k *)
  | Kid

defunctorized函數如下所示:

let depth tree =
  let rec depth tree k = match tree with
    | Leaf x -> eval k 0
    | Node(_,left,right) ->
      depth left (Kleft(right, k))
  and eval k d = match k with
    | Kleft(right, k) ->
      depth right (Kright(d, k))
    | Kright(dleft, k) ->
      eval k (1 + max d dleft)
    | Kid -> d
  in depth tree Kid
;;

我沒有構建函數k並將其應用於葉子( k 0 ),而是構建了一個類型為('a, int) cont ,需要稍后對其進行eval以計算結果。 eval ,當它傳遞給Kleft ,會執行閉包(fun dleft -> ...)正在做的事情,即它在右子(fun dleft -> ...)遞歸調用depth evaldepth是相互遞歸的。

現在仔細看看('a, 'b) cont ,這個數據類型是什么? 這是一個清單!

type ('a, 'b) next_item =
  | Kleft of 'a tree
  | Kright of 'b

type ('a, 'b) cont = ('a, 'b) next_item list

let depth tree =
  let rec depth tree k = match tree with
    | Leaf x -> eval k 0
    | Node(_,left,right) ->
      depth left (Kleft(right) :: k)
  and eval k d = match k with
    | Kleft(right) :: k ->
      depth right (Kright(d) :: k)
    | Kright(dleft) :: k ->
      eval k (1 + max d dleft)
    | [] -> d
  in depth tree []
;;

列表是一個堆棧。 我們這里所擁有的實際上是前一個遞歸函數的調用堆棧的具體化(轉換為數據),兩種不同的情況對應於兩種不同類型的非tailrec調用。

請注意,defunctionalization只是為了好玩。 在實踐中,CPS版本很簡單,易於手工派生,相當容易閱讀,我建議使用它。 閉包必須在內存中分配,但是('a, 'b) cont元素也是如此 - 盡管那些可能更緊湊地表示。 我會堅持使用CPS版本,除非有很好的理由去做更復雜的事情。

在這種情況下(深度計算),您可以累加對( subtree depth * subtree content )以獲得以下尾遞歸函數:

let depth tree =
  let rec aux depth = function
    | [] -> depth
    | (d, Leaf _) :: t -> aux (max d depth) t
    | (d, Node (_,left,right)) :: t ->
      let accu = (d+1, left) :: (d+1, right) :: t in
      aux depth accu in
aux 0 [(0, tree)]

對於更一般的情況,您確實需要使用Gabriel描述的CPS轉換。

使用fold_tree和CPS有一個簡潔而通用的解決方案 - 連續傳遞方式:

let fold_tree tree f acc =
  let loop t cont =
    match tree with
    | Leaf -> cont acc
    | Node (x, left, right) ->
      loop left (fun lacc ->
        loop right (fun racc ->
          cont @@ f x lacc racc))
  in loop tree (fun x -> x)

let depth tree = fold_tree tree (fun x dl dr -> 1 + (max dl dr)) 0

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM