Haskell拓展篇:斐波那契数列

Posted Lambda小粽子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Haskell拓展篇:斐波那契数列相关的知识,希望对你有一定的参考价值。

斐波那契额数列,又称黄金分割数列,是著名数学家Leonardoda Fibonacci引入的。这个数列的特点是,除了前两个数字,每一个数字都是其前两项的和:


1123581321345589144, …


根据我们的定义,我们可以得到F_n = F_n-1 + F_n-2这个公式。可能大家有所了解这个序列是有通项公式的,不过今天我们的重点并不在此,今天我来带领大家用Haskell来编写一个程序,计算斐波那契额数列。 


根据定义我们一开始可以把函数定义为一下这个样子:这是一个从整数Int映射到Int的函数。



然而这样的定义是有很大的问题的。比如在我们运算fib 5的时候,函数会计算fib 4  fib 3的值,然而在计算fib 4的时候其实已经计算了fib 3的值了,也就是说fib 3的值其实被计算了两次。当输入的变量并不大的时候还并不明显,然而当我们计算很大数值的fib值时(其实大多数电脑计算fib 100的时候就已经需要等很长很长时间了)就会浪费很多的时间造成巨大的运算浪费,究其原因还是普通递归在解决这一类的问题时,对subpart进行了多次的重复的计算。


说到这里可能大家就想到了,当我们解决这一类的问题时,比较好的解决方法是动态规划(dynamic programming)。简单来说,动态规划的思想就是从下而上,将每一个sub part只计算一次,并用subpart的计算结果进行更上一层的计算。在本问题中我们知道F_n = F_n-1 + F_n-2,反过来也就是如果我们已知F_n-1F_n-2,那我们就能很轻松的求出F_n。让我们以F_5为例,我们已知F_1 = 1, F_2 = 1,接下来我们可以计算出 F_3 = 2F_4 = 3,最后求出F_5 =  5。更多的动态规划的思想我会在之后的文章中详细提到。


如此我们可以将我们的函数设计成以下这样:


Haskell拓展篇:斐波那契数列


这个函数中我们使用了三个Haskell内置的函数和操作符号。(:)是Haskelllist的构造符号,它的type a->[a]->[a],将一个element和这个element类型的list连接起来构成一个新的element类型的listtail函数式取list的尾部,如tail [1,2,3,4] = [2,3,4]zipWith是一个比较复杂的函数,我们也会在之后有详细的介绍。这个函数接收一个函数和两个list作为变量,将两个list中的element根据接收的函数依次进行计算,然后生成一个新的list。举个例子,zipWith (+) [1,2,3] [10,20,30] =  [11,22,33]。那么这个函数理解起来就简单了,它先是声明了fib数列的前两项,然后之后的项由其前两项相加而来。更加具体化的例子是,fib2 = [0,1,a,b,c…..]tail fib2 = [1,a,b,c….],所以a = 0 + 1 = 1,然后我们更新上面的序列fib2 = [0,1,1,b,c…..]tail fib2 = [1,1,b,c….],所以b = 1 + 1 = 2,以此类推。在这个例子中,在我们计算F_n时,我们先依次将F_1F_n-1的数全部计算了出来,由此得出F_n的值。

由于还没有系统的讲过Haskell中的list,所以上面那一块可能大家体会动态规划不是很明显,接下来的定义就会比较的清晰了:每次我们都只记录两个数(a,b),然后h的作用是(a,b) => (b,a+b),其实就是(F_n-2, F_n-1=> (F_n-1,F_n-2+F_n-1) = (F_n-1,F_n),每将h apply在这个tuple上一次时我们就会得到下一层的Fib值,而我们要做的就是根据输入的inputh apply在初始tuple(1,1)上几次,如此一来,如果我们想要计算Fib n的值,我们只需要运算n遍就能得到我们想要的结果了。




这一讲的内容涉及到很多还没有细细提及的知识,之后我会将其中的内容仔细分章节的讲给大家。


编辑:以月


注:本文为原创内容,转载请标明出处。



以上是关于Haskell拓展篇:斐波那契数列的主要内容,如果未能解决你的问题,请参考以下文章

2021/6/10 刷题笔记斐波那契数列+泰波那契数

13.斐波那契数

算法动态规划 - 斐波那契数

2834 斐波那契数

斐波那契数列(递归非递归算法)

509. 斐波那契数