一、算法的复杂度
- 时间复杂度
- 空间复杂度
1、时间复杂度
一个算法的执行时间大致上等于其所有语句执行时间的总和,对于语句的执行时间是指该条语句的执行次数和执行一次所需时间的乘积
算法时间分析度量的标准不是针对实际执行时间精确算出算法执行的具体时间,而是针对算法中语句的执行次数做出估计(因为哪怕同一个算法,在不同时刻也会由于计算机资源占用情况的不同,使得在同一台机器上的执行时间不同),从中得到算法执行时间的信息
时间复杂度实际上就是一个函数 T(n),该函数计算的是算法中语句总的执行次数
在实际中通常关注的是算法的最坏运行情况,即:任意输入问题规模 n,总能使算法中语句总的执行次数达到最大
时间复杂度的大 O 渐进表示法
T(n) = O(f(n)),当问题规模 n 增大时,T(n) 和 f(n) 的增长率相同,称 O(f(n)) 为时间复杂度的 O 渐进表示法
一般情况下使用 O 渐进表示法计算时间复杂度,计算方法如下:
1、用 1 取代 T(n) 中的常数项
2、保留 T(n) 中的最高阶项
3、如果最高阶项的系数不是1,将其改为 1
常见的时间复杂度排序
O(1) < O(logn,底数 >= 2) < O(n) < O(nlogn,底数 >= 2) < O() < O(
) < O(
) < O(n!) < O(
)
另外,在时间复杂度中,O(log2n)与O(lgn)是等价的,因为对数的换底公式:logab=logcb/logca,所以log2n=log2(10)*lgn,将系数变为1,由此可见二者显然是等价的。
例2
T(n)=常数=O(1)
T(n)=an+b(a、b为常数)=O(n)
T(n)=a+bn+c(a、b、c为常数)=O(n2)……
T(n)=+b(a、b为常数)=O(log2n)
T(n)=+b(a、b为常数)=O(nlog2n)
T(n)==O(
)后三种的计算方法与上述提及的方法稍稍的有些不同,但归根结底,你只需要记住一点,那就是找函数T(n)中增长最快的那一项,随后将其系数改为1就行了。
例3
答案为A, 因为该算法基本操作的重复次数相当于从1依次累加n,累加n次,所以答案得出。
2、空间复杂度
函数中创建变量的个数关于问题规模n的函数表达式,一般情况下用O的渐进表示法表示。其计算方法与上述类似。
例4
int Sum(int n)
{
int count = 0;
for(int i = 1; i <= N; i++)
{
count += i;
}
return count;
}
空间复杂度:O(1)
算法执行时间的耗费和所占存储空间的耗费两者是矛盾的,难以兼得。即算法执行时间上的节省一定是以增加空间存储为代价的,反之亦然。不过,就一般而言,常常以算法执行时间作为算法优劣的主要衡量指标。
例5
int* Merge(int* array1, int size1, int* array2, int size2)
{
int index1 = 0, index2 = 0, index = 0;
int* temp= (int*)malloc(sizeof(int)*(size1+size2));
if(NULL == temp)
{
return NULL;
}
while(index1 < size1 && index2 < size2)
{
if(array1[index1] <= array2[index2])
{
temp[index++] = array1[index1++];
}
else
{
temp[index++] = array2[index2++];
}
}
while(index1<size1)
{
temp[index++] = array1[index1++];
}
while(index2 < size2)
{
temp[index++] = array2[index2++];
}
return temp;
}
空间复杂度:O(m+n)
时间复杂度:O(m+n)
其中的m和n就是未知量size1和size2的大小,显然空间复杂度就已得出,在此提出一点就是定义一个数组,哪怕数组 [ ] 里面的数很大,其空间复杂度仍就为O(1)。该算法的空间复杂度是很难计算的,但分析算法的最终是想将两个原本有序的数组合并成一个有序数组,所以时间复杂度就为O(m+n)。
二、递归算法的复杂度
递归算法的时间复杂度:递归总次数*每次的递归次数
递归算法的空间复杂度:递归深度*每次调用里面创建变量的个数
例6
int Fib(int n)
{
if(n<3)
{
return 1;
}
return Fib(n-1)+Fib(n-2);
}
时间复杂度:O(2n)
空间复杂度:O(n)
以n为6试计算一下
上述的斐波那契算法效率非常低,而且在递归调用次数多的情况下,很容易造成栈溢出。
优化一:采用循环结构实现
int Fib(int n)
{
int first = 1, second = 2, ret = 0;
int i = 3;
for(; i<=n; i++)
{
ret = first+second;
first = second;
second = ret;
}
return second;
}
优化二:采用尾递归实现(在调用一个函数的过程中,直接或间接地调用该函数本身,称为函数的递归调用。而尾递归是指所有递归形式的调用一定是发生在函数的末尾。形式上只要最后一个return语句是单纯的函数就可以。如:return tailrec(x+1);,其他形式都不可以),其时间复杂度为O(n),空间复杂度为O(n)或O(1),如果编译器能对尾递归进行优化的话,优化后的尾递归和迭代也就是循环是类似的,此时的空间复杂度就是O(1)了。
int Fib(int first, int second, int n)
{
if(n<3)
{
return 1;
}
if(3==n)
{
return first+second;
}
return(second, first+second, n-1);
}
以Fib(1, 1, 6)试一下