写快速排序几乎是很多人被Haskell圈粉的第一瞬间,然而可能很多人一开始的实现并不是最优的。此处记录了我的心路历程。
最初的实现
quick_sort :: Ord a => [a] -> [a]
quick_sort [] = []
quick_sort [x] = [x]
quick_sort xs = quick_sort [xs !! i | i <- [0..len-1], xs !! i <= mid_num && i /= half_pos]
++ [mid_num]
++ quick_sort [xs !! i | i <- [0..len-1], xs !! i > mid_num && i /= half_pos]
where mid_num = xs !! half_pos
half_pos = floor $ fromIntegral (len / 2)
len = length xs
使用filter
上面的代码存在两点可以改进的东西:
1.使用filter函数取得比中值小的所有数,语法比列表解析式更加简洁
2.如果数据是随机分布的,可以不必取中位元素,取开头元素即可
quick_sort :: Ord a => [a] -> [a]
quick_sort [] = []
quick_sort [x] = [x]
quick_sort xs = quick_sort (filter (< mid_num) xs)
++ quick_sort (filter (== mid_num) xs)
++ quick_sort (filter (> mid_num) xs)
where mid_num = head xs
张淞老师的书里面写得更简单:
quick_sort :: Ord a => [a] -> [a]
quick_sort [] = []
quick_sort [x] = [x]
quick_sort (x:xs) = quick_sort (filter (<= x) xs)
++ [x]
++ quick_sort (filter (> x) xs)
只需要filter一次
进一步地,上面的算法每次分割对列表扫描2次(两次filter),但实际上一次就够了(两部分列表各为补集)。实现如下:
sep :: Ord a => a -> [a] -> ([a], [a])
sep _ [] = ([], [])
sep sep_num (x:xs) | x <= sep_num = ([x] ++ fst sep_xs, snd sep_xs)
| otherwise = (fst sep_xs, [x] ++ snd sep_xs)
where sep_xs = sep sep_num xs
quick_sort :: Ord a => [a] -> [a]
quick_sort [] = []
quick_sort [x] = [x]
quick_sort (x:xs) = quick_sort (fst sep_tuple)
++ [x]
++ quick_sort (snd sep_tuple)
where sep_tuple = sep x xs
优雅地解决问题
几个月后又回顾这段代码:
- 善用模式匹配!
(l, r)
的写法比fst, snd
的写法好很多。 - 善用标准库!
Data.List.partition
提供了sep
的功能,没必要reinvent - 对代码里面的冗余要敏锐!
quick_sort [x] = [x]
完全没有必要。 - 使用默认构造器提高效率!
[x]++xs
不如x:xs
效率高。
import Data.List
quickSort :: Ord a => [a] -> [a]
quickSort [] = []
quickSort (x:xs) = quickSort l ++ (x : quickSort r) where
(l, r) = partition (<x) xs
写好之后还要quickcheck一下成果:
import Test.QuickCheck
sorted :: Ord a => [a] -> Bool
sorted [] = True
sorted (x:[]) = True
sorted (x:y:xs) = x <= y && sorted (y:xs)
prop_sort :: Ord a => [a] -> Bool
prop_sort xs = sorted (quickSort xs)
成功通过
*Main> :l sort.hs
[1 of 1] Compiling Main ( sort.hs, interpreted )
Ok, modules loaded: Main.
*Main> quickCheck prop_sort
+++ OK, passed 100 tests.
完整的代码
import Data.List (partition)
import Test.QuickCheck
-- qsort
quickSort :: Ord a => [a] -> [a]
quickSort [] = []
quickSort (x:xs) = quickSort l ++ [x] ++ quickSort r where
(l, r) = partition (<x) xs
sorted :: Ord a => [a] -> Bool
sorted [] = True
sorted (x:[]) = True
sorted (x:y:xs) = x <= y && sorted (y:xs)
-- check
prop_sort :: Ord a => [a] -> Bool
prop_sort xs = sorted (quickSort xs)
-- quickCheck prop_sort