C(N)=(2/N)*∑(i: 0->N-1) C(i)+N
其中C(0)=1。
问题来源《数据结构与算法分析》P289 “Eval函数中的递归计算”。
对此递归程序的优化有了一点微小的心得。。
优化过程
原书中的递归函数 O(L^n)
double Eval(int N)
{
int i;
double Sum;
if(N==0)
return 1.0;
else
{
Sum=0.0;
for(i=0;i<N;i++)
Sum+=Eval(i);
return 2.0*Sum/N+N;
}
可以看出,用了一个递归结构来计算这个方程。优点:易于描述和理解。缺点:时间消耗极大,呈指数增长。
输入10,20 还好,30就已经需要4秒以上的时间来计算了。。
对此递归结构的剪枝优化,粗略估计为O(nlogn)~O(n^2)
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
double Eval(int n);
int visit[100000];
double Sum[100000],Sam[100000];
int main()
{
int n;
while( ~scanf("%d",&n))
{
memset(visit,0,sizeof(visit));
memset(Sam,0,sizeof(Sam));
memset(Sum,0,sizeof(Sum));
printf("C[n]=%lf\n",Eval(n));
}
}
double Eval(int n)
{
if(visit[n]) return Sam[n];
if(!n) return 1.0;
else
{
for(int i=0;i<n;i++)
Sum[n]+=Eval(i);
Sam[n]=(2.0/n)*Sum[n]+n;
visit[n]=1;
return Sam[n];
}
}
对原程序附加了一点优化。因为原程序对Eval函数的子分支进行了大量的重复运算,所以我把每个运算完成后添加一个标记数组visit,并且对于每个n都用数组存储。
计算时间大大缩短了。输入10000需要1S,20000需要3S左右,30000需要6秒以上。。。
当然,还可以进一步优化,每次循环不清空Sum[]和C[],以加速下一个数的计算。
优化到线性计算 O(n)
#include<stdio.h>
#include<iostream>
using namespace std;
double c[1000000] = {1.0};
double sum[1000000] = {0.0};
double Eval(int n);
int main()
{
int n;
while( ~scanf("%d",&n))
{
printf("C[n]=%lf\n",Eval(n));
}
}
//Final 优化后的线性结构 O(n)
double Eval(int n)
{
for(int i=1;i<=n;i++)
{
sum[i] = sum[i-1] + c[i-1];
c[i] = (2.0/i)*sum[i]+i;
}
return c[n];
}
O(n)时间复杂度果然棒棒哒,输入1000000和输入1,肉眼看不出时间差别。应该还能进一步优化,加个判断,计算过的值不用重复运算就好了。
书上给了个关于此的O(n^2)算法,让思考将其优化到O(n),应该达标了吧。。
没看那个O(n^2)算法,就不贴出来了,(在书P290页)。
( ̄▽ ̄)╭ ,数学不是很好,如果优化到O(1)的话估计需要推出通项公式吧。。应该能推出来吧。。反正我不能。。