方案:实施快速排序

时间:2017-04-18 13:45:18

标签: functional-programming scheme

我正在尝试使用方案实现快速排序,这里的一些家伙已经帮我修复了我的split函数,现在我要求你帮助将所有内容组合成一个有效的算法。

到目前为止,这是我的代码:

(define quick-sort (lambda (lst)

                 (define pivot (lambda (lst)
                                 (if (null? lst)
                                     null
                                     (car lst))))
                 (define split (lambda (lst pivot)
                                 (define lst1 null)
                                 (define lst2 null)
                                 (define split-helper (lambda (lst pivot lst1 lst2)
                                                        (if (null? lst)
                                                            (list lst1 lst2)
                                                            (if (<= (car lst) pivot)
                                                                (split-helper (cdr lst) pivot (cons (car lst) lst1) lst2)
                                                                (split-helper (cdr lst) pivot lst1 (cons (car lst) lst2))))))
                                 (split-helper lst pivot lst1 lst2)))

                 (if (null? lst)
                            null
                            (append (quick-sort (car (split lst (pivot lst)))) (quick-sort (cdr (split lst (pivot lst))))))))

正如你所看到的,我选择的枢轴只是列表中的第一个元素,我面临的问题是,当pivot是列表中的最小元素时,程序会遇到无限循环,因为它使程序一遍又一遍地选择相同的轴。

此外,它当前实现的方式使它真的无效率,因为split函数在lst的每个ineration中被调用两次并且具有相同的quick-sort但我只是'对控制有足够的控制权以任何其他方式写它。

我在Scheme中看到了一些关于Quick-Sort的帖子,但它们的实现有点不同,我宁愿尝试纠正自己的实现而不是复制其他一些家伙的工作。

谢谢。

2 个答案:

答案 0 :(得分:3)

对于快速排序而言,这是一个经典的错误。您的数据透视表不应该是分区的一部分。这样一个元素列表就会生成两个空分区,一个在枢轴之前,一个在枢轴之后。

两次做同样的操作。使用let缓冲拆分结果并使用变量两次。

答案 1 :(得分:2)

删除了过多的lambdas,别名,绑定和重新格式化,但没有更改或注释语义(Sylwester已经指出了这个bug):

(define (quick-sort lst)
   (define (pivot lst)
      (if (null? lst)
         '()
         (car lst) ))
   (define (split lst pivot)
      (let split-helper ((lst lst)         ; Named let instead of internal
                         (lst1 '())        ; definition
                         (lst2 '()) )
         (if (null? lst)
            (cons lst1 list2)
            (if (<= (car lst) pivot)
               (split-helper (cdr lst)
                             (cons (car lst) lst1)
                             lst2)
               (split-helper (cdr lst)
                             lst1
                             (cons (car lst) lst2) )))))
   (if (null? lst)
      '()
      (let ((spl (split lst (pivot lst)))) ; Memoization of the `split`
         (append (quick-sort (car spl))
                 (quick-sort (cdr spl)) ))))

我认为您正在尝试实施partition

(define (partition pred xs)
   (let part ((ps '()) (ns '()) ; Initial "positives" `ps`, and "negatives" `ns`
              (xs' xs) )
      (if (null? xs')
         (cons ps ns)             ; Returning pair of lists
         (let ((x (car xs')))     ; Memoization of `(car lst)`
            (if (pred x)
               (part (cons x ps) ns (cdr xs'))
               (part ps (cons x ns) (cdr xs')) )))))

(define (quicksort xs)
   (if (null? xs) '()
      (let* ((x (car xs))
             (pn (partition               ; Memoization of `partition`
                    (lambda (x')
                       (< x' x) )
                    (cdr xs) )))
         (append (quicksort (car pn))      ; Extracting positives from pair
                 (list x)                  ; Pivot
                 (quicksort (cdr pn)) )))) ; Negatives


(display
   (quicksort (list 4 2 3 5 1)) )    ; (1 2 3 4 5)

part在如Scheme等严格语言中效率低下;它为每个递归步骤复制所有三个参数。通常,基本折叠(如filtermap的直接表述最有效。使用filter实现更高效的实现:

(define (quicksort xs)
   (if (null? xs) '()
      (let ((x (car xs))
            (xs' (cdr xs)) )
         (append (quicksort
                    (filter (lambda (x')
                               (< x' x) )
                            xs'))
                 (list x)
                 (quicksort
                    (filter (lambda (x')
                               (>= x' x) )
                            xs'))))))

这种策略在函数式语言中恰好可以简单地表达出来。

在懒惰的Haskell中,单遍历partition实际上比filter两次效率更高。

select :: (a -> Bool) -> ([a], [a]) -> a -> ([a], [a])
select pred (ps, ns) x | pred x    = (x : ps, ns)
                       | otherwise = (ps, x : ns)

partition :: (a -> Bool) -> [a] -> ([a], [a])
partition pred  =  foldl (select pred) ([], [])

quicksort :: Ord a => [a] -> [a]
quicksort []       = []
quicksort (x : xs) = let (lt, gt) = partition (< x) xs
                     in  quicksort lt ++ [x] ++ quicksort gt