List 为什么在Scheme中反向O(n^2)的性能会下降?

List 为什么在Scheme中反向O(n^2)的性能会下降?,list,scheme,lisp,List,Scheme,Lisp,假设我在Scheme中的反向函数按以下方式编码: (define (reversee lst) (if (null? lst) '() (append (reversee (cdr lst)) (list (car lst))))) 为什么这个过程的性能O(n^ 2)?好,考虑函数需要做什么: 对于零元素列表,它只返回列表 对于1元素列表,它附加零元素列表和第一个元素的相反位置 对于2元素列表,它附加1元素列表和第一个元素的相反位置 。。。等等 很明显,为了反转

假设我在Scheme中的反向函数按以下方式编码:

(define (reversee lst)
  (if (null? lst)
      '()
      (append (reversee (cdr lst)) (list (car lst)))))

为什么这个过程的性能O(n^ 2)?

好,考虑函数需要做什么:

  • 对于零元素列表,它只返回列表
  • 对于1元素列表,它附加零元素列表和第一个元素的相反位置
  • 对于2元素列表,它附加1元素列表和第一个元素的相反位置
  • 。。。等等
很明显,为了反转一个n元素列表,它递归地调用自己n次。这看起来时间复杂度是O(n),对吗

不是那么快:对于每个递归调用,它都会附加两个列表。因此,我们需要担心
append
的复杂性。好吧,让我们写一篇我们能写的最好的
append

(define (my-append l1 l2)
  (if (null? l1)
      l2
      (cons (car l1) (my-append (cdr l1) l2))))
好的,那么这有多复杂?首先,只取决于
l1
的长度,所以我们只需要担心:

  • 如果
    l1
    是零元素列表,则返回
    l2
  • 如果l2是一个单元素列表,则将其第一个元素添加到将其剩余元素附加到
    l2
  • 。。。等等
因此,要将n元素列表附加到其他列表中,需要n个步骤。那么,这些步骤的时间复杂度是多少?嗯,它是恒定的:
cons
cdr
car
需要恒定的时间。因此,
append
的时间复杂度是第一个列表的长度

这意味着你的反向函数的时间复杂度是

n+n-1+n-2+…+一,

这是一个著名的结果,这样一个和的值是n(n+1)/2(你可以把这个结果写成((n+1)+(n-1+2)+(1+n))/2=((n+1)+(n+1))/2,和中有n个项,所以复杂度是n(n+1),它渐近地等于n^2


当然,有一个更好的版本是
reverse

O(N)
reverse

(定义(反向lst)
(让反向器(lst lst)
(倒“()))
(如果(空?lst)
颠倒的
(反向器(cdr lst)(cons(轿厢lst)反向‘‘‘‘‘)
(反面(1、2、3))

通过使用尾部递归方法构建反向列表,它只需遍历列表一次。

追加与反向函数结合在一起本质上是一个嵌套循环。简短回答:因为您使用的是
append
,所以应该使用
cons
来编写
O(n)
解决方案。