目录
算法效率
如何衡量一个算法的好坏呢?是否越简洁就是越好的算法呢?我们举如下例子:
long long Fib(int n)
{
if(n<3)
return 1;
return Fib(n-1)+Fib(n-2);
}
这是斐波那契数列递归方式的实现, 代码虽然简洁,但是效率一定高吗?并不是这样。这里我们的算法效率一般是从时间和空间两个维度来衡量的。即时间复杂度和空间复杂度。
什么是时间复杂度?
在计算机科学中,算法的时间复杂度是一个函数,描述算法的执行时间与数据规模之间的增长关系,用来衡量算法运行的快慢。从理论上讲,是不能算出来具体时间的,只有将程序上机运行,才能知道,而且不同计算机的计算能力也会是使得具体时间不同。所以,我们用时间复杂度的分析方式来解决,一个算法花费的时间与其执行次数成正比,因此,算法中的基本操作的执行次数,为算法的时间复杂度。
时间复杂度的计算
即找到某条基本语句与问题规模N之间的数学表达式,就是算法的时间复杂度。
例如,求下面代码中Fun函数中number++语句共执行了多少次?
void Fun(int N)
{
int number=0;
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
number++;
}
}
for(int k=0;k<2*N;k++)
{
number++;
}
int M=20;
while(M--)
{
number++:
}
printf("%d",number):
}
由分析可知,时间复杂度函数F(N)=N^2+2*N+20.
- N=10 F(N)=140
- N=100 F(N)=10220
- N=1000 F(N)=1002020
- ··········
N值越大,后两项对结果影响越小。实际上并不一定要计算精确的执行次数,这里我们用到大O渐进表示法。即表示为 O(N^2).(即保留原函数中影响最大的一项)
下面是大O表示法随N的增长曲线
几种常见时间复杂度函数的大O表示法:
了解时间复杂度的计算之后,看下面一个例子
如何计算二分查找法查找x的时间复杂度?
int BinarySearch(int *a, int n, int x )
{
assert(a);
int left = 0;
int right = n;
while(left < right)
{
int mid = left + ((right-left)>>1);
if(a[mid] < x)
left = a[mid] + 1;
else if(a[mid] > x)
right = a[mid];
else
return a[mid];
}
return 0;
}
由分析可知,x正好位于数组中间时,此时只需要执行一次就可以查找出x,这是最好的情况,时间复杂度为O(1);当数组中查找不到x或x在数组最边上时,此时需要执行很多次循环,时间复杂度为最坏的境况,每执行一次N的值都减半,因此时间复杂度为O(log₂N);这里就需要对时间复杂度做悲观预期。
时间复杂度做悲观预期
一般情况下,当一个算法随着输入不同,时间复杂度不同时,我们要考虑该算法的最坏执行情况,即对时间复杂度做悲观预期,来表示该算法的时间复杂度。
最后回到最开始的问题,来看看斐波那契数列的递归实现方式的时间复杂度是怎样的?
用图解说明
可以看出,每次调用都会有两个新的分支,即二叉树,时间复杂度表示为O(2^N)。当N=50时就已经是非常大的数了,时间复杂度很高,并不够高效。最好不使用递归方法实现斐波那契数列。
什么是空间复杂度?
空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用储存空间大小的量度。
因为内存发展很快,现在的计算机内存空间很大,所以看程序占用了多大空间也没有意义,一般算变量的个数来表示空间复杂度,也用大O渐进表示法。但不同于时间的是,空间是可以重复利用的,用过的空间可以归还后再次开辟使用,不像时间是线性轴的。
例如:计算斐波那契数列函数的空间复杂度。
long long *Fib(size_t n)
{
if(n==0)
return NULL;
long long *fibarray = (long long*)malloc(n+1*sizeof(long long));
fibarray[0] = 0;
fibarray[1] = 1;
for(int i = 2;i <= n;++i)
{
fibarray[i] = fibarry[i-1] + fibarry[i -2];
}
return fibarray;
}
由分析可知,先递归第一次调用时开辟N个空间,之后每一次递归都可以重复使用第一次开辟的空间,所以空间复杂度为O(N)。
最后,这是对自己每天学习的一个小记录,每一次知识的掌握都离不开勤奋的练习,加油加油!