我可以使用Scheme有效地实现快速排序吗?

时间:2013-12-19 18:23:11

标签: performance list scheme quicksort

这就是我所做的:

(define qsort
  (lambda (l)
    (let ((lesser '()))
      (let ((greater '()))
        (cond
          ((null? l) '())
          (else (map (lambda (ele)
                       (if (> (car l) ele)
                           (set! lesser (cons ele lesser))
                           (set! greater (cons ele greater)))) (cdr l))
                (append (qsort lesser) (cons (car l) (qsort greater))))
        )))))

我注意到,当提供已经排序的列表时,它变得极其缓慢。 经过一番搜索后,我发现如果以随机方式选择“枢轴”,可以提高性能。 然而,我知道实现这一目标的唯一方法是list-ref,它似乎是O(n)。 更糟糕的是,我必须实现类似cdr的函数来删除列表中的第n个元素,这可能效率极低。

也许我的方向错了。你能给我一些建议吗?

3 个答案:

答案 0 :(得分:5)

true quicksort在random-access数组上运行,具有就地分区功能。例如see this

您可以首先将列表转换为带有list->vector的向量,然后通过以C方式使用变换交换对向量进行分区来实现快速排序。

随机化它很容易:只需随机选择一个位置,并在每个分区步骤之前将其内容与要排序的范围中的第一个元素交换。完成后,请使用vector->list将其转换回来。

快速排序的有效实现可以在没有递归的情况下运行,在循环中,保持一堆较大的部分边界,总是在较小的部分上下降(然后,当在底部时,切换到堆栈的第一部分)。三向分区总是更可取,只需一次打击即可。

您的基于列表的算法实际上是一个未解决的treesort

另见:

答案 1 :(得分:3)

虽然已经有一个已接受的答案,但我认为您可能会感谢Sheep Trick from The Pitmanual的方案翻译。你的代码实际上已经非常类似了。 Scheme确实支持do循环,但它们并不是特别惯用,而名为let的s更常见,所以我在这段代码中使用了后者。正如您所指出的,如果列表已经排序,选择第一个元素作为枢轴会导致性能问题。由于您必须在每次迭代时遍历列表,因此可能会有一些聪明的事情可以提前为递归调用选择左侧和右侧的枢轴。

(define (nconc l1 l2)
  ;; Destructively concatenate l1 and l2. If l1 is empty,
  ;; return l2.  Otherwise, set the cdr of the last pair
  ;; of l1 to l2 and return l1. 
  (cond
    ((null? l1)
     l2)
    (else 
     (let loop ((l1 l1))
        (if (null? (cdr l1))
            (set-cdr! l1 l2)
            (loop (cdr l1))))
     l1)))

(define (quicksort lst)
  (if (null? lst) lst
      (let ((pivot (car lst))
            (left '())
            (right '()))
        (let loop ((lst (cdr lst))) ; rebind to (cdr lst) since pivot wasn't popped
          (if (null? lst)
              (nconc (quicksort left)
                     (cons pivot
                           (quicksort right)))
              (let ((tail (cdr lst)))
                (cond
                  ((< (car lst) pivot)
                   (set-cdr! lst left)
                   (set! left lst))
                  (else
                   (set-cdr! lst right)
                   (set! right lst)))
                (loop tail)))))))
(quicksort (list 9 1 8 2 7 3 6 4 5))
;=> (1 2 3 4 5 6 7 8 9)

Scheme支持do,所以如果你对此感兴趣(它确实使Common Lisp和Scheme版本非常相似),它看起来像这样:

(define (quicksort lst)
  (if (null? lst) lst
      (do ((pivot (car lst))
           (lst (cdr lst))   ; bind lst to (cdr lst) since pivot wasn't popped
           (left '())
           (right '()))
        ((null? lst)
         (nconc (quicksort left)
                (cons pivot
                      (quicksort right))))
        (let ((tail (cdr lst)))
          (cond
            ((< (car lst) pivot)
             (set-cdr! lst left)
             (set! left lst))
            (else
             (set-cdr! lst right)
             (set! right lst)))
          (set! lst tail)))))
(display (quicksort (list 9 1 8 2 7 3 6 4 5)))
;=> (1 2 3 4 5 6 7 8 9)

答案 2 :(得分:2)

真正有效的Quicksort实现应该是就地的,并使用可以通过索引有效访问的数据结构来实现 - 这使得不可变链接列表成为一个糟糕的选择。

这个问题询问是否可以使用Scheme有效地实现Quicksort - 答案是肯定的,只要你不使用列表。切换到使用vector,它是可变的,并且对其元素具有O(1)基于索引的访问权限,就像使用类似C语言的编程语言中的数组一样。

如果您的输入数据是链接列表,您可以随时执行此类操作,它可能比直接对列表进行排序更快:

(define (list-quicksort lst)
  (vector->list
   (vector-quicksort ; ToDo: implement this procedure
    (list->vector lst))))