flatten'
是尾递归的,所以它不应该占用任何堆栈空间。然而,它会沿着树的左侧向下走,在堆中吐出一堆 thunk。如果您在示例树上调用它,并将其简化为 WHNF,您应该得到如下所示的内容:
:
/ \
1 flatten' Tip :
/ \
2 flatten' (Node 4) []
/ \
(Node 3) Tip
/ \
Tip Tip
该算法是O(N)
,但它必须检查Tip
s 以及Node
s。
这看起来是按顺序展平树的最有效方法。该Data.Tree
模块在这里flatten
有一个函数,它做很多相同的事情,除了它更喜欢前序遍历。
更新:
在图形缩减引擎中,flatten
inmain
将生成如下图:
@
/ \
@ []
/ \
/ \
/ \
flatten' Node 2
/ \
/ \
/ \
Node 1 Node 4
/ \ / \
Tip Tip / \
/ \
Node 3 Tip
/ \
Tip Tip
为了将其简化为 WHNF,图形缩减引擎将展开脊椎,将[]
和推Node 2
到堆栈上。然后它将调用该flatten'
函数,该函数会将图形重写为:
@
/ \
/ \
/ \
@ :
/ \ / \
/ \ 2 \
/ \ \
flatten' Node 1 \
/ \ \
Tip Tip @
/ \
@ []
/ \
/ \
/ \
flatten' Node 4
/ \
/ \
/ \
Node 3 Tip
/ \
Tip Tip
并将从堆栈中弹出两个参数。根节点仍然不在 WHNF 中,因此图形缩减引擎将展开脊椎,将2:...
和Node 1
推入堆栈。然后它将调用该flatten'
函数,该函数会将图形重写为:
@
/ \
/ \
/ \
@ :
/ \ / \
/ \ 1 \
/ \ \
flatten' Tip @
/ \
/ \
/ :
@ / \
/ \ 2 \
/ Tip @
/ / \
flatten' @ []
/ \
/ \
/ \
flatten' Node 4
/ \
/ \
/ \
Node 3 Tip
/ \
Tip Tip
并将从堆栈中弹出两个参数。根节点仍然不在 WHNF 中,因此图形缩减引擎将展开脊椎,将1:...
和Tip
推入堆栈。然后它将调用该flatten'
函数,该函数会将图形重写为:
:
/ \
1 \
\
@
/ \
/ \
/ :
@ / \
/ \ 2 \
/ Tip @
/ / \
flatten' @ []
/ \
/ \
/ \
flatten' Node 4
/ \
/ \
/ \
Node 3 Tip
/ \
Tip Tip
并将从堆栈中弹出两个参数。我们现在处于 WHNF 中,最多消耗了两个堆栈条目(假设Tree
节点不是需要额外堆栈空间来评估的 thunk)。
所以,flatten'
是尾递归的。它无需评估额外的嵌套 redexes 即可替换自身。第二个flatten'
仍然是堆中的一个 thunk,而不是堆栈。