时间复杂度和空间复杂度

一、时间复杂度

1、基本概念

1、定义:若存在函数 f(n),使得当n趋近于无穷大时,T(n)/ f(n)的极限值为不等于零的常数,则称 f(n)是T(n)的同数量级函数。记作 T(n)= O( f(n) ),称O( f(n) )为算法的渐近时间复杂度,简称时间复杂度。渐进时间复杂度用大写O来表示,所以也被称为大O表示法。

2、基本操作执行的次数T(n)
T(n)描述了一个算法中的基本操作执行的次数。

3、T(n)是O(n)的详细表示,O(n)是渐进表示。

情景1: 线性

假设我们有 一条长10寸的面包,每3天吃掉1寸,那么吃掉整个面包需要几天?答案是 3 X 10 = 30天。如果面包的长度是 n 寸呢?此时吃掉整个面包,需要 3 X n = 3n 天。如果用一个函数来表达这个相对时间,可以记作 T(n) = 3n。

情景2: 对数

一条长16寸的面包,每5天吃掉面包剩余长度的一半, 第一次吃掉8寸,第二次吃掉4寸,第三次吃掉2寸…那么把面包吃得只剩下1寸,需要多少天呢?

这个问题翻译一下,就是数字16不断地除以2,除几次以后的结果等于1?这里要涉及到数学当中的对数,以2位底,16的对数,可以简写为log16。因此,把面包吃得只剩下1寸,需要 5 X log16 = 5 X 4 = 20 天。如果面包的长度是 N 寸呢?需要 5 X logn = 5logn天,记作 T(n) = 5logn。

情景3: 常量

一条长10寸的面包和一个鸡腿,每2天吃掉一个鸡腿。那么吃掉整个鸡腿需要多少天呢?答案自然是2天。因为只说是吃掉鸡腿,和10寸的面包没有关系 。如果面包的长度是 N 寸呢?无论面包有多长,吃掉鸡腿的时间仍然是2天,记作 T(n) = 2。

情景4:多项式

一条长10寸的面包,吃掉第一个一寸需要1天时间,吃掉第二个一寸需要2天时间,吃掉第三个一寸需要3天时间…每多吃一寸,所花的时间也多一天。那么吃掉整个面包需要多少天呢?答案是从1累加到10的总和,也就是55天。如果面包的长度是 N 寸呢?此时吃掉整个面包,需要 1+2+3+…+ n-1 + n = (1+n)*n/2 = 0.5n^2 + 0.5n。记作 T(n) = 0.5n^2 + 0.5n。

上面的这些情景,暂时没有讨论控制结构,只考虑了源操作,其中“吃”就是原操作,面包的长度n就是问题的规模。随着n的改变,T(n)也随之变化,如果无法事先知道不同算法n的区别,我们也无法使用时间频度 T(n)来判断算法的复杂程度。这是就引入了渐近时间复杂度的概念。

2、7个常用的时间复杂度

  • O(1):常数复杂度
  • O(log n):对数复杂度
  • O(n):线性时间复杂度
  • O(n2):平方
  • O(n3):立方
  • O(2n):指数
  • O(n!):Factorial 阶乘

注意:
1、只看最高复杂度的运算. 不考虑前面的系数,比如O(1)并不代表它的复杂度是1,也可以是2或3、4
,只要是常数复杂度,不管是1次、2次、3次还是x次,时间复杂度都是O(1)
2、对于线性复杂度,不管是n还是2n都不对前面的系数进行考虑,都默认为n

3、 如何确定时间复杂度

直接看函数,或者这段代码根据n的不同情况它会运行多少次

(1)常数复杂度

int i=1000;
System.out.println("hey,your input is"+n);

从上面可以看出不管n等于多少,代码只运行1次,所以属于常数复杂度,即O(1)

 int i=1000;
System.out.println("hey,your input is"+n);
System.out.println("hey,your input is"+n);
System.out.println("hey,your input is"+n);

即便添加多次输出,此代码也只运行一次,不过时间复杂度变成O(3),但是如果在面试中问到,切不可回答O(3),要回答O(1),或者属于常数复杂度。

(2)线性复杂度

for(int i=1;i<=n;i++){
	System.out.println("hey,i'm busy looking at"+i);
}

从上线可以看出,当n为1,代码运行1次,当n=100时,代码运行100次,所以属于O(n)线性复杂度

for(int i=1;i<=n;i++){
	System.out.println("hey,i'm busy looking at"+i);
}
for(int j=1;j<=n;j++){
	System.out.println("hey,i'm busy looking at"+j);
}

当并列一个循环的时候,相当于两个n,即O(2n),n前面的常数系数我们不关心,所以这段代码的时间复杂度依旧是O(n)。

(3)平方复杂度

for(int i=1;i<=n;i++){
	for(int j=1;i<=n;i++){
	System.out.println("hey,i'm busy looking at"+i);
	}
}

对上一段线性复杂度进行嵌套,我们可以发现,如果n=100时,System将会执行100X100=10000次,即O(n^2)平方复杂度

