算法分析中递推式的一般代数解法

2024-05-04 23:18

本文主要是介绍算法分析中递推式的一般代数解法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

貌似看不清楚:http://blog.codinglabs.org/articles/linear-algebra-for-recursion.html

算法分析中经常遇到需要求解递推式的情况,即将递推式改写为等价的封闭形式。例如汉诺塔问题的时间复杂度递推形式为 T(n)=2T(n1)+1(n1) ,可以解出封闭形式为 T(n)=2n1 (设初始状态 T(0)=0 )。

因为递推式求解的重要性,许多算法书籍对其有专门介绍。Donald Knuth在Concrete Mathematics一书中多个章节都涉及递推式求解方法。算法导论也在第四章中专门论述的这个主题。

在这些相关论述中,主要介绍了一些启发式方法,这些方法往往需要一些特殊的技巧和灵感才能完成。

而本文将论述一种纯代数式的方法,这种方法将求解递推式转化为求解一个多项式的根和求解一组线性方程组,这样就使得整个求解过程不依赖于太多技巧,因此具有更好的易用性。

本文首先会给出两个例子:如何使用纯代数方法求解斐波那契数列和汉诺塔递推式;然后会借助线性代数论述这种方法背后的数学意义,说明线性递推式与线性方程的内在联系以及这种解法的数学原理;最后将例子中的方法推广到一般情况。

示例

例1:斐波那契数列

斐波那契数列大家应该很熟悉了,这里不再赘述,直接进入问题。

问题

设斐波那契数列为由如下递推式定义的数列:

T(0)T(1)T(n)===01T(n2)+T(n1)(n2)

求解 T(n) 的封闭形式(也就是斐波那契数列的通项公式)。

求解

首先忽略初始条件,考虑递推式 T(n)=T(n2)+T(n1) 。可以对解的形式进行一个猜测 T(n)=qn (这个不是瞎猜的,实际上可以证明线性递推式都遵循这种形式)。那么,递推式可以重写为:

T(n)=T(n2)+T(n1)qn=qn2+qn1q2=1+qq2q1=0

这样问题被转化为一个一元二次方程的求根问题。利用求根公式可得:

q=1±52

因此得到递推式的一个通解:

T(n)=c1(1+52)n+c2(152)n

即其中 c1 c2 为任意实数。下一步要代入初始条件解出 c1 c2 。根据n为0和1时的初始条件,可得:

c1+c2c1(1+52)+c2(152)==01

解得 c1=15 c2=15 。因此最终解为:

T(n)=15((1+52)n(152)n)

例2:汉诺塔

汉诺塔的时间复杂度通常使用递归式定义,在这个例子中将使用代数方法求解其封闭形式。

问题

汉诺塔的时间复杂度为 T(n)=2T(n1)+1 ,求解其封闭形式。

求解

这里并不能直接使用例1中的方法,因为右边除了递推项外,还有一个非递推项1,用线性代数的语言说,这个线性递推式是非齐次的。

可以回想一下线性代数中求解非齐次方程组通解的方法:1)求解其齐次部分的通解。2)求其一个特解,将特解加到通解上即得非齐次方程组通解。

我们用类似的方法求解汉诺塔时间复杂度递推式。首先,忽略后面的1,则得到一个齐次线性递推式:

T(n)=2T(n1)

转化为多项式方程:

T(n)=2T(n1)qn=2qn1q=2

因为方程是一次多项式,我们直接得到了其解为2。因此齐次递推式的通解为 c2n ,其中c为任意常数。

然后我们需要求得 T(n)=2T(n1)+1 的一个特解,解是一个以n为变量的函数。我们可以先从常数试起,设特解为 T(n)=a ,带入得 a=2a+1 , 解得 a=1 。因此,原递推式的通解为:

T(n)=c2n1

最后我们求解常数c。

将初始条件 T(0)=0 带入,得 0=c1 ,因此 c=1 。代入原通解,得汉诺塔时间复杂度递推式的封闭形式为:

T(n)=2n1

数学原理

上面两个例子可能有些同学看的不是很明白,其中提到了一些线性代数术语。在这一章节中,我们分析上述解法的数学原理,看看递推式是如何与线性代数关联起来的。

线性递推式的一般化

斐波那契数列和汉诺塔递推式可以看成线性递推式的特例,下面给出线性递推式的一般定义:

我们将满足如下递推关系的递推式称为线性递推式:

T(n)=a1T(n1)+a2T(n2)++ak1T(nk+1)+akT(nk)+C(n)

其中 C(n) 是只与n有关系的一个函数。如果 C(n)=0 ,则称递推式为齐次此,否则称为非齐次的。齐次递推式一定有平凡解 T(n)=0

注意仅有递推式是不能求得 T(n) 的唯一解,因此递推关系式只能给出一个通解。只有当下列初始条件确定后,才有可能给出 T(n) 的唯一特解。

T(0)=b0T(1)=b1T(k1)=bk1

齐次递推式求解定理

下面先考虑齐次线性递推式的求解。定理如下:

设有线性齐次递推式 T(n)=a1T(n1)+a2T(n2)++ak1T(nk+1)+akT(nk)

另设多项式方程 qka1qk1ak1qak=0(q>0,ak0) 的根是  q1,q2,,qk ,我们先讨论不存在重根的情况,也就是说k个根互不相等。

