一.复杂度的概念
简而言之就是用来衡量一个程序/算法的好坏,因为算法在编写成可执行程序后需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度与空间复杂度。
随着计算机硬件的不断更新迭代,目前主要关注的是时间复杂度。时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小,所以对空间复杂度很是在乎,如今计算机的存储容量已经达到了很高的程度,所以不需要再特别关注一个算法的空间复杂度。
二.时间复杂度
1.定义:算法的时间复杂度是一个函数式T(N),定量描述了算法的运行时间,时间复杂度是衡量程序的时间效率,但我们不能用程序的运行时间来作为算法的时间复杂度。原因有三:
a.程序的运行时间与机器的编译环境和运行机器的配置都有关系;
b.同一个算法程序,用一个老配置机器和高配置机器运行,运行时间也不同;
c.时间只能程序写好后测试,不能在写程序前通过理论计算估计。
我们选用了语句的执行次数来作为算法时间复杂度的衡量,这样既脱离了编译环境的影响,并且执行次数和运行时间就是呈正相关,即执行次数就就可以代表程序时间效率的优劣。
示例1:
在图上所示的程序中 ,第一个for循环外层执行N次,其中的内层的语句执行N次,即N^2次,第二个for循环执行2*N次,while循环执行十次,所以此程序一共执行了N^2+2*N+10次,时间复杂度函数表达式表示为T(N)=N^2+2*N+10。
但当N的数值很大时,由高数的部分知识可以知道,2*N与常数项10的影响很小,呈线性,而N^2的影响占很大部分,由此我们可有了时间复杂度的大O渐进表示法。
2.大O的渐进表示法
推导大O阶规则:
a.时间复杂度函数式T(N)中,只保留最高阶项,去掉那些低阶项,因为当N不断变大时,低阶想对结果影响越来越小,当N无穷大时,就可以忽略不计了。
b.如果最高阶项存在且不是1,则去除这个项的常数系数,因为当N不断变大,这个系数对结果影响越来越小,当N无穷大时,就可以忽略不计了。
c.T(N)中如果没有N相关的项目,只有常数项,用常数1取代所有加法常数。
以下再举出几个时间复杂度的例题:
示例2.
第一个for循环中执行了M次,第二个for循环中执行了N次,所以用时间复杂度的函数表达式表示为T(N)=M+N,用大O的渐进表示法为O(M+N)。O(N)和O(M)都是错误的。N和M都是变量。
但若已经给出条件M>>N,则为O(M),反之为O(N)。
示例3.
for循环中执行了100次,T(N)=100,O(1),这里的1表示的是常数,并不是次数。
示例4.
这里T(N)取决于字符串的长度及查找的位置。如果查找的字符在前半段,则T(N)=1,O(1),如果在后半段甚至在字符串外,则T(N)=N,O(N)。所以最好的情况为O(1),最坏的情况为O(N),平均情况为O(N)。
通过示例4可以发现,有些算法的时间复杂度存在最好,平均和最坏情况。
最坏情况:任意上输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
大O的渐进表示法在实际中一般情况下关注的是算法的上界,也就是最坏运行情况。
示例5.
首先此排序是升序排序,exchange是为了避免数组本身就是有序的,先在第一层循环中定义exchange=0,若数组有序,则进入第二层循环,遍历所有数之后未发生交换不会执行第二层for循环中的if语句,exchange仍等于1,直接走到if语句跳出外层循环,则此时第二层for循环中的语句执行了N次,T(N)=N,时间复杂度表示为O(N)。
若数组无序时,则不会跳出外层循环,当外层循环执行第一次时,内层循环执行n-1次;执行第二次时,end减了一,则内层循环执行了n-2次,如此执行下去,外层循环共执行n次,内层循环执行了(n-1)+(n-2)+...+3+2+1次,由等差数列的求和公式易知,一共是二分之一乘n平方减n。根据大O的渐进表示法,时间复杂度表示为O(N^2)。
示例6.
前置条件为cnt=1,进入while条件循环后,每次以二倍递增,设x次可跳出循环,令2^x=n,则x=log以2为底的n,在这里当n为无穷大时,底数对结果的影响并不大,所以可以简写为log n。即次语句执行log n次。若在此设n=10,则当x=4时跳出循环。在此log n为常数,所以T(N)=log n,时间复杂度表示为O(logn)。
示例7.
到N=0之前,一直会执行N次return Fac(N-1)*N,或者是每次执行一次return Fac(N-1)*N,每次的复杂度是O(1),直到N=0时,一共执行了N次,N个O(1)相加,最终复杂度为O(N)。
递归的时间复杂度=单次递归时间复杂度*递归次数
三.空间复杂度
空间复杂度也是一个数学表达式,是对一个算法再运行过程中因为算法的需要额外临时开辟的空间。空间复杂度不是程序占用了多少byte的空间,因为常规情况每个对象大小差异不会很大,所以空间复杂度算的是变量的个数。
空间复杂度计算规则基本根时间复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数,局部变量,一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时显示申请的额外空间来确定。
以下再给出几个空间复杂度的例题。
示例8.
只需要计算额外申请的空间,图上所画三处,则T(N)=3,用大O的渐进表示法表示时间复杂度为O(1)。
示例9.
此处需要递归N次,创建N个函数栈帧,所以T(N)=N,则时间复杂度为O(N) 。
下面举出各个时间复杂度的曲线图供参考:
时间复杂度越高时间越长,尽量避免时间复杂度过高,最好控制在一次。