(4)对数复杂度

for(int i=1;i<n;i=i*2){
	System.out.println("hey,i'm busy looking at"+i);
}

从上面可以看出,当n=4时,i只会执行2次,这个函数会执行他的log2(n)次,n前面的常数系数我们不关心,所以这段代码的时间复杂度是O(logn)对数复杂度。

(5)指数复杂度

int fib(int n){
	if(n<2)return n;
	return fib(n-1)+fib(n-2);
}

这是一个斐波那契数列的运用递归的形式的函数,函数执行的次数为kn,为指数复杂度,即O(kn)指数复杂度。

4、时间复杂度曲线

在这里插入图片描述
在这里插入图片描述
在写程序的时候,一定要对自己的程序的时间、空间复杂度有所了解,而且要养成习惯,写完之后,能够下意识地分析出写的程序的时间、空间复杂度。 第二点能够用最简洁的时间、空间复杂度完成这段程序的话,基本上是一个顶尖职业选手必备的素养。 如果程序写砸了的话,对公司的机器、资源损耗是成百上千的增加;反之,影响也非常大。

接下来我们看一个简单的例子:
求1+2+…n的和。

方法一:
从1到n的循环累加
y=0
for i=1 to n:
y+=i

方法二:
求和公式sum=n(n+1)/2
y=n*(n+1)/2

从两个方法可以轻易的看到差别,方法一是个一重循环,n等于多少的话,他就循环多少次,时间复杂度为O(n)。方法二无论n等于多少,代码永远只执行一次,故时间复杂度为O(1)。所以在得到结果相同的时候,我们需要选择复杂度最低的算法,节省后期的成本消耗。

面试 四件套 1、首先和面试官把题目的意思确认无误 2、想尽所有可能的方法,同时比较这些时间、空间复杂度 3、找出最优的解决方案,最优的解决方案时间最少,用的内存最少,然后开始写 4、测试实验结果 复杂条件下:递归 递归的话我们关键要了解递归的语句,总共执行了语句多少次。

5、当在递归条件中如何求时间复杂度

我们关键要了解递归的语句,总共执行了语句多少次
递归的话是层层嵌套,我们要借助的,把递归的执行顺序,画出一个树型结构,我们称之为它的递归状态的递归树或者就是状态树。

例子:
求斐波那契数列的第n项
Fib:0,1,1,2,3,5,8,13,21…
F(n)=F(n-1)+F(n-2)

面试中大家一般直接用递归的方法求解:

int fib(int n){
	if(n<2)return n;
	return fib(n-1)+fib(n-2);
}

这里我们假设n=6
在这里插入图片描述
假设为6的话,展开他的递归树,可以看出来,每次多出一层,都要比前一层多出2倍的执行次数,即2的n次方,所以时间复杂度为O(2n).
我们会发现有大量的重复的冗余值,导致呈现指数阶的增长。所以面试的时候一定不要这么写。要么通过缓存将这些中间结果缓存下来,要么直接用循环来写完这整个Fibonacci数列求n项和。

6、主定理求递归的时间复杂度

主定理基本概念

在这里插入图片描述
1、二分查找,一般发生在一个数列本身有序的时候,你要在有序的数列里找到你的目标数。只查一边这么下去的话,最后他的时间复杂度是log(n)。
2、二叉树遍历,他的时间复杂度是O(n)。不通过主定理,简化的思考方式,二叉树遍历的话,我们会每一个节点都访问一次,而且仅访问一次。
3、在一个排好序的二维矩阵中进行二分查找。时间复杂度是O(n),如果是一维数组二分查找,时间复杂度是O(logn)
4、归并排序(merge sort),时间复杂度是nlogn的时间复杂度。

思考题:
1、手写二叉树遍历-前、中、后序,并分析时间复杂度。
答:O(n), .O(n)这里的n代表二叉树里边的树的节点总数。通过主定理得到;或者这么说不管是前序中序后续,二叉树里边的每个节点会访问一次且仅访问一次,所以它的时间复杂度线性于二叉树的节点总数,因此是O(n).
2、图的遍历:时间复杂度是多少? 答:O(n),这里的n的话是图里所有的节点总数。
3、搜索算法:DFS、BFS的时间复杂度 答:不管是DFS(深度优先)、BFS(广度优先),因为访问的节点是访问一次,所以时间复杂度都是O(n),这里的n指的就是搜索空间里边的节点总数。
4、二分查找:时间复杂度是多少? 答:log(n)

二、空间复杂度

两个原则:
1、数组的长度
2、递归的深度(特殊说明)

说明:
1、如果你的代码里开了数组,那么空间复杂度就是数组的长度
2、如果有递归,递归最深的深度,就是空间复杂度的最大值。 O(n) =数组 + 递归 两者之间的最大值,就是空间复杂度。

leetcode经典案例(爬楼梯)

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试
应支付0元
点击重新获取
扫码支付

支付成功即可阅读