T(n) 的通解为:

T(n)=c1qn1+c2qn2++ckqnk

并且对于任意的初始情况

T(0)=b0T(1)=b1T(k1)=bk1

都存在一组解 c1,,ck 使得递推式成立

定理的证明

要证明以上定理,主要需要证明两部分。一是证明多项式根的线性组合可以满足递推式,二是证明任意初始条件下总有解。

可满足性证明

首先来证明 T(n)=c1qn1+c2qn2++ckqnk 可以满足递推式。

T(n)=a1T(n1)+a2T(n2)++ak1T(nk+1)+akT(nk) 经过变换可以改写为  T(n)a1T(n1)a2T(n2)ak1T(nk+1)akT(nk)=0

假设 T(n)=qn ,因为 q>0 ,所以两边除以 qnk ,得到 qka1qk1ak1qak=0

因此这个多项式和原递推式同解,因此多项式的每个根q的几何级数 qn 都是原递推式的一个解。同时,根的线性组合  c1qn1+c2qn2++ckqnk 均满足原递推式(可以带入验证)。

任意初始值有解证明

下面要证明对于任意初始条件,均存在适当的常数 c1,,ck

T(0)=b0T(1)=b1T(k1)=bk1

带入通解公式,得到一个线性方程组

c1+c2++ckc1q1+c2q2++ckqkc1q21+c2q22++ckq2kc1qk11+c2qk12++ckqk1k====b0b1b2bk1

此时问题转化为证明此方程组对于必然有解,下面就要用到线性代数的知识了。这个方程组的系数行列式为:

det(V)=1q1q21qk111q2q22qk121qkq2kqk1k

这个行列式就是非常著名Vandermonde行列式,所以

det(V)=1i<jk(qiqj)

上面我们假设了多项式各个根均互异,因此行列式的值不等于0,这意味着系数矩阵的秩为k。有线性代数知识可知,这表明 对于任意初始值 b0,,bk1 ,方程组均有唯一解。

证毕。

顺便说一下,上面的多项式叫特征多项式。其根叫特征根。

通用解法

通过上面的数学分析,我们得到了一个解线性递推式的通用方法。

齐次递推式

设有齐次递推式

T(n)=a1T(n1)+a2T(n2)++ak1T(nk+1)+akT(nk)

我们可以写出其特征多项式方程

qka1qk1ak1qak=0(q>0,ak0)

解出其k个根 q1,,qk 。如果k个根互异(但可以有复根),则原递推式的通解为

T(n)=c1qn1+c2qn2++ckqnk

然后将初始条件 b0,,bk1 带入组成线性方程组

x1+x2++xkx1q1+x2q2++xkqkx1q21+x2q22++xkq2kx1qk11+x2qk12++xkqk1k====b0b1b2bk1

解线性方程组得唯一解 x^1,,x^k 。带回通解公式则得到递推式的最终解

T(n)=x^1qn1+x^2qn2++x^kqnk

非齐次递推式

对于非齐次递推式

T(n)=a1T(n1)+a2T(n2)++ak1T(nk+1)+akT(nk)+C(n)(C(n)0)

可以首先按上面的方法求解其齐次部分的通解 Tn 。然后求得其一个特解 yn ,则非齐次递推式的通解为

Tn+yn

然后用同样的方法带入初始值,通过线性方程组求出个常量参数带回即可(具体可参见例2)。

有重根的情况

上面的解法只针对特征根互异,如果有重根的话,则上述方法会无效。不过只要经过一定处理也可以有通用方法求解, 因有点复杂,本文不在针对重根情况进行叙述。关于重根情况下的求解,有感兴趣的同学可以参考线性代数或微分方程相关文献。

相关阅读:

这篇关于算法分析中递推式的一般代数解法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/960312

相关文章

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

Spring、Spring Boot、Spring Cloud 的区别与联系分析

《Spring、SpringBoot、SpringCloud的区别与联系分析》Spring、SpringBoot和SpringCloud是Java开发中常用的框架,分别针对企业级应用开发、快速开... 目录1. Spring 框架2. Spring Boot3. Spring Cloud总结1. Sprin

Spring 中 BeanFactoryPostProcessor 的作用和示例源码分析

《Spring中BeanFactoryPostProcessor的作用和示例源码分析》Spring的BeanFactoryPostProcessor是容器初始化的扩展接口,允许在Bean实例化前... 目录一、概览1. 核心定位2. 核心功能详解3. 关键特性二、Spring 内置的 BeanFactory

MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析

《MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析》本文将详细讲解MyBatis-Plus中的lambdaUpdate用法,并提供丰富的案例来帮助读者更好地理解和应... 目录深入探索MyBATis-Plus中Service接口的lambdaUpdate用法及示例案例背景

MyBatis-Plus中静态工具Db的多种用法及实例分析

《MyBatis-Plus中静态工具Db的多种用法及实例分析》本文将详细讲解MyBatis-Plus中静态工具Db的各种用法,并结合具体案例进行演示和说明,具有很好的参考价值,希望对大家有所帮助,如有... 目录MyBATis-Plus中静态工具Db的多种用法及实例案例背景使用静态工具Db进行数据库操作插入