Clojure
快速排序
快排
快速排序(Quicksort)是一种优秀的排序算法,这个就不多介绍了。
本文用最通俗的语言讲解快速排序的原理,以及如何使用 Clojure 实现快速排序。
首先快速排序的原理很简单,不要被那些专业名词吓傻了。什么分治法,什么分析时间复杂度,又搞什么两个指针一会儿这边移动一会儿交换一会儿那边移动。这些对你理解快排的原理毫无帮助,反而会阻碍你理解快排的本质。
快排的本质就一句话:
从需要排序的数里面随便找出一个,然后,把比这个数小的放在这个数左边,比这个数大的放在这个数右边,一样大的和这个数放在一起,最后,左右两边各自重复上述过程,直到左边或右边只剩下一个数(或零个数)无法继续为止。
我们可以明显的看出这是一个递归定义。
先看灵魂画手的示意图:
假如我们要给这五个数字排序,首先我们先从中随意选取一个数字,比如 4。
按照规则,比 4 大的数字都放在右面,比 4 小的数字都放在左面。所以一次过后变成了这样。
按照规则,左右两面各自重复上述步骤,直到数量为一个以下无法继续。右面已经只有一个了,所以不用重复了。左面重复上述步骤。
还是随机从中选取数字,这次我们选择 2。把大于 2 的放在右面,小于 2 的放在左面。
左右两边都已经是一个,不用继续了,排序结束。
那在 Clojure 中如何实现这种算法呢?
我们直接看代码:
(defn quick-sort
[nums]
(if (< (count nums) 2)
nums
(concat
(quick-sort (filter #(< % (first nums)) nums))
(filter #(= % (first nums)) nums)
(quick-sort (filter #(> % (first nums)) nums)))))
这段代码十分浅显易懂。首先定义一个函数,名叫 quick-sort
,它接受一个数字集合。
如果集合中元素的数量小于 2,那么直接返回这个集合。这符合快排的定义。
如果元素数量大于等于 2,我们选取集合的第一个元素作为分割的标准(定义中是随机选取,所以在这里可以根据你的喜好和需要进行选取),使用 filter
函数筛选出小于、大于、等于,三种情况,最后使用 concat
函数进行连接。
一切都是这么自然。
我们来看一下实际运行情况。
首先要生成一些随机数以供排序。这个我们写一个函数来产生:
(defn rand-n-m [n m]
"从 1 到 m 范围内,随机产生 n 个数字"
(repeatedly n #(+ 1 (rand-int m))))
生成 1 到 100 内的随机数 10 个:
=> (println (rand-n-m 10 100))
(25 42 4 34 8 35 93 47 58 16)
还不错,我们来看看排序算法工作如何:
=> (println (quick-sort (rand-n-m 10 100)))
(10 16 18 35 41 47 57 83 86 90)
刷的一下就出来了。
不过就 10 个数字显露不出我们快排的威力,这次我们上多点数字。并在外侧套上 time
统计运行时间。
=> (time (quick-sort (rand-n-m 10000 100000)))
"Elapsed time: 144.926382 msecs"
旁边的冒泡排序依然在吭哧吭哧算着呢...
P.S..
为啥 C 、Java 等一批语言写快排怎么搞了一个数组两个指针循环过来循环过去?
这个你们自己思考,声明式编程和命令式编程的区别。