程序的效率可以分为空间和时间两个方面。空间表现在一个程序所占的代码空间大小,即ROM或FLASH空间(虽然大多数情况下这个无关紧要可以忽略,因为运行程序的设备的ROM空间一般都足够大)和所占的内存空间大小,即RAM空间。时间则表现在运行完一段程序所消耗的时间,这个时间与处理器的运行速度有关,同样的程序在不同的设备运行的时间可能是不一样的。程序使用的运算方式也有关系,比如在部分处理器或单片机中,对于浮点数的运算,耗费的时间是远远大于整形数的运算的,所以在通常会把浮点数先放大一定的倍数使其变为整形,运算完成后再缩放原来的倍数。还有一个对时间效率影响最大的因素,就是算法。通过算法减少不必要的运算,时间效率自然就提升了。这里就从程序的时间效率上浅谈一下应用算法与数学知识对程序进行优化。
从一个简单的例子开始分析。
要解决这样的一个问题:一段楼梯有10级台阶,规定每一步只能跨一级或两级,要登上10级台阶有______种不同的走法。
第一种解法:暴力
通过for循环列举所有的可能,分别列举不走两级,走一次两级...走5次两级的情况各有多少种可能,这是数学上经典的排列组合的方法,答案是C(n-i,i)的总和(i=0,n/2)。
对于这个问题,答案是C(10,0)+C(9,1)+C(8,2)+C(7,3)+C(6,4)+C(5,5)=1+9+28+35+15+1=89。
写出一个通用的计算程序:
long long solve1(int step)
{
long long res = 1;
long long a,b;
for(int i=1;i<=step/2;i++)
{
a=step-i;
b=i;
for(int j=0;j<i-1;j++)
{
a *= (step-i-j-1);
b *= (i-j-1);
}
res += a/b;
}
return res;
}
由于使用了阶乘的计算,因此中间变量会非常容易溢出,只适合数据量小的情况。同时,使用了2个for循环,有大量的数据重复运算的情况,运算量也比较大。
第二种解法:递归
假设现在还差走一次就能到10级了,那么就只有2种可能,1:现在在第8级,2:现在在第9级。假设走到第n级有F(n)种走法,则F(10)=F(9)+F(8). 同样的,要走到第9级,只能从第7级或者第8级走过去,由此可以得出F(n)=F(n-1)+F(n-2)。同时可以发现F(1)=1,F(2)=2。于是写出一个递归的解法
long long solve2(int step)
{
if(step == 1) return 1;
if(step == 2) return 2;
return solve2(step-1)+solve2(step-2);
}
问题似乎解决了,但当遇到n比较大时,这个解法的运行时间也是相当可怕的,当n=50时,运行时间达到了60+秒。
不难发现,上面的解法中,有重复计算的部分,比如计算F(9)时 需要计算F(7),计算F(8)时也需要计算F(7),所以当n变大时,运算量也是指数增长的。如果使用保存中间的计算结果,就能省去很多不必要的运算。
int t=0;
int a[100]={0};
long long ans[100]={0};
long long solve2(int step)
{
if(step == 1) return 1;
if(step == 2) return 2;
if(a[step] ==1) return ans[step];
else
{
ans[step] = solve2(step-1)+solve2(step-2);
a[step]=1;
return ans[step];
}
}
这是用c语言模拟C++的map的写法,存在溢出的可能,需要根据n的大小调整数组的长度。当n=50时,运行结果:
同时也可以注意到,时间是大大减小的,但占用的内存空间变大了,这就是以牺牲空间换取时间效率的一种做法。
第三种解法:
其实,当分析这个问题,发现F(n)=F(n-1)+F(n-2)的时候,就能知道这是数学上非常有名的一个斐波那契数列。所以,问题的解可以直接使用求第n项的方法。
long long solve3(int step)
{
if(step == 1) return 1;
if(step ==2 ) return 2;
long long res=0;
long long a=1;
long long b=2;
for(int i=3;i<=step;i++)
{
res = a+b;
a=b;
b=res;
}
return res;
}
于是,不再需要第二种方法那样另外开辟内存空间,对于空间的效率也有了提升。
所以,对于同一个问题,可能会存在不同的解决方法,清楚每种解法的适用性和安全性(正确性),就像前面的第一种解法,看上去似乎能解决问题,但实际上当n比较大的时候因为存在溢出,计算结果就会出错。同时也要注意尽量优化不必要的运算,以提高程序的效率。