【问题标题】:Quicksort in Lisp Strange behavior?Lisp中的快速排序奇怪的行为?
【发布时间】:2016-01-13 03:15:46
【问题描述】:

我设法让我的快速排序功能正常工作,但我很困惑为什么代码的轻微更改会导致该功能表现异常。 这是工作代码:

(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)

我知道事先存储分区是没有必要的,但是这有什么不应该起作用的原因吗?

【问题讨论】:

    标签: lisp common-lisp quicksort


    【解决方案1】:

    问题在于您在 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,即,在某些情况下感觉,一种全局变量。

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

    有关该主题的更全面讨论,请参见例如优秀书籍Practical Common Lisp 中的chapter on variables

    解决方案

    在使用局部变量之前,您应该使用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)))))
    

    最后,请注意,您可以用这种方式更简洁、更惯用地编写函数lowhigh 也是如此):

    (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))
    

    【讨论】:

      【解决方案2】:

      正如 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,而在它的顶部,它们的值应该包含非空列表。

      一般来说,混合递归(读取函数式编程)和赋值是个坏主意。

      【讨论】:

        【解决方案3】:

        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)))))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-23
          • 1970-01-01
          • 2011-03-13
          • 2012-12-17
          • 2011-03-06
          相关资源
          最近更新 更多