时间复杂度和空间复杂度

最近在看一些与算法有关的书,了解到时间复杂度和空间复杂度这两个词,平时在讨论工作时也会提价到,就把概念记录一下,以后忘记了也方便随时查阅。毕竟好记性不如烂笔头。

假如A和B同时实现一个功能,其中A提交的代码运行一次要100ms,占用内存5m,而B提交的代码运行一次要10s,占用内存500m。虽然A和B都实现了功能,但是B的代码运行的时间和占用的空间都比A代码运行锁消耗的资源要多,这是不能忍受的。由此可见,运行时间的长短和占用内存空间的大小是衡量一个程序好坏的重要因素。

时间复杂度

怎么能确定代码的运行时间呢?

受运行环境和输入规模等各种因素的影响,代码的绝对执行时间是无法预估的。但是我们可以预估代码的基本操作执行次数。

场景1:给A一个长度为10cm的面包,A每3分钟吃掉1cm,那么吃完整个面包需要多久?

答案是3x10=30分钟

如果面包的长度为n cm呢?

此时吃掉整个面包需要3乘以n即3n分钟。用一个函数来表示吃掉整个面包的所需要的时间,可以记作T(n)=3n,n为面包的长度

场景2:给A一个长度为16cm的面包,A每5分钟吃掉面包剩余长度的一半,即第5分钟吃到8cm,第10分钟吃到4cm,以此类推。那么A把整个面包吃得只剩1cm,需要多久?

这个问题用数学的表达方式就是数字16不断的除以2,那么除几次过后的结果等于1?。这里用数学中的对数,即以2为底16的对数log216。因此,把面包吃得只剩1cm,需要5xlog216即20分钟。

如果面包的长度为n cm呢?

此时吃掉整个面包需要5乘以log2n即5log2n分钟,可以记作T(n)=5log2n

场景3:给A一个长10cm的面包和一个鸡腿。A每2分钟吃掉一个鸡腿。那么吃完整个鸡腿需要多少时间?

答案当然是2分钟,这里和10cm的面包没有关系。

如果面包的长度为n cm呢?

无论面包多长,吃掉鸡腿的时间都是2分钟,记作T(n)=2

场景4:给A一个长度为10cm的面包,A吃掉第一个1cm需要1分钟,吃掉第二个1cm需要2分钟,吃掉第三个1cm需要3分钟,没吃掉1cm所花的时间都比上一个1cm多用1分钟。那么吃掉整个面包都需要多久?

答案是1到10的和,也就是55分钟

如果面包的长度为n cm呢?

根据高斯算法,吃掉整个面包需要1+2+3+4+5+…+(n-1)+n,即(1+n)Xn/2分钟,也就是0.5n2+0.5n,记作T(n)=0.5n2+0.5n

上述思想同样适用于程序基本操作执行次数的统计。设T(n)为程序基本操作执行次数的函数(也可以认为是程序的相对执行时间函数),n为输入规模,以上4种场景对应程序的4中执行方式。

有了基本操作执行次数函数T(n),分析和比较代码的运行时间还是有点困难的。如函数A的执行次数是T(n)=100n,函数B的执行次数是T(n)=5n2,看这两个函数的执行时间就要看n的取值了,为了解决时间分析的难题,就有了渐进时间复杂度(asymptotic time complexity)

渐进时间复杂度

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

如何推断出时间复杂度呢?有以下几个原则:

  1. 如果运行时间是常数量级,则用常数1表示
  2. 只保留时间函数中的最高阶项
  3. 如果最高阶项存在,则省去最高阶项前面的系数

上述4个场景举例:

  • 场景1:T(n)=3n,最高阶项为3n,省去系数3,则转化的时间复杂度为:T(n)=O(n)
  • 场景2:T(n)=5log2n,最高阶项为log2n,省去系数5,则转化的时间复杂度为:T(n)=O(log2n)
  • 场景3:T(n)=2,只有常数量级,则转化的时间复杂度为:T(n)=O(1)
  • 场景4:T(n)=0.5n2+0.5n,最高阶项为0.5n2,省去系数0.5,则转化的时间复杂度为:T(n)=O(n2)

当n的值足够大时,不难判断出:O(1) < (log2n) < O(n) < O(n2),当然还有各种各样的算法,也还有不同形式的时间复杂度。

##空间复杂度
和时间复杂度类似,空间复杂度是对一个算法咱运行过程中临时占用存储空间大小的量度,它同样使用了大O表示法,程序占用空间大小的计算公式记作S(n)=O(f(n)),其中n为问题的规模,f(n)为算法所占存储空间的函数。

常见的空间复杂度有以下几种情况:

  1. 常量空间
    当算法的存储空间大小固定,和输入规模没有直接的关系时,空间复杂度记作O(1),例如:
    void method(int n){
    	int a=1;
    	int b=2;
    	...
    }
    
  2. 线性空间
    当算法分配的空间是一个线性的集合(如数组),并且集合大小和输入规模n成正比,空间复杂度记作O(n),例如:
    	void method(int n){
    		int[] array=new int[n];
    		int b=2;
    		...
    	}
    
  3. 二维空间
    当算法空间分配的是一个二位数组结婚,并且集合的长度和宽度都与输入规模n成正比时,空间复杂度记作O(n2),例如:
    	void method(int n){
    		int[][] array=new int[n][n];
    		int b=2;
    		...
    	}
    
  4. 递归空间
    递归是一个比较特殊的场景,虽然递归代码中没有显示的声明变量或集合,但是计算机在执行程序时,会专门分配一块内存,用来存储“方法调用栈”。方法调用栈包括进栈和出栈两个行为。当进入一个新方法是,执行入栈操作,把调用的方法和参数信息压入栈中。当方法返回时,执行出栈操作,把调用的方法和参数信息从栈中弹出。执行递归操作所需要的内存空间和递归深度成正比,纯粹递归操作的空间复杂度也是线性的,如果递归的深度是n,那么空间复杂度就是O(n)。

之所以花大力气去评估算法的时间复杂度和空间复杂度,根本原因是计算机的运算速度和空间资源是有限的。鱼和熊掌不可兼得,我们必须要在时间复杂度和空间复杂度中选其一。在大多数情况下,时间复杂度更为重要,日益急促的生活节奏下,人们已经不再习惯等待。

参考资料:《漫画算法:小灰的算法之旅》

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值