在Scheme中编写最小值过程的另一种方法是什么?

时间:2013-09-25 04:35:25

标签: scheme

所以,如果我有以下内容,它返回一组四个数字中的最小值:

(define (minimum2 a b c d)
  (cond ((and (< a b) (< a c) (< a d)) a)
        ((and (< b c) (< b d)) b)
        ((< c d) c)
        (else d)))

但是,我想写它以便我比较a到b并找到这两者之间的最小值,然后比较c和d,找到它们之间的最小值,然后将这两个最小值进行比较以找到实际的最小值。如果我写的内容很难理解,可以把它想象成一个锦标赛支架,其中“播放”b,而获胜者则扮演c和d之间的另一个赢家。提前感谢您的帮助!

2 个答案:

答案 0 :(得分:2)

这是一种方法:

(define (min4 a b c d)
  (define (min2 x y)
    (if (< x y) x y))
  (min2 (min2 a b) (min2 c d)))

另一种方法,如果你不想使用内部函数:

(define (min4 a b c d)
  (let ((min-ab (if (< a b) a b))
        (min-cd (if (< c d) c d)))
    (if (< min-ab min-cd) min-ab min-cd)))

答案 1 :(得分:2)

以下是两种方法。我认为第一个使用reduce更加惯用,但它没有采用锦标赛风格结构,尽管它使用了相同数量的比较。第二个是锦标赛风格结构,实际上只是一个广义合并排序的特例。比较次数相同的原因是锦标赛风格比较,

  

min(a,b,c,d)= min(min(a,b),min(c,d))

并在reduce表述中,

  

min(a,b,c,d)= min(min(min(a,b),c),d)

两者都需要三次调用最低级别的min程序。

基于reduce的方法

根据我的经验,此解决方案使用fold(在Lisp语言中通常称为reduce)。 Scheme(R 5 RS)不包括reduce或折叠,但它很容易实现:

(define (reduce function initial-value list)
  (if (null? list)
      initial-value
      (reduce function (function initial-value (car list))
              (cdr list))))

左关联折叠是尾递归且高效的。给定二进制函数 f ,初始值 i ,列表[ x 1 ,..., x n ],它返回f(f(... f(f( i x 1 ), x 2 )...), x n -1 ), x n )。

在这种情况下,二进制函数是min2。 R5R5实际上已经包含了一个 n -ary(好吧,它实际上至少需要一个参数,它是 at-least-one -ary)min,这意味着min已经可以作为二元函数使用了,但是如果你想使用内置的min,那么你首先要做(min a b c d)。所以,为了完整起见,这里有一个min2,它只接受两个参数。

(define (min2 a b)
  (if (< a b)
      a
      b))

然后我们的 n -ary min*只是在初始值和列表上减少min2。我们可以在参数列表中使用.表示法使其成为需要至少一个参数的可变参数函数。这意味着除了更典型的多参数调用之外,我们还可以(min* x) => x

(define (min* a . rest)
  (reduce min2 a rest))

例如:

(min* 4 2 1 3)
;=> 1

基于合并排序的真正的锦标赛式解决方案

正确的锦标赛风格min实际上与merge sort同构。合并排序递归地将列表拆分为一半(这可以使用原始列表的索引就地完成,而不是实际将列表拆分成新列表),直到产生长度为1的列表。然后相邻列表合并以生成长度为2的列表。然后,合并长度为2的相邻列表以生成长度为4的列表,依此类推,直到只有一个排序列表。 (如果输入列表的长度不是2的幂,则这里的数字并不总是完美的,但是同样的原则适用。)如果你编写一个合并排序的实现,它将合并函数作为参数,然后你可以让它返回包含较小值的一个元素列表。

首先,我们需要一个将列表拆分为左侧和右侧的函数:

(define (split lst)
  (let loop ((left '())
             (right lst)
             (len (/ (length lst) 2)))
    (if (< len 1)
        (list (reverse left) right)
        (loop (cons (car right) left)
              (cdr right)
              (- len 1)))))
> (split '(1 2 3 4))
((1 2) (3 4))
> (split '(1))
(() (1))
> (split '(1 2 3))
((1) (2 3))

合并排序现在很容易实现:

(define (merge-sort list merge)
  (if (or (null? list) (null? (cdr list))) 
      list
      (let* ((sides (split list))
             (left (car sides))
             (right (cadr sides)))
        (merge (merge-sort left merge)
               (merge-sort right merge)))))

我们仍然需要合并程序。我们需要一个可以采用两个列表的列表,而不是采用两个列表并返回其已排序元素列表的标准列表,其中每个列表最多包含一个元素,并且最多一个列表可能为空。如果任一列表为空,则返回非空列表。如果两个列表都是非空的,则返回具有较小元素的列表。我称之为min-list

(define (min-list l1 l2)
  (cond
    ((null? l1) l2)
    ((null? l2) l1)
    (else (if (< (car l1) (car l2))
              l1
              l2))))

在这种情况下,您可以定义min*来调用merge-sort,其中合并过程为min-list。 Merge-sort将返回包含一个元素的列表,因此我们需要car从列表中获取该元素。

(define (min* a . rest)
  (car (merge-sort (cons a rest) min-list)))
(min* 7 2 3 6)
;=> 2