我想创建一个遍历列表,处理头部,在进行K次递归后停止并在每次递归中使用head元素创建相同列表的函数。
fun trav (0, _, list) = list
| trav(K, x::xs, list) =
trav(K - 1, xs, list@[x])
所以如果我打trav(4,[1,2,3,4,5,6],[])
我希望
list =[1] ,K=3
=[1,2] ,K=2
=[1,2,3] ,K=1
=[1,2,3,4] ,K=0
但是对于很大的输入,此-> list@[x]
似乎使我的程序崩溃(我不确定为什么),并且如果我使用(x :: list)
而不是给出一个不同(但大小相同)的列表在每一步中一切正常,为什么会发生?我如何使用cons运算符实现list@[x]
?
答案 0 :(得分:2)
list@[x]
需要遍历整个list
,然后通过逐个元素地将其复制到[x]
中来复制它,这是非常低效的。
传统的解决方案是将结果反向生成,然后在完成后将其反转为所需的顺序:
fun trav (0, _, list) = List.rev list
| trav (K, x::xs, list) = trav (K-1, xs, x::list)
这似乎效率不高,但实际上比附加版本效率高。
(它具有线性的时间复杂度,而不是二次的,以防万一。)
答案 1 :(得分:1)
[...] 对于非常大的输入
list@[x]
似乎使我的程序 [...] 崩溃,如果我使用x :: list
而是在每个步骤中给出不同(但大小相同)的列表,因此一切正常。为什么会这样?
list@[x]
耗尽了程序的stack memory,因为@
运算符不是尾递归的。
当list
很长时,它将构建如下表达式:
[a,b,c,d,e,f,...] @ z
~> a :: [b,c,d,e,f,g,...] @ z
~> a :: b :: [c,d,e,f,g,...] @ z
~> ...
~> a :: b :: ... :: [] @ z
~> a :: b :: ... :: z :: []
所有这些中间列表元素都保留在递归调用堆栈中,程序最终将用完该堆栈。最重要的是,对列表中的每个元素都重复进行了这种昂贵的计算,因此花费了 O(n²)时间成本。