- 时间复杂度
一个算法的执行时间大致上等于其所有语句执行时间的总和,对于语句的执行时间是指该条语句的执行次数和执行一次所需时间的乘积。并且一个算法花费的时间是与算法中所有语句总的执行次数成正比的,也就是说哪个算法中语句的执行次数多,它所花费的时间就多。
算法时间分析度量的标准不是针对实际执行时间精确算出算法执行的具体时间(因为一个算法在不同的机器上执行所花的时间不一样。在不同时刻也会由于计算机资源占用情况的不同,使得算法在同一台计算机上执行的时间也不一样),而是针对算法中语句的执行次数做出估计,从中得到算法执行时间的信息。
时间复杂度实际上就是一个函数,该函数计算的是算法执行基本操作的次数(为便于比较解决同一问题的不同算法,通常以算法中基本操作重复执行的频度作为度量标准,没必要求出算法中所有语句总的执行次数,见例1),而且在实际中通常关注的是算法的最坏运行情况,即:任意输入问题的规模n,算法的最长运行时间。
例1
void Test(int n) 该算法每一语句的语句频度
{ 语句频度是指该语句在一个算法中重复执行的次数
int iCount = 0; 1
for(int iIdx = 0; iIdx < 10; ++iIdx) 10
{
iCount++; 10
}
}
所以说,该算法所有语句总的执行次数T(n)=21,但该算法的本意是让变量iCount从0一直累加到10,那么该算法执行基本操作的次数为10,此时的T(n)=10。
- 时间复杂度之大O渐进表示法
所谓算法的时间复杂度,即是算法的时间量度,记做T(n)=O(f(n)),它表示随问题规模n(问题规模对不同的问题其含义不同,对矩阵是阶数,对线性表是长度等)的增大,算法执行时间的增长率和f(n)的增长率相同,称O(f(n))为算法的渐进复杂度,简称时间复杂度。其中的f(n)是T(n)的同数量级。一般情况下都是使用O渐进表示法来表示算法的时间复杂度。
- 常见的时间复杂度按数量级递增排列有
常数型(阶)O(1),对数型(阶)O(log2n),线性型(阶)O(n),二维型(线性对数阶)O(nlog2n),平方型(阶)O(n2),立方型(阶)O(n3),……,k次方型(阶)O(nk),指数型(阶)O(2n),O(n!),O(nn),上述的时间复杂度不断增大,算法的执行效率越低。
- 一般算法O(n)的计算方法(其他的算法计算也类似于这种,见例 2)
1、将T(n)中的所有加法常数用1取代
2、保留T(n)中的最高次项
3、如果最高阶项的系数不是1,将其改为1
另外,在时间复杂度中,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)=an2+bn+c(a、b、c为常数)=O(n2)……
T(n)=alog2n+b(a、b为常数)=O(log2n)
T(n)=anlog2n+b(a、b为常数)=O(nlog2n)
T(n)=2n=O(2n)后三种的计算方法与上述提及的方法稍稍的有些不同,但归根结底,你只需要记住一点,那就是找函数T(n)中增长最快的那一项,随后将其系数改为1就行了。
例3
答案为A, 因为该算法基本操作的重复次数相当于从1依次累加n,累加n次,所以答案得出。
- 空间复杂度
函数中创建变量的个数关于问题规模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)试一下