递推式(Recurrence)与生成函数(Generating Function)

前言

原文写于2014年3月22日。修订于2018年7月14日。

也是终于完成了这个系列第二篇,在寻找良好素材的时候也无意中发现了不少相关方面的优秀资料,一时兴起而做的决定为自己也带来了诸多益处,这实在令人欣慰.

用生成函数求解递推式是一个非常机械的过程,按照流程一步步做就能得到答案。不过大多数时候也并不像听上 去那么容易—如之前组合恒等式的证明那样,我们需要注意和处理令人厌烦的角标。

本文不期望读者能够掌握什么具体的知识,但如若能够为诸君于某刻解决问题时作一份参考,亦或是能有些许思路上的共鸣,之于我都将是莫大的荣誉。

0.符号约定

[zn]R(z)意为多项式R(z)zn的系数。

我们以<an>作为数列<a1,a2,,an,>的缩写。

condition为真,则我们有[condition]=1,否则为0

没有明确说明的情况下,G(z),F(z)意为数列<gn>,<fn>的生成函数。

1.定义与基本性质

一般来说,给定一个序列<gn>,其生成函数(Ordinary Generating Function)G(z)定义如下:

(1)G(z)=g0+g1z+g2z2+=n0gnzn

尽管定义中的z并没有被指明是一个什么样的东西,它仅仅只是一个符号,或者某种特征的替代表示,但为了讨论方便,也为了迎合我们的需要,我们视G(z)为一个复变量的函数且|z|<1

F(z),G(z)分别为序列<gn>,<fn>的生成函数,通过一些简单的计算—加法,积分,数乘等,我们可以发现:

(2)αF(z)+βG(z)=n(αfn+βgn)zn (3)zmG(z)=ngnmzn (4)G(cz)=ncngnzn (5)G(z)=n(n+1)gn+1zn (6)0zG(t)dt=n11ngn1zn (7)F(z)G(z)=n(kfkgnk)zn

最后一个等式看起来非常的熟悉,在样式上似乎是我们在前一篇所提到过的卷积,应用前面曾提到的些许结论,我们令数列<fk><1,1,>,则可得到

(8)(1+z+z2+...)G(z)=n(kngk)zn(9)1zn1zG(z)=n(kngk)zn(10)11zG(z)=n(kngk)zn

这是一个非常有用的等式,他告诉我们用11z来乘以一个生成函数,会得到其数列的自累加的生成函数。

注意到我们这里用了一些幂级数的基本知识(即F(x)的计算部分),实际上,这也正是我们引入生成函数的原因—通过一些分析的手段去解决离散的,有关于数列的问题。

在这里我们不会用到什么深奥的幂级数知识,但即便如此,生成函数作为工具也已经十分强大。

简单的关于幂级数的知识可以让我们得到许多生成函数的封闭形式,例如调和数列Hn=<0,1,12,13,14,>的生成函数

(11)n11nzn=ln11z

2.与递推式

我们希望借助幂级数来解决一些关于数列的问题,例如通过数列的递推式去求解通项公式,这也是我们引入生成函数的原因,那么它能够胜任么?

我们首先来看一个简单的例子。

ex.2.1 一个经典问题

假设我们有n个数a1,,an,这n个数的乘积需要通过n1次乘法来加以确定,因而我们要问,在乘积a1an中有多少种插入括号的方法 gn,使得乘法的次序被完全指定(我们假定交换律并不成立)。

我们这样来思考这个问题,我们为这n个数不断的添置括号(当然是合法的嵌套形式),直到置完成n个括号,这时候已经不存在游离于括号之外的因子,让我们来看看添置第n1个括号的情形,由于在添置第n1个括号之前我们已经添置了n2个括号,由于每一个括号将两个因子(形式上的)“合成”了一个因子,在添置n2个括号之后,n个因子必然已经被分裂成这样的形式(a1a2ak)(ak+1an),进而我们就能够确定一个关于gn的递推式

(12)gn=g1gn1+g2gn2++gn1g1=k=1n1gkgnk,n2,g1=1

由于递推式并没有g0,因而生成函数以g1作为第一项,即有

(13)G(z)=g1+g2z+=k=1gkzk1

