在SML中遍历列表

时间:2019-04-21 18:09:58

标签: sml smlnj

我想创建一个遍历列表,处理头部,在进行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]

2 个答案:

答案 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²)时间成本。