时间复杂度
算法的好与坏在于它的时间复杂度和空间复杂度。所以一开始就学习了时间复杂度的分析。
大O表示法:
将算法相对的执行时间函数T(n)简化成一个数量级,T(n) = O(f(n)),O为算法的渐进时间复杂度,简称时间复杂度,因为是用大O来进行表示的,所以也称之为大O表示法。
推导时间复杂度的步骤:
- 如果运行时间是常数量级,用1表示,O(1);
- 只保留时间函数中的最高阶的一项,如 f(n) = n^2 + n,,那么时间复杂度为 O(n^2);
- 如果最高阶项存在,则省去最高阶项前面的系数,如f(n) = 3n^2;那么时间复杂度为 O(n^2);
时间复杂度的差异:
每个算法的时间复杂度不一样时,差距也是不同的,下面是一个例子:
算法A的执行次数是T(n) = 10n,时间复杂度为O(n);
算法B的执行次数是T(n) = 2n^2, 时间复杂度为O(n^2);
随着输入规模n的增长,我们看看会发生什么事情呢?
T(n) = 10n | T(n) = 2n^2 | |
---|---|---|
n = 1 | 10 | 2 |
n = 5 | 50 | 50 |
n = 10 | 100 | 200 |
n = 100 | 1000 | 20000 |
n = 1000 | 10000 | 2000000 |
n = 10000 | 100000 | 200000000 |
n = 100000 | 1000000 | 20000000000 |
n = 1000000 | 10000000 | 200000000000 |
n = 10000000 | 100000000 | 20000000000000 |
n = 100000000 | 1000000000 | 20000000000000 |
可以看出数据规模到一定程度时,O(n^2) 的 时间是远远多于 O(n)的,故在写代码时考虑时间复杂度是非常有必要的。
举例分析时间复杂度:
1.O(n)
public void fly( int n)
{
for(int i = 0; i < n; i++)
{
System.out.println("我飞了" + i + "米");
System.out.println("你飞了" + i + "米");
}
}
以上这段代码的时间复杂度为O(n),因为其中 System.out.println(“我飞了” + i + “米”);执行n次,所以花费的时间为n,两句就是 n + n 为2n,省略系数2,得到时间复杂度为O(n)。
2.O(n^2)
public void fly( int n)
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < i; j++)
{
System.out.println("我飞了" + i * j+ "米");
}
}
}
这段代码的时间复杂度为O(n^2),首先是第一层循环里的循环执行了n次,每执行一次,System.out.println()这一句就会执行i×i次,那么最后这句话就执行了 n ×n次,即n^2次,故时间复杂度为O(n^2)。
3.O(logn)
i=1;
while (i <= n) {
i = i * 2;
}
这个代码的时间复杂度是O(logn)。代码每循环一次就i乘以2,变量i的取值就是⼀个等⽐数列。如果我把它⼀个⼀个列出来,就应该是这个样⼦的:
2^0 2^1, 2^2, 23…2x = n,通过计算x = log2n,的时间复杂度为O(logn);
时间复杂度分为最好时间复杂度,最坏时间复杂度和平均时间复杂度。
最好时间复杂度:
在最理想的情况下,执⾏这段代码的时间复杂度
最坏时间复杂度:
在最糟糕的情况下,执⾏这段代码的时间复杂度
平均时间复杂度又是期望时间复杂度。
空间复杂度:
在时间复杂度分析后,影响算法运行效率的还有一个重要的因素,算法所占内存的多少。
内存空间是有限的,在时间复杂度相同的情况下,算法占用的内存空间自然是越小越好。
空间复杂度同样也使用大O表示法来表示。
下面有常见的空间复杂度:
1、常量空间
当算法的存储空间大小固定,和输入规模没有直接关系时,复杂度记作O(1)
如:
void fun1(int n){
int var = 4;
}
2、线性空间
当算法分配的空间是一个线性的集合(如数组),并且集合的大小和输入规模成正比时,空间复杂度记作O(n).
如:
void fun2(int n){
int[] array = new int[n];
}
3.二维空间
当算法分配的是一个空间二维数组集合,并且长度和宽度都与输入规模成正比时,空间复杂度记作O(n^2)。
如:
void fun3(int n)
{
int[][] array = new int[n][n];
}
4、递归空间
在递归代码执行的时候,会专门分配一块内存,用来存储方法调用栈,方法调用栈包括出栈和进栈两个行为。
下面这个程序是一个递归程序:
void fun4(int n){
if(n<=1){
return;
}
System.out.println(n);
fun4(n-1);
}
执行递归操作所需要
的内存空间和递归的深度成正比。纯粹的递归操作的空间复杂度也是线
性的,如果递归的深度是n,那么空间复杂度就是O(n)。
在绝大多数时候,时间复杂度比较重要,宁可多分配一点内存,也要提升程序的执行速度。