如何在Lisp中生成一系列Pell编号而不是特定的编号

时间:2019-03-18 14:29:42

标签: lisp common-lisp

如何使用缺点或其他方式打印Pell numbers的列表直到第N个数字?

(defun pellse (k)
   (if (or (zerop k) (= k 1))
       k
   (+ (* 2 (pellse (- k 1)))
      (pellse (- k 2)))))
 (print (pellse 7))

3 个答案:

答案 0 :(得分:5)

这是一种以非指数方式进行的操作:

(defun pells (n)
  (loop repeat n
    for current = 0 then next
    and next = 1 then (+ (* 2 next) current)
    collect current))

给定前两个元素,计算第 n 个元素的时间复杂度为O(log( P n )),其中 P n 是第 n 个Pell编号;您需要log( P n )位作为答案,而log( P n )位用于加法运算。实际上,我们不需要弄清楚 P n 是什么:它是由一个简单的线性递归关系定义的,因此解决方案必须是指数的,因此log( P n )= O( n )。因此,计算第一个 n 个佩尔数的复杂度为O( n * n )= O( n 2 )。

一个人不能 [ * ] 比O( n 2 )做得更好,因为一个人必须写O( n 2 )位代表所有这些数字。

[ * ] 尽管我对此非常怀疑,但从理论上讲,可以通过某种方式共享数据以更紧凑的方式表示列表。 / p>

答案 1 :(得分:5)

这是解决此问题的一种方法,该方法通过定义无限的Pell数据流而起作用。这是基于SICP,尤其是section 3.5中提出的想法。每个人都应该读这本书。

首先,我们需要定义一个构造,让我们谈论无限的数据结构。我们通过延迟除有限部分以外的所有部分来进行评估。因此,从一个名为delay的宏开始,该宏会延迟对表单的求值,并返回一个'promise'(当然,这是一个函数),以及一个名为force的函数,该函数迫使系统在它的承诺:

(defmacro delay (form)
  ;; Delay FORM, which may evaluate to multiple values.  This has
  ;; state so the delayed thing is only called once.
  (let ((evaluatedp-n (make-symbol "EVALUATEDP"))
        (values-n (make-symbol "VALUES")))
    `(let ((,evaluatedp-n nil) ,values-n)
       (lambda ()
         (unless ,evaluatedp-n
           (setf ,evaluatedp-n t
                 ,values-n (multiple-value-list
                            (funcall (lambda () ,form)))))
         (values-list ,values-n)))))

(defun force (promise)
  ;; force a promise (delayed thing)
  (funcall promise))

(出于我们的目的,此实现有些复杂,但这就是我要处理的。)

现在,我们将使用delay来定义,它们可能是无限的精简链。这些操作对应于conses操作,但以stream-为前缀,并且有一个名为null-stream的对象,它对应于()(实际上在此实现中是同一对象)

(defmacro stream-cons (car cdr)
  ;; a cons whose cdr is delayed
  `(cons ,car (delay ,cdr)))

(defun stream-car (scons)
  ;; car of a delayed cons
  (car scons))

(defun stream-cdr (scons)
  ;; cdr of a delayed cons, forced
  (force (cdr scons)))

(defconstant null-stream
  ;; the empty delayed cons
  nil)

(defun stream-null (stream)
  ;; is a delayed cons empty
  (eq stream null-stream))

现在定义一个函数pell-stream,该函数返回Pell编号流。该函数手工制作流的前两个元素,然后使用生成器来制作其余元素。

(defun pell-stream ()
  ;; A stream of Pell numbers
  (labels ((pell (pn pn-1)
             (let ((p (+ (* 2 pn) pn-1)))
               (stream-cons p (pell p pn)))))
    (stream-cons 0 (stream-cons 1 (pell 1 0)))))

现在我们可以简单地反复使用stream-cdr来计算佩尔数。

(defun n-pell-numbers (n)
  (loop repeat n
        for scons = (pell-stream) then (stream-cdr scons)
        collect (stream-car scons)))

现在

> (n-pell-numbers 20)
(0
 1
 2
 5
 12
 29
 70
 169
 408
 985
 2378
 5741
 13860
 33461
 80782
 195025
 470832
 1136689
 2744210
 6625109)

请注意,实际上,pell-stream可以是全局变量:它不必是函数:

(defparameter *pell-stream*
  (labels ((pell (pn pn-1)
             (let ((p (+ (* 2 pn) pn-1)))
               (stream-cons p (pell p pn)))))
    (stream-cons 0 (stream-cons 1 (pell 1 0)))))

(defun n-stream-elements (stream n)
  (loop repeat n
        for scons = stream then (stream-cdr scons)
        collect (stream-car scons)))

如果我们定义一些基准测试程序:

(defun bench-pell (n)
  (progn (n-stream-elements *pell-stream* n) n))

然后,很有趣的是,这显然是一个线性过程(由于后面的元素变大,所以它放慢了速度,因为数字变大,因此对它们的操作需要很长时间),并且promise的有状态实现使其成为在第一次迭代后要快得多(以保持大量数字为代价):

> (time (bench-pell 100000))
Timing the evaluation of (bench-pell 100000)

User time    =        2.020
System time  =        0.803
Elapsed time =        2.822
Allocation   = 1623803280 bytes
441714 Page faults
100000

> (time (bench-pell 100000))
Timing the evaluation of (bench-pell 100000)

User time    =        0.007
System time  =        0.000
Elapsed time =        0.006
Allocation   = 1708248 bytes
0 Page faults
100000

答案 2 :(得分:3)

一种可能的解决方案是使用Common Lisp的LOOP宏,例如:

(print
    (loop for x in '(1 2 3 4 5 6 7)
      for y = (pellse x)
      collect y))

打印出以下结果:

(1 2 5 12 29 70 169)

基于此,您可以构建以下功能:

(defun list-of-n-pell-numbers (n)
    (loop for x from 0 to n
          for y = (pellse x)
          collect y))

并按如下所示运行它:

(print (list-of-n-pell-numbers 7))
(0 1 2 5 12 29 70 169)

但是在使用此代码时请小心,因为您对pellse函数的定义是递归的,并且有栈溢出的风险:使其反复调用足够多(例如,对于大的N值),以及除非您进行一些尾部递归,否则它可能会炸毁调用堆栈。您可能需要查看以下说明: