时间复杂度
一、什么是时间复杂度?
时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
二、常见的时间复杂度量级有:
- 常数阶O(1)
- 对数阶O(logN)
- 线性阶O(n)
- 线性对数阶O(nlogN)
- 平方阶O(n²)
- 立方阶O(n³)
- K次方阶O(n^k)
- 指数阶(2^n)
上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。
-
常数阶O(1) 无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)
-
对数阶O(logN)
还是先来看代码:
int i = 1;
while(i<n)
{
i = i * 2;
}
从上面代码可以看到,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n
也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)
- 线性阶O(n)
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。 4. 线性对数阶O(nlogN)
线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。
就拿上面的代码加一点修改来举例:
for(m=1; m<n; m++)
{
i = 1;
while(i<n)
{
i = i * 2;
}
}
- 平方阶O(n²)
平方阶O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
举例:
for(x=1; i<=n; x++)
{
for(i=1; i<=n; i++)
{
j = i;
j++;
}
}
这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n*n),即 O(n²)
如果将其中一层循环的n改成m,即:
for(x=1; i<=m; x++)
{
for(i=1; i<=n; i++)
{
j = i;
j++;
}
}
那它的时间复杂度就变成了 O(m*n)
- 立方阶O(n³) 、K次方阶O(n^k)
对比 O(n²) 即嵌套了3层N循环以及k层N循环
三、以递归算法实例探究时间复杂度
以递归算法为例,对递归算法的时间复杂度做进一步的分析
经典例题:求x的n次方
方法一:for循环
def fun1(x,n):
ans=1
for i in range(n):
ans=ans*x
return ans
时间复杂度为O(n)
还有没有更好的方法呢?
方法二:递归
int digui2(int x, int n) {
if (n == 0) {
return 1; // return 1 同样是因为0次方是等于1的
}
return digui2(x, n - 1) * x;
}
那么这份代码的时间复杂度是多少?
递归算法的时间复杂度本质上是要看: 递归的次数 * 每次递归中的操作次数
每次n-1,递归了n次 时间复杂度是O(n),每次进行了一个乘法操作,乘法操作的时间复杂度一个常数项O(1)
所以这份代码的时间复杂度是 n * 1 = O(n)
int digui3(int x, int n) {
if (n == 0) {
return 1;
}
if (n % 2 == 1) {
return digui3(x, n/2) * digui3(x, n/2)*x;
}
return digui3(x, n/2) * digui3(x, n/2);
}
优化后的递归算法代码
int digui4(int x, int n) {
if (n == 0) {
return 1;
}
int t = digui4(x, n/2);// 这里相对于digui3,是把这个递归操作抽取出来
if (n % 2 == 1) {
return t*t*x;
}
return t*t;
}