Lisp中的Quicksort奇怪的行为?

时间:2016-01-13 03:15:46

标签: lisp common-lisp quicksort

我设法让我的快速排序功能起作用,但我很困惑为什么稍微改变代码会导致函数表现奇怪。 这是工作代码:

(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函数中,如果我尝试将分区存储在l1l2中,然后调用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)

我知道预先存储分区不是必需的,但是有没有理由认为这不应该起作用?

3 个答案:

答案 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

请注意,编译器会给出警告,而不是错误,原因有两个:

  1. 您可以稍后介绍一个名为result的全局变量,然后函数执行将是正确的;

  2. (setq x y)的语义是,如果未定义变量x,则将值y分配给符号 x从某种意义上说,这是一种全局变量。

  3. 由于第二个原因,你的函数无法正常工作,因为在你的递归定义中,你使用l1l2就像它们是局部变量一样,用不同的值实例化每个递归调用,而不是在不同的调用之间全局分配,产生不正确的结果。

    有关该主题的更全面的讨论,请参阅优秀书chapter on variablesPractical 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所回答的那样,l1l2是全局变量。如果您通过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

因此,l1l2在递归调用中被赋予更深的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)))))