Хвост рекурсивная функция, чтобы найти глубину дерева в Ocaml - PullRequest
33 голосов
/ 17 февраля 2012

У меня есть тип 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))
;;

Эта функция не является хвостовой рекурсивной. Есть ли способ для меня, чтобы написать эту функцию в хвостовой рекурсивной форме?

Ответы [ 3 ]

45 голосов
/ 17 февраля 2012

Вы можете сделать это тривиально, превратив функцию в 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)

Это хорошо известный трюк, который может сделать любую функцию рекурсивной.Вуаля, это хвостовая рекомендация.

Следующая известная хитрость в пакете - это «нефункционализация» результата CPS.Представление продолжений ((fun dleft -> ...) частей) в виде функций аккуратно, но вы можете захотеть увидеть, как это выглядит в виде данных.Таким образом, мы заменяем каждое из этих замыканий конкретным конструктором типа данных, который фиксирует используемые в нем свободные переменные.

Здесь у нас есть три замыкания продолжения: (fun dleft -> depth right (fun dright -> k ...)), который только повторно использует переменные среды rightи k, (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

Дефекторизованная функция выглядит следующим образом:

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 -> ...), то есть рекурсивно вызывает depth на правом поддереве.eval и depth являются взаимно рекурсивными.

Теперь внимательно посмотрим на ('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.

Обратите внимание, что дефункциональная функциятолько там для прикола.На практике CPS-версия короткая, ее легко вывести вручную, довольно легко прочитать, и я бы порекомендовал ее использовать.Замыкания должны быть размещены в памяти, но также и элементы ('a, 'b) cont - хотя они могут быть представлены более компактно`.Я бы придерживался версии CPS, если нет веских причин сделать что-то более сложное.

17 голосов
/ 17 февраля 2012

В этом случае (вычисление глубины) вы можете накапливаться по парам (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)]

В более общих случаях вы будетедействительно нужно использовать преобразование CPS, описанное Габриэлем.

0 голосов
/ 19 марта 2017

Существует аккуратное и универсальное решение, использующее 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
...