【问题标题】:Quicksort in Scheme using a partition使用分区的方案中的快速排序
【发布时间】:2022-07-01 13:57:15
【问题描述】:

我有一个用于快速排序的分区:

(define (partition pivot lst)
  ((lambda (s) (s s lst list))
   (lambda (s l* c)
     (if (null? l*)
         (c '() '())
         (let ((x (car l*)))
           (s s (cdr l*)
              (lambda (a b)
                (if (< x pivot)
                    (c (cons x a) b)
                    (c a (cons x b))))))))))

partition code source

测试:

 =>(partition '5 '(1 3 5 7 9 8 6 4 2))
;Value: ((1 3 4 2) (5 7 9 8 6))

如何在快速排序中实现此分区?到目前为止我已经尝试过了:

(define (quicksort lst)
  (if (null? lst) '()
    (let* ((y (car lst))
           (pn (partition y (cdr lst))))
      (append (quicksort (car pn))      
              (list y)                  
              (quicksort (cdr pn))))))

【问题讨论】:

  • 如果您将cdr 更改为cadr,它看起来应该可以工作。
  • listcons
  • 您或许应该通过使用递归而不是依赖看起来像是在最大限制下完成的代码来使您的partition 更具可读性。变量命名有助于提高可读性,我在调用枢轴y 时看不到任何逻辑。

标签: sorting scheme partitioning


【解决方案1】:

拥有partition 后,还有一小步要做。

请注意,您需要确保partition 始终将输入拆分为较小的集合。换句话说,partition 不返回一些空集。枢轴可以进入任何集合并使用此事实来检查您是否返回空集合,以防您的比较运算符没有真正减小输入的大小。这就是我插入相等运算符的原因——以便能够检查我是在第一个返回的集合中还是在第二个返回的集合中插入枢轴。

(define (partition pivot lst ret)
  ((lambda (s)
     (s s lst
        (lambda (a b p*)
          (if (and (null? a) (null? b))
              (ret (list pivot) (cdr p*))
              (if (null? a)
                  (ret p* b)
                  (if (null? b)
                      (ret a p*)
                      (if (< (car b) pivot)
                          (ret a (append p* b))
                          (if (< (car a) pivot)
                              (ret (append a p*) b)
                              (error "never here")))))))))
   (lambda (s l* c)
     (if (null? l*)
         (c '() '() '())
         (let ((x (car l*)))
           (s s (cdr l*)
              (lambda (a b p*)
                (if (= x pivot)
                    (c a b (cons pivot p*))
                    (if (< x pivot)
                        (c (cons x a) b p*)
                        (c a (cons x b) p*))))))))))

    
(define choose-pivot car)

在实际实现中,您将一直使用向量,这就是为什么append 不会出现的原因,因为在partition 末尾的地方排序,两边将相对排序给对方。在这里,我们需要使用append重新组装两个面:

(define (quicksort lst)
  (if (null? lst) '()
      (if (null? (cdr lst))
          lst
          (let* ((pivot (choose-pivot lst)))
            (partition pivot lst
                       (lambda (p< p>)
                         (append
                           (quicksort p<)
                           (quicksort p>))))))))

测试:

1 ]=> (quicksort '(1 3 5 7 9 8 6 4 2))
;Value: (1 2 3 4 5 6 7 8 9)

1 ]=> (quicksort '(1 9 3 8 5 7 7 6 9 5 8 4 6 3 4 2 2 1))
;Value: (1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9)

我将输入的第一个元素用作要拆分的元素,但您可以重新定义 choose-pivot 以选择其他元素。

实际上,此算法与其他排序结合使用——当输入的元素少于 4-8 个时,quicksort 不再重复,但其他排序用于递归关系的最低情况.

我在代码中直接使用了&lt;——你可以将它作为参数插入,以防你喜欢更通用的过程......无论如何,你使用的运算符需要模拟相等和不同的同时出现。

更新我已经更新了partition,以便考虑重复的元素。在我的第一个版本中,它忽略了重复的元素。

【讨论】:

  • 这更像是一种合并排序而不是快速排序(公平地说,它更适合排序列表;保存向量的快速排序)
  • @Shawn 哦,你是对的 :)。我已经忘记了。谢谢!作为原型而不是真正的实现,只是为了表达算法,用列表没关系。
  • Q 中的代码没有这样的问题,因为它将汽车作为枢轴分开,并将 cdr 分区。
  • partition 的顶部延续中,(&lt; (car b) pivot) 的情况永远不会成立,因为b 中的所有元素都大于pivot。此外,如果partition 的输入列表为空,(ret (list pivot) (cdr p*)) 将导致错误,因为p* 也将为空。是的,您的qicksort 永远不会拨打这样的电话,但用户可能会,因为partition 是全球可用的。另一件事是,你坚持要建立两个列表供ret 使用,但最好让它按原样处理这三个列表,因此顶部延续变为(lambda (a b p*) (ret a p* b))...
  • ...然后在quicksort 调用变为(partition pivot lst (lambda (p&lt; p= p&gt;) (append (quicksort p&lt;) (append p= (quicksort p&gt;)))))
【解决方案2】:

首先,您的代码通过将cdr 更改为cadr 来轻松修复:

