不一样的快速排序
快速排序是一个从程序设计基础开始,到数据结构,到算法都会提到的经典例子,常见的做法是取开头元素作为主元,将不大于它的元素放在前面,比它大的放在后面,而前面和后面再次递归调用。
在scheme当中是这个样子的:
1 #lang racket 2 (define (smaller x l) 3 (cond 4 [(null? l) null] 5 6 [(>= x (car l)) (cons (car l) (smaller x (cdr l)))] 7 [else (smaller x (cdr l))])) 8 9 (define (bigger x l) 10 (cond [(null? l) '()] 11 [(< x (car l)) (cons (car l) (bigger x (cdr l)))] 12 [else (bigger x (cdr l))] )) 13 14 15 (define (quicksort l) 16 17 (cond [(null? l) '()] 18 [(not (pair? l)) l] 19 [else 20 (let ( 21 [small (quicksort (smaller (car l) (cdr l)))] 22 [big (quicksort (bigger (car l) (cdr l)))] 23 ) 24 (append small (cons (car l) big)) )] 25 )) 26 27 (define l (list 8 3 2 1 5 4 6 9 9 7)) 28 (display ( quicksort l))
简单的解释一下,先写了两个辅助性的函数:smaller bigger,接受参数为一个参考主元值和一个列表,返回的是那个列表中比参考值小的所有元素的值构成的列表,实际上是一个filter,完全可以用filter来直接做。 注意的一点是,bigger 和 smaller当中的某一个需要包含相等的情况。
quicksort 函数体很简单,如果列表是空的,返回空列表;
如果参数并不是一个列表,而只是一个数,返回这个数;
如果是一个列表,递归定义small 和 big ,然后 执行append small (cons (car l) big) ,也就是先构造 cons (car l) big,将当前主元值和big连接起来,在把它整体接在small的后面。
程序看起来是不是比之前用C++ 写的话简洁很多~
使用filter 的scheme版本:
1 #lang racket
2 (define (quicksort l)
3 (cond [(null? l) '()]
4 [(not (pair? l)) l]
5 [else
6 (let ( [small (quicksort (filter (lambda (y) (>= (car l) y)) (cdr l) ))]
7 [big (quicksort (filter (lambda (y) (< (car l) y)) (cdr l) ))])
8 (append small (cons (car l) big)) )]))
9
10 (define l (list 8 3 2 1 5 4 6 9 9 7))
11 (display ( quicksort l))
比之前看起来简单很多了吧~
如果用haskell 来写的话:
1 myquicksort :: (Ord a) => [a] -> [a] 2 myquicksort [] = [] 3 myquicksort (x:xs) = 4 let small = myquicksort [a | a <-xs,a <= x] 5 large = myquicksort [a | a <-xs,a > x ] 6 in small ++ [x] ++ large
哇,看起来不错~
简单解释一下,第一行是一个类型声明,可以作为排序函数的参数的元素必须是可以比较的,也就是属于Ord类型的
后面是一个简单的分类:
1. 参数是[] ,返回[]
2. 参数是list,用模式匹配分解 x:xs,同样利用filter来实现small 和 large ,这里用了列表推导式。(同样注意,等于的情况要并在小于或者大于之中)
最后将三部分拼接起来。
函数式编程还是比较有魅力的,但其实不只是上面展示的这种简洁,更舒服的是自定义很多语法糖,并且元语法很少,可以快速有效的掌握,而更多的语法糖不仅可以学会使用,还可以知道是如何构造出来的!