【数据结构】之时间复杂度和空间复杂度
1.时间复杂度和空间复杂度的定义
时间复杂度和空间复杂度是用来评价算法效率高低的2个标准;
时间复杂度:也称渐进时间复杂度,它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,算法复杂度分析的实际是一种时间增长的局势,通俗来说就是计算算法执行所需要的次数。
空间复杂度:是对一个算法在运行过程中临时中占用的存储空间大小的量度。
2.时间复杂度计算
2.1推导大O阶方法
分析一个算法的时间复杂度步骤:
1.用常数1取代运行时间中的所有加法常数。
2.在修改后的运行次数函数中,只保留最高阶项。
3.如果最高阶项存在且不是1,则去除与这个项相乘的常数。
4.得到的最后结果就是大O阶。
(1)常数阶
例子:
int sum = 0, n = 100;
printf(“I love you.com\n”);
printf(“I love you.com\n”);
printf(“I love you.com\n”);
printf(“I love you.com\n”);
printf(“I love you.com\n”);
printf(“I love you.com\n”);
sum = (1+n)*n/2;
很明显每一个print执行下来时间复杂度都是O(1)。
(2)线性阶:一般含有非嵌套循环涉及线性阶,线性阶就是随着问题规模n的扩大,对应计算次数呈直线增长。
int i , n = 100, sum = 0;
for( i=0; i < n; i++ )
{
sum = sum + i;
}
上面这段代码,它的循环的时间复杂度为O(n),因为循环体中的代码需要执行n次。
(3)平方阶
int i, j, n = 100;
for( i=0; i < n; i++ )
{
for( j=0; j < n; j++ )
{
printf(“I love FishC.com\n”);
}
}
也就是说外层循环每执行一次,内层循环就执行100次,那总共程序想要从这两个循环出来,需要执行100*100次,也就是n的平方。所以这段代码的时间复杂度为O(n^2)。
总结:如果有三个这样的嵌套循环就是n^3。所以总结得出,循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。
int i, j, n = 100;
for( i=0; i < n; i++ )
{
for( j=i; j < n; j++ )
{
printf(“I love FishC.com\n”);
}
}
由于当i=0时,内循环执行了n次,当i=1时,内循环则执行n-1次……当i=n-1时,内循环执行1次,所以总的执行次数应该是:
n+(n-1)+(n-2)+…+1 = n(n+1)/2
n(n+1)/2 = n^2/2+n/2
用我们推导大O的攻略,第一条忽略,因为没有常数相加。第二条只保留最高项,所以n/2这项去掉。第三条,去除与最高项相乘的常数,最终得O(n^2)。
(4)对数阶
int i = 1, n = 100;
while( i < n )
{
i = i * 2;
}
由于每次i*2之后,就距离n更近一步,假设有x个2相乘后大于或等于n,则会退出循环。
于是由2^x = n得到x = log(2)n,所以这个循环的时间复杂度为O(logn)。
2.3函数调用的时间复杂度分析
nt i, j;
for(i=0; i < n; i++) {
function(i);
}
void function(int count) {
printf(“%d”, count);
}
函数体是打印这个参数,这很好理解。function函数的时间复杂度是O(1),所以整体的时间复杂度就是循环的次数O(n)。
假如function是下面这样:
void function(int count) {
int j;
for(j=count; j < n; j++) {
printf(“%d”, j);
}
}
事实上,这和之前平方阶的时候举的第二个例子一样:function内部的循环次数随count的增加(接近n)而减少,所以根据游戏攻略算法的时间复杂度为O(n^2)。
n++; //1
function(n); //1
for(i=0; i < n; i++) { //n
function(i);
}
for(i=0; i < n; i++) { //n^2
for(j=i; j < n; j++) {
printf(“%d”, j);
}
}
void function(int count) {
printf(“%d”, count);
}
为:1+1+n+n2,所以最后是O(n2)
常用的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(logn) < (n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
如图:
1.4 最坏情况与平均情况
我们查找一个有n个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为O(1),但也有可能这个数字就在最后一个位置,那么时间复杂度为O(n)。
平均运行时间是期望的运行时间。
最坏运行时间是一种保证。在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
3.空间复杂度
3.1空间复杂度计算方法
(1) 忽略常数,用O(1)表示
(2)递归算法的空间复杂度=递归深度N*每次递归所要的辅助空间
(3)对于单线程来说,递归有运行时堆栈,求的是递归最深的那一次压栈所耗费的空间的个数,因为递归最深的那一次所耗费的空间足以容纳它所有递归过程。
a = 0
b = 0
print(a,b
空间复杂度O(n)=O(1);
def fun(n):
k = 10
if n == k:
return n
else:
return fun(++n)
递归实现,调用fun函数,每次都创建1个变量k。调用n次,空间复杂度O(n*1)=O(n)。
for(i=0;i<n;++):
temp = i
变量的内存分配发生在定义的时候,因为temp的定义是循环里边,所以是n*O(1)
temp=0;
for(i=0;i<n;i++):
temp = i
temp定义在循环外边,所以是1*O(1)