红黑树是众多平衡二叉搜索树数据结构中比较复杂的一种,而红黑树的删除操作更是出了名的难写。
尽管实现复杂,在实际工程中红黑树却有着广泛应用(STL map, Java TreeMap, Linux Kernel),很多教科书(CLRS)中也有所介绍。
网上大多数红黑树实现大多很冗长,或者缺少删除操作的实现。
本文将用函数时编程语言Haskell,42行代码实现红黑树的插入与删除。
阅读本文不需要Haskell与红黑树基础知识,但需要对二叉搜索树算法有基本了解。
对Haskell有基本了解(会写quicksort)的读者可以跳过Haskell 基础。
Haskell 基础
环境
安装ghcThe Glasgow Haskell Compilerwww.haskell.org
Hello world
# a.hs
main = putStrLn "hello world"
运行
$ runghc a.hs
hello world
也有像python一样interactive console
$ ghci
Prelude> putStrLn "hello"
hello
也可以编译成可执行文件
$ ghc a.hs -o a
$ ./a
hello world
List
Prelude> [1,2,3]
[1,2,3]
Prelude> [1..10]
[1,2,3,4,5,6,7,8,9,10]
Haskell的List是单向链表。[1..10]表达式有点像python的range。
head可以获取list的第一个元素,tail函数返回第一个元素之后的list,相当于获得单向链表头节点的value与next
Prelude> head [0..5]
0
Prelude> tail [0..5]
[1,2,3,4,5]
List Comprehension
Haskell List也有类似Python的List Comprehension。Haskell的语法有点像数学里集合的表示。
[x | x
等同于python里的
[x for x in range(10)]
多层循环也可以
[(x,y) | x
相当于Python里
[(x,y) for x in range(4) for y in range(4)]
(Haskell也有类似Python中的Tuple)。
也可以加上条件过滤
[x | x
相当于Python里
[x for x in range(10) if x % 2 == 1]
"++"运算符可以连接两个list
Prelude> [0..2] ++ [3..5]
[0,1,2,3,4,5]
函数与模式匹配(Pattern Matching)
Haskell函数定义与模式匹配密不可分。
阶乘函数在Haskell中可以这么实现:
fact 0 = 1
fact n = n * (fact (n - 1))
函数调用时,参数不需要用括号括起来。
Prelude> fact 3
6
Haskell调用函数时,按顺序尝试匹配定义中的参数。第一个匹配上的参数对应的定义会被采用。
调用fact 3,匹配fact 0失败(3!=0),匹配fact n成功(n可以匹配任何数),于是返回3 * fact 2。
递归调用fact 2,匹配fact 0失败,匹配fact n成功,返回2 * fact 1。
递归调用fact 1,匹配fact 0失败,匹配fact n成功,返回1 * fact 0。
递归调用fact 0,匹配fact 0成功,返回1。
一路回溯到fact 3,最终返回6。
两行顺序不能颠倒,不然"fact n"将匹配任何参数,而"fact 0"永远不会被匹配到。
Haskell自带大整数支持
Prelude> fact 100
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
类似的方法可以实现斐波那契数列
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
读者可能注意到这个斐波那契数列的实现是
的。下面的方法可以在
时间内计算斐波那契数列
helper 0 a _ = a
helper n a b = helper (n - 1) b (a + b)
fib n = helper n 0 1
第一次接触函数编程的读者可以多花一些时间思考一下。
另外对于快速计算斐波那契数列算法感兴趣的读者可以阅读这篇文章。Fast Fibonacci algorithmswww.nayuki.io