这个答案特别关注 lists(不是向量)的递归回文检查的情况,作为Gwang-Jin Kim 答案的 cmets 的后续行动。有一种递归方法可以检查回文列表,它不分配新列表,并且仅在 O(n) 时间内迭代给定列表,即。在线性时间,而不是二次。
为了解释这种方法,让我定义一个辅助函数map-opposites,它是一个高级函数,将函数映射到列表两端的值。例如,(map-opposites f '(a b c)) 在以下几个参数上调用 f:(c a)、(b b) 和 (a c)(按此顺序)。然后,palindrome 检查函数将只是一个带有闭包的map-opposites 应用程序(提前退出)。
为了使map-opposites 可用于回文检查(可能还有其他功能),它还跟踪列表中每个元素的当前索引。
map-opposite 调用的函数应该接受两个值x 和y,以及两个索引x-index 和y-index。
地图对面
(defun map-opposites% (fun l1 whole i1)
(if l1
(destructuring-bind (h1 . t1) l1
(multiple-value-bind (i2 l2) (map-opposites% fun t1 whole (1+ i1))
(if l2
(destructuring-bind (h2 . t2) l2
(funcall fun h1 h2 i1 i2)
(values (1+ i2) t2))
i2)))
(values 0 whole)))
(defun map-opposites (fun list)
(map-opposites% fun list list 0))
让我们定义一个辅助函数并跟踪它,以及map-opposites%:
(defun dbg (&rest args)
(declare (ignore args)))
(trace map-opposites% dbg)
用这个输入调用主函数:
(map-opposites 'dbg '(a b c d e))
提供以下跟踪(为清楚起见,删除了包前缀):
0: (MAP-OPPOSITES% DBG (A B C D E) (A B C D E) 0)
1: (MAP-OPPOSITES% DBG (B C D E) (A B C D E) 1)
2: (MAP-OPPOSITES% DBG (C D E) (A B C D E) 2)
3: (MAP-OPPOSITES% DBG (D E) (A B C D E) 3)
4: (MAP-OPPOSITES% DBG (E) (A B C D E) 4)
5: (MAP-OPPOSITES% DBG NIL (A B C D E) 5)
5: MAP-OPPOSITES% returned 0 (A B C D E)
5: (DBG E A 4 0)
5: DBG returned NIL
4: MAP-OPPOSITES% returned 1 (B C D E)
4: (DBG D B 3 1)
4: DBG returned NIL
3: MAP-OPPOSITES% returned 2 (C D E)
3: (DBG C C 2 2)
3: DBG returned NIL
2: MAP-OPPOSITES% returned 3 (D E)
2: (DBG B D 1 3)
2: DBG returned NIL
1: MAP-OPPOSITES% returned 4 (E)
1: (DBG A E 0 4)
1: DBG returned NIL
0: MAP-OPPOSITES% returned 5 NIL
主要有两个区域:
-
首先,函数递归到最终的空列表,同时计算列表的大小;请注意,整个原始列表作为第三个参数未经修改地传递。
-
当展开堆栈时,整个列表将被第二次遍历(参见第二次返回值),同时调用堆栈向上移动。事实上,有人可能会争辩说,调用堆栈扮演着列表副本的角色。
更准确地说,请注意:
-
在递归的底部,函数返回列表的大小,以及整个列表。当堆栈向上展开时,将向下访问整个列表。
-
在递归的中间级别,我们有当前值h1(向下递归时获得的值)和第二个值h2 通过迭代整个列表(称为l2)获得。它们对应于列表中的相反索引,如对dbg 的调用所示。递归的中间级别返回两个值:左侧元素的递增索引 (i2) 和列表尾部 t2。
回文
回文检查定义如下:
(defun palindrome (list)
(prog1 t
(map-opposites (lambda (x y ix iy)
(when (<= ix iy)
(return-from palindrome t))
(unless (eql x y)
(return-from palindrome nil)))
list)))
它为空列表返回t,否则如果值不同(案例nil)或当我们尝试访问列表的前半部分时从lambda 提前返回,这对于此检查是多余的。
只有索引
通过只使用索引,我希望遍历的结构更清晰一些;这里我只计算相关的索引。
(defun map-opposite-indices% (fun list i1)
(if list
(let ((i2 (map-opposite-indices% fun (rest list) (1+ i1))))
(funcall fun i1 i2)
(1+ i2))
0))
(defun map-opposite-indices (fun list)
(map-opposite-indices% fun list 0))
(defun dbg (&rest args)
(print args))
(map-opposite-indices 'dbg '(a b c d e))
打印出来:
(4 0)
(3 1)
(2 2)
(1 3)
(0 4)
仅使用元素
只跟踪列表而不跟踪索引的版本:
(defun map-opposites% (fun l1 whole)
(if l1
(destructuring-bind (h1 . t1) l1
(let ((l2 (map-opposites% fun t1 whole)))
(when l2
(destructuring-bind (h2 . t2) l2
;; in the base case l2 is the whole list (a b c d e)
;; then one level up in the recursion l2 is bound to
;; (b c d e); one level up it is (c d e),
;; then (d e), etc.
;; At the same levels of recursion, l1 is bound
;; respectively to (), then (e), then (d e),
;; then (c d e), etc.
(prog1 t2
(funcall fun h1 h2))))))
whole))
(defun map-opposites (fun list)
(map-opposites% fun list list))
(defun dbg (&rest args)
(print args))
(map-opposites 'dbg '(a b c d e))
打印:
(E A)
(D B)
(C C)
(B D)
(A E)