数列gn的递推式是一个卷积式,初看并没有什么头绪,不过我们可以做一些简单的尝试,很容易观察到,如果我们对G(z)进行平方,就能够得到gigj此类的项。

(14)G2(z)=g1g1+g1g2z+g2g1z++gngnz2(n1)+(15)G2(z)=k=11gkg2k+k=12gkg3kz++k=1n1gkgnkzn2+(16)=1z(g2z+gnzn1+)

这样我们很容易就能看出

(17)G(z)=zG2(z)+g1=zG2(z)+1

经由一些简单的代数运算,我们就能得到

(18)G(z)=114z2z

实际上求解上面的代数方程我们会得到关于G(z)的两个封闭表达式,然而其中有一个在z0时得到,尽管我们没有定义n=0的情形,但是对于没有因子的情形,一个空的括号应该表示一种添置括号的方法,因而G(0)=1更加符合我们的逻辑。且有

(19)limx0114z2z=1

之后,只需要运用你在高数课上习得的知识,将G(z)展开为的某一幂级数的形式,其每一项的系数即为数列<gn>的值。

ex.2.2 源于快速排序

我们都知道快排的平均时间复杂度是O(nlog2n),对于这个结果的分析在许多算法相关的书籍上都有介绍,不过今天让我们来看看对于n个随机数进行快速排序总的比较次数的平均情况。

我们假设待排序数列为

(20)a1,,an

选定枢纽元之后,设比枢纽元小的元素数量为k,由于数列完全随机,我们可以认为k是从[1,n1]中均匀采样的。因此,令n个数进行快速排序总的比较次数cn,我们能够建立如下递推式

(21)cn=n1+1nk=1n(ck+cnk)=n1+2nk=1nck

其中第一项的n1为与枢纽元进行比较的开销,而ckcnk分别代表分割之后的子问题所对应的答案。 总的平均比较次数则为不同大小的子问题的平均比较数量。

C(z)<cn>的生成函数,我们发现递推式中有一个自累加的项,因而结合第一节中的结论,我们有

(22)C(z)=c1+n=2(n1+2nk=1nck)zn1(23)C(z)=n=2((n1)n+2k=1nck)zn1(24)=n=2n(n1)zn1+2n=2k=1nckzn1(25)=z(11z)+2C(z)1z(26)=2z(1z)3+2C(z)1z

求解这个微分方程,我们就能得到

(27)C(z)=2ln(1z)2x(1z)2

来看看我们是怎么处理上面这个递推式的,在我们发现可能可以用到之前某些已经得到的结论的时候,2nk=1nck项上的1/n就变成了最大的障碍,因此我们需要消除它,而对C(z)求导无疑就是最直接的选择。

ex.2.3 它看起来奇怪而熟悉

或许更多时候我们遇到的递推式会是这样的

(28)an=4an14an2+(n2)2n+1,a0=a1=1,n2

线性的递推项,和一个可能比较奇怪的尾项,运用我们之前所提到的技术,设A(x)<an>的生成函数,则我们有

(29)an4an1+4an2=n(n1)2n1+1(30)n=2(an4an1+4an2)xn=n=2n(n1)2n1xn+n=2xn

把边界加入进去,我们得到

(31)n=0anxn(a0+a1x)4x(n=0anxna0)+4x2n=0anxn (32)=(14x+4x2)g(x)(1+x)+4x

再来看看右边的情况

(33)n=2n(n1)2n1xn=x22n=2n(n1)2nxn2(34)=x22d2dx2112x

且有

(35)n=2xn=11x(1+x)

因而我们得到

(36)(14x+4x2)g(x)(1+x)+4x=4x2(12x)3+11x(1+x) (37)g(x)=110x+44x284x3+80x432x5(1x)(12x)3(14x+4x2)

这结果简直让人菊花一紧。

2.1 到此为止?

细心一点的读者可能发现,我们可能可以使用在算数・二项式系数与组合恒等式中提到的二项式定理来求解这个ex2.1,注意到

(38)14z=k0(1/2k)(4z)k=1+k112k(1/2k1)(4z)k

从而有

(39)114z2z=k11k(1/2k1)(4z)k1(40)=n0(1/2n)(4z)nn+1(41)=n0(2nn)znn+1

因而得到

(42)gn=(2nn)1n+1

而快速排序所得到的式子看起来更麻烦一些,因为其中有对数,但事实上这真是它简单的地方,因为我们知道

