10.2.2.1 添加元素到列表

728 篇文章 1 订阅
349 篇文章 0 订阅

10.2.2.1 添加元素到列表

 

到目前为止,我们已经看到如何追加元素到已有(函数式)列表的前面;如果我们想在列表的末尾追加元素,又该如何做呢?这要求听起来是合理的,那么,我们尝试实现它。清单 10.10 显示了在列表的前面和在后面插入naïve 企图之间性能的差别。

 

清单10.10 在列表中添加元素(F# Interactive)

> let prepend el list = el::list;;     [1]

val prepend : 'a -> 'a list -> 'alist

 

> let rec append el list =     [2]

     match listwith 

     | [] ->[el]     <-- 追加到空列表

     | x::xs ->x::(append el xs)     <-- 递归追加到其余部分

val append : 'a -> 'a list -> 'a list

 

> #time;;     [3]

> let l = [ 1 .. 30000 ];; 

val l : int list

> for i = 1 to 100 do ignore(prepend 1l);;   |

Real: 00:00:00.000, CPU: 00:00:00.000    |

                                   | 追加的性能更慢

> for i = 1 to 100 do ignore(append 1l);;   |

Real: 00:00:00.434, CPU: 00:00:00.421    |

 

prepend 的实现非常简单[1],因为可以使用 cons 运算符(::),直接构造新的列表单元;而追加元素到列表的末尾,则需要写递归函数[2],遵循通常的递归列表处理模式,一个分支处理空列表,另一个处理 cons cell。

接下来,我们输入一个非常有用的 F# Interactive 命令,#time,打开记时器(timing)。在这种模式下,对我们输入的命令,F# 会自动输出执行所用的时间。可以看到,对于大型列表,在末尾追加元素要慢得多。我们在 for 循环中运行一百次,在前面追加所需的时间为零,但在后面追加元素,需要一个相当长的时间。任何“简单”的操作,那怕只需要半秒钟,迭代一百次都值得关注。

我们的追加函数不是尾递归,但在这里不是问题。尾递归能够避免堆栈溢出,但对性能的影响轻微。问题是函数式列表并不适合我们要尝试执行的操作。

图 10.5 显示了为什么这个操作对于函数式列表,不能有效地实现,而在前面追加元素却很容易。因为列表是不可变数据结构,我们可以创建一个单元,并指向原始列表。不可变性保证以后不会有人能改变原始列表,背着我们改变的是“新”列表的内容。与此相比,在后面追加元素,需要改变最后一个元素。以前,最后一个元素“知道”它自己在最后,而我们需要在它的后面有新的元素。列表是不可变的,所以,不能改变保存在最后元素中的信息。相反,必须克隆最后一个元素,这样,还要克隆前面的元素(这样,它就知道,后面跟着的是克隆最后一个元素),等等。

图 10.5 在前面追加元素,我们创建了新的 cons cell 和对原始列表的引用;在后面追加元素,需要遍历并克隆整个列表。

 

当然,有不同的数据结构,每一种数据结构都有不同的可以高效执行的操作。总要有一个权衡,这就是为什么对于不同的问题,选择正确的数据结构,是非常重要的。

 

 

算法的复杂性

 

计算机科学家使用非常精确的数学术语来讨论算法的复杂性,即使我们非正式使用,但这些术语背后的概念仍然非常重要。通常,操作的复杂性表明,算法要求的“基本”步骤数,依赖于输入的大小,它并不预测准确的步骤数,只与输入的大小有关系。

我们现在来分析一下前面的例子。在列表的前面追加元素,总是只要一步:创建新的列表 cons cell。用正规表示,写成 O(1),即,步数是恒定的,不管列表如何大。不管是在有一百万个元素的列表前面添加元素,还是在只有一个元素的列表前面添加元素,花费一样!

在列表的后面追加元素要复杂一。如果开始的时候,列表中有 N 个元素,我们需要处理工复制 N 个 cons cell,这可写成 O(N),表示步骤数大致与列表的大小成正比:在有 1000 个元素的列表后面添加元素,大约是在有500 个元素的列表后面添加元素,的开销的两倍。

例如,如果我们想d列表中追加 M 个新元素,其复杂性应该乘以 M。因此,在前面追加将需要 O(M) 步,因为,1*M = M;同理,在后面追加将需要 O(N * M) 步,可能就大了一个数量级。

 

到目前为止,我们已经讨论了函数式列表,在函数编程中是最重要的集合。现在,要跨一大步,看一看在几乎所有命令式编程语言都存在的集合:不起眼的数组。F# 是 .NET 语言,因此,也可以使用正常 .NET 数组。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值