我设法让我的快速排序功能起作用,但我很困惑为什么稍微改变代码会导致函数表现奇怪。 这是工作代码:
(defun low (mylist)
(setq result (list))
(loop
for x in mylist
do (if (< x (car mylist))
(setq result (cons x result))))
result)
(defun high (mylist)
(setq result (list))
(loop
for x in mylist
do (if (> x (car mylist))
(setq result (cons x result))))
result)
(defun qsort (mylist)
(if (null mylist)
nil
(progn
;(setq l1 (low mylist))
;(setq l2 (high mylist))
(append (qsort (low mylist))
(list (car mylist))
(qsort (high mylist))))))
但是在qsort
函数中,如果我尝试将分区存储在l1
和l2
中,然后调用qsort
,则该函数将不再有效:
(defun qsort (mylist)
(if (null mylist)
nil
(progn
(setq l1 (low mylist))
(setq l2 (high mylist))
(append (qsort l1)
(list (car mylist))
(qsort l2)))))
使用此(qsort (list -3 5 4 3 1 2))
返回(-3 1 2)
我知道预先存储分区不是必需的,但是有没有理由认为这不应该起作用?
答案 0 :(得分:9)
问题是你在Common Lisp中使用了错误的变量 - 这在大多数实现中都有信号:
CL-USER> (defun low (mylist)
(setq result (list))
(loop for x in mylist do
(if (< x (car mylist))
(setq result (cons x result))))
result)
;Compiler warnings :
; In LOW: Undeclared free variable RESULT
LOW
请注意,编译器会给出警告,而不是错误,原因有两个:
您可以稍后介绍一个名为result
的全局变量,然后函数执行将是正确的;
(setq x y)
的语义是,如果未定义变量x
,则将值y
分配给符号 x从某种意义上说,这是一种全局变量。
由于第二个原因,你的函数无法正常工作,因为在你的递归定义中,你使用l1
和l2
就像它们是局部变量一样,用不同的值实例化每个递归调用,而不是在不同的调用之间全局分配,产生不正确的结果。
有关该主题的更全面的讨论,请参阅优秀书chapter on variables的Practical Common Lisp。
解决方案
在使用之前,您应该使用let
特殊形式引入局部变量。例如,您可以用这种方式编写函数low
:
(defun low (mylist)
(let ((result (list)))
(loop for x in mylist
if (< x (car mylist))
do (setq result (cons x result)))
result))
let
引入了新变量,稍后您可以使用setq
运算符进行分配。例如,这里是qsort
的正确版本:
(defun qsort (mylist)
(if (null mylist)
nil
(let ((l1 (low mylist))
(l2 (high mylist)))
(append (qsort l1) (list (car mylist)) (qsort l2)))))
最后,请注意,您可以通过这种方式更简洁,更惯用地编写函数low
(同样适用于high
):
(defun low (mylist)
(loop for x in mylist
when (< x (car mylist))
collect x))
最后的注释
您的算法(以及我的重写)没有正确排序列表,因为它消除了重复的元素(例如尝试将其应用于列表(7 3 2 2 4 9 1)
)。
一种纠正方法是修改两个辅助功能中的一个,以便获得所有元素,例如,小于或等于子列表的汽车。这是重写产生正确算法的low
函数:
(defun low (mylist)
(loop for x in (cdr mylist)
when (<= x (car mylist))
collect x))
答案 1 :(得分:1)
正如Renzo所回答的那样,l1
和l2
是全局变量。如果您通过qsort
的定义跟踪其值,则会为调用(qsort '(-1 4 2 3 0 1))
获取以下跟踪:
L1 = NIL L2 = (1 0 3 2 4)
L1 = (0) L2 = (4 2 3)
L1 = NIL L2 = NIL
(-1 0 1)
同时,如果您使用let
表单,则跟踪显示:
L1 = NIL L2 = (1 0 3 2 4)
L1 = (0) L2 = (4 2 3)
L1 = NIL L2 = NIL
L1 = (3 2) L2 = NIL
L1 = (2) L2 = NIL
L1 = NIL L2 = NIL
因此,l1
和l2
在递归调用中被赋予更深的NIL
,而在其顶部,它们的值应该包含非空列表。
一般来说,混合递归(读取函数式编程)和赋值是一个坏主意。
答案 2 :(得分:1)
Renso有答案,但由于你已经使用loop
,你应该挖掘它的潜力。因此你可以这样做
(defun partition (number-list pivot)
(loop :for number :in number-list
:if (<= number pivot)
:collect number :into small
:else
:collect number :into large
:finally (return (values small large))))
qsort
可以这样做来使用它:
(defun qsort (number-list)
(if (not (cdr number-list))
number-list
(multiple-value-bind (small large)
(partition (cdr number-list)
(car number-list))
(nconc (qsort small)
(list (car number-list))
(qsort large)))))