(43)ln(1z)=kzkk

加以之前惯用的技术,我们有

(44)C(z)=2ln(1z)2x(1z)2(45)=k22zkkk0(k+1)zk

经过略显繁琐的代数运算与系数比对,我们能够得到

(46)cn=2(n+1)k=1n1k4n=2(n+1)Hn4n

这也揭示了快速排序的平均时间复杂度为O(nlog2n)

3.一般策略

很多时候,将一个复杂的生成函数展开为幂级数形式是非常困难的,这让人感到沮丧。不过,通过上面的计算我们发现,我们能够经常得到一些形如P(x)/Q(x),其中P(x)Q(x)是两个多项式的生成函数。多项式经常是一种特殊的结构,那么在这里我们是否也有一些特殊的性质呢?

幸运的是,混凝土数学(《具体数学》 by Knuth)上,有一个如下叙述的定理:


若生成函数R(z)=P(z)/Q(z),其中Q(z)=q0(1p1z)d1(1plz)dl{p1,,pl}是不同的实数,且有degree(P(z))<d1++dl,则有

(47)[zn]R(z)=f1(n)p1n++fl(n)pln n0

其中fk(n)是首项系数ak满足以下等式的dk1次多项式

(48)ak=P(1/pk)(dk1)!q0jk(1pj/pk)dj

但书中并没有给出证明,因此我试着证明了一下。

在初等的代数书中介绍了多项式的带余除法的概念,借助带余除法,我们知道上述有理函数R(z)可以分解成以下形式

(49)R(z)=P(x)q0(1p1z)d1(1plz)dl(50)=1q0(A1(z)(1p1z)d1++Al(z)(1plz)dl)

其中Ak(z)是低于dk次的多项式。我们将上式通分,即可得到

(51)jAj(z)kj(1pkz)dk=P(z) (52)jAj(1/pj)kj(1pk/pj)dk=P(1/pj)

11pz=1+pz++pnzn+逐次微分,即可得到

(53)(di1)!(1piz)di=(di1)!++(di+n1)!pnzn+(54)1(1piz)di=1+dipz++dinn!pnzn+

因而将

(55)R(z)=1q0(A1(z)(1p1z)d1++Al(z)(1plz)dl)

逐项进行幂级数展开,则对于每一个展开式中的zn项,其系数为

(56)1q0Ak(z)dknn!pkn

(57)1q01(dk1)!Ak(z)(dk+n1)!n!pkn

因而我们能够知道其系数fk(n)是一个次数为dk1的关于n的多项式。则其首项系数(即 ndk1的系数)ak

(58)1q01(dk1)!Ak(z)

而由

(59)jAj(1/pj)kj(1pj/pk)dj=P(1/pj)

可以看出,对于每一个确定的pj,含有Ak kj项的连乘因子中必有取z=pj的项, 使得乘积为0,因而有

(60)Aj(1/pj)=P(1/pj)kj(1pj/pk)dj

最终在

(61)1q01(dk1)!Ak(z)

z=1/pk,得到

(62)ak=P(1/pk)(dk1)!q0jk(1pj/pk)dj

我们来简单演示一下这个定理的用法,譬如递推式

(63)gn=gn1+2gn2+(1)n[n0]+[n=1]

我们很容易就可求得其生成函数是

(64)G(z)=1+z+z(12z)(1+z)2

所以根据定理,我们知道对于某个常数c,有

(65)gn=a12n+(a2n+c)(1)n

其中

(66)a1=1+1/2+1/4(1+1/2)2=7/9,a2=11+112/(1)=1/3

再将n=0的情形代入,即可得到通项公式为

(67)gn=792n+(13n+29)(1)n

4.后记

生成函数是一项非常实用的工具,在这里我仅仅介绍了其冰山一角。更多的有关于生成函数的知识,只有等到我们遇到相关问题之时再做介绍了。

在这篇部分问题的解答过程中,我也曾走入歧途,一度执迷于创造新奇的方法而忽略了现有的工具,于定理证明上挣扎许久。实际上,如果你无法解出一个问题,那么要么是有一个更简单的问题你未能解决,要么是你仍有工具还未使用。


0 comments
Anonymous
Markdown is supported

Be the first person to leave a comment!