算法(09):动态编程

来源:互联网 发布:人工智能的文献 编辑:程序博客网 时间:2024/06/04 18:38

分治法的一个最基本的特性是把一个问题划分为几个独立的子问题。当子问题不独立时,情况就变得更复杂了,这是因为这种问题小规模的最简单的直接递归实现需要无法接受的时间。

先看一个简单的例子:斐波纳契数列
F(N)=F(N-1)+F(N-2),F(0)=0,F(1)=1

根据公式定义,我们很容易实现递归实现:

static int F(int i)
{
 
if(i<1)return 0;
 
if(i==1)return 1;
 
return F(i-1)+F(i-2);
}


这个递归简洁优美,但并不能作为实际使用,因为它要花费指数时间来计算F(N)。计算F(N+1)的递归调用次数是F(N+1)次,而F(N)约为φ^N次幂,φ≈1.618它是黄金比例。

下面的图很清楚地显示了重复计算的量,在递归的过程中存在着子问题重叠的问题

一般来说我们要通过递归程序来分析问题的特性,然后设计出在线性的时间内(与N成比例)能完成工作的程序进行计算。

我们通过计算前N个斐波纳契数列并把它存入一个数组中,供计算F(N+1)时进行调用,这样就可以避免重复计算已经存在的值。因为数列是成指数增长的,因此数组并不大,比如:F(45)=1836311903是能表示成32位整数的最大斐波纳契数列,所以大小为46的数组就可以了。

我们有了指导思路,我们就可以对前面的递归程序进行改造,我们只需要检查保存值并保存就可以了。

static final int maxN=47;
static int knownF[]=new int[maxN];
static int F(int i)
{
 
if(knownF[i]!=0)return knownF[i];//我们使用数组存储了已经计算过的数值,这样就避免了重复计算带来的重复调用开销
 int t=i;
 
if(i<0)return 0;
 
if(i>1)t=F(i-1)+F(i-2);
 
return knownF[i]=t;
}

动态编程把递归方法的运行时间降低到至多是小于或等于给定参数的所有参数对函数求值时所需要的时间,把递归调用的开销看作常数。

下图说明了如何通过存储计算值把开销从指数级降低到线性级的

我们在以后的高级问题算法设计中会再次讨论动态编程技术,但它也存在着致命的问题。就是对于超大规模的问题求解,我们无法提供足够的空间存储所有值,这时动态编程就不可用了。 

原创粉丝点击