(define (partition pivot lst)
  ((lambda (s) (s s lst list))
   ......))           ; ^^^^  `list` the top continuation

(define (quicksort lst)
  (if (null? lst) '()
    (let* ((y (car lst))
           (pn (partition y (cdr lst))))
      (append (quicksort (car pn))      
              (list y)                  
              (quicksort (cadr pn))))))
                     ;;   ^^^^  cdr --> cadr

因为partition 中使用的顶部延续是list,所以调用

  (partition pivot lst)

相当于调用

  (list { x IN lst SUCH THAT x < pivot }
        { x IN lst SUCH THAT x >= pivot } )

{...} 中的部分是伪代码,我们不关心实现,只关心结果)

因此要访问由partition 构建的列表的两个部分,您需要使用carcadr

或者你可以将cdr 保留在quicksort 中代码的访问部分,如果你想将顶部延续更改为cons

(define (partition pivot lst)
  ((lambda (s) (s s lst cons))
   ......))           ; ^^^^  `cons` the top continuation

(define (quicksort lst)
  (if (null? lst) 
    '()
    (let* ((y (car lst))
           (pn (partition y (cdr lst))))
      (append (quicksort (car pn))      
              (list y)                  
              (quicksort (cdr pn)))))) ; cdr works fine with cons

这是因为编程中的一般原则,用于构建数据的函数决定了哪些函数将用于访问该数据:

    (list   <A>   <B>  )
            car   cadr

    (cons   <A>   <B>  )
            car   cdr

(这种特殊的对应关系是因为(list &lt;A&gt; &lt;B&gt;)(cons &lt;A&gt; (cons &lt;B&gt; '()))相同,(cadr &lt;C&gt;)(car (cdr &lt;C&gt;))相同:)

    (list  <A>         <B>     )
 =
    (cons  <A>   (cons <B> '()))
           car   cdr
                       car

当然,现代标准认为这种编码方式不必要地令人费解,因为它通过参数传递和重用模拟递归,就像著名的 Y 组合器一样, -- 但是任何名副其实的 Scheme 都已经支持递归了。

所以这个partition通常会写成完全等效但更易读的“命名let结构,

(define (partition pivot lst)
  (let s ( (l*   lst)      ; first `l*` is `lst`
           (c    cons) )   ; top   `c`  is `cons`
     (if (null? l*)
         (c '() '())
         (let ((x (car l*)))
           (s (cdr l*)
              (lambda (a b)
                (if (< x pivot)
                    (c  (cons x a)  b)
                    (c  a   (cons x b)))))))))

除了名称loop 在这里通常用来代替s(它本身很可能是“self”的缩写)。

但您的快速排序/分区对的真正问题是算法

是的,我说的是pair(当然不是cons 意义上的),因为两者是一起的。一个的实现决定了另一个的实现——在两个方向。 partition 的代码决定了 quicksort 的代码,或者如果我们先写了 quicksort,我们需要以相应的方式实现 partition——这样两个 一起工作。这意味着quicksort 确实产生了正确的结果,将任何输入列表转换为排序列表:

    (quicksort lst) --->  { xs SUCH THAT
                                 FOR ANY splitting xs = { ..., x, ...ys }
                                 AND ANY splitting ys = { ..., y, ...   }
                                 IT HOLDS THAT x <= y
                               AND ALSO xs is a permutation of lst 
                                 (which implies (length lst) == (length xs))
                          }

那么,是什么麻烦呢?真正的 quicksort 在分区后没有任何作用。无:

(define (quicksort lst)
  (if (null? lst) 
    '()
    (let* ((y (car lst))
           (pn (partition y (cdr lst))))
              (quicksort (car pn))         ; no `append`, NB!
              ;;                           ; no (list y) either
              (quicksort (cdr pn)))))

这怎么可能?什么样的partition 实现会让这成为可能?好吧,肯定不是功能性的。

相反,它必须以某种方式改变(即变异)非常lst本身

     { a, b, c, ....., k, l, m, ..... }
   -->
     { d, e, ....,   p,   n, o, ..... }  
       ~~~~~~~~~~~        ~~~~~~~~~~~

我们用p 表示分区点——所以实际上在这种类型的分区之后剩下的就是对第一部分进行排序——然后对第二部分进行排序——之后就没有什么可做的了!这是托尼·霍尔最初的表述中的关键见解:

  TO SORT
     { a, b, c, ....., k, l, m, ..... }  DO
  PARTITION:
     { d, e, ....,   p,   n, o, ..... }  
       ~~~~~~~~~~~        ~~~~~~~~~~~
        SORT!               SORT!
                 DONE.

这种分区通常是用swap! 实现的,它实际上交换两个元素底层数据结构中。大多数情况下,该数据结构是一个数组,它可以在任何给定索引处更改存储在其中的值。

但它也可以是一个列表,其中的更改,即 mutation 可以使用 set-car! 原语来完成。

看起来我们需要从输入列表中构建一个cdrs 列表,然后再反向构建一个列表,以实现这一目标。

我将把它留到另一天,暂时。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-24
    • 2019-07-19
    • 1970-01-01
    • 2015-10-28
    • 2018-09-13
    • 2013-12-08
    相关资源
    最近更新 更多