算法的复杂度介绍
创始人
2024-05-30 22:30:07
0

算法的复杂度介绍

算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。
为什么要进行算法分析?因为我们需要预测算法所需的资源:计算时间(CPU 消耗)、内存空间(RAM消耗)、通信时间(带宽消耗)等。
除了需要预测算法所需的资源,我们还需要预测算法的运行时间:在给定输入规模时,所执行的基本操作数量,或者称为算法复杂度(Algorithm Complexity)。
那么,应该如何去衡量不同算法之间的优劣呢?
主要还是从算法所占用的“时间”和“空间”两个维度去衡量。
时间维度:是指执行当前算法所消耗的时间,我们通常用“时间复杂度”来描述。
空间维度:是指执行当前算法需要占用多少内存空间,我们通常用“空间复杂度”来描述。

算法分析主要关注点是:最坏情况(Worst Case):任意输入规模的最大运行时间。
算法分析要保持大局观(Big Idea),其基本思路是:
(1)忽略掉那些依赖于机器的常量。
(2)关注运行时间的增长趋势。

一、时间复杂度(Time complexity)
算法的“时间复杂度”,很多人首先想到的的方法就是把这个算法程序运行一遍,那么它所消耗的时间就自然而然知道了。这适合事后分析统计方法,但这种方式非常容易受编程语言、运行环境的影响,在性能高的机器上跑出来的结果与在性能低的机器上跑的结果相差会很大。而且对测试时使用的数据规模也有很大关系。再者,并我们在写算法的时候,还没有办法完整的去运行呢。另一种更为通用的方法就出来了:“ 大O表示法”,记做T(n)=O(f(n)),撇开上述因素,认为算法的执行时间是问题规模n的函数,大O表示代码执行时间随数据规模增长的变化趋势。算法的时间复杂度反映了程序执行时间随输入规模增长而增长的量级。

【若有某个辅助函数 f(n),使得当 n 趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称 f(n)是 T(n)的同数量级函数,记作 T(n)=O(f(n)),O(f(n))称为算法的渐进时间复杂度,简称时间复杂度。
虽然对 f(n)没有规定,但是一般都取尽可能简单的函数。
Θ(2n2+n+1)=n2(Θ即去掉低阶项、常数项、高阶项的常参得到)】

求解算法的时间复杂度的具体步骤是:
(1)首先找出算法中的基本语句。算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
(2)计算基本语句的执行次数的数量级。只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上,那就是增长率。
(3)用 O(大写字母 O)记号表示算法的时间性能。将基本语句执行次数的数量级放入 O(大写字母 O)记号中。
如果算法中包含嵌套的循环,则基本语句通常是最内层的循环体;如果算法中包含并列的循环,则将并列循环的时间复杂度相加。
例如,有以下程序段

for (i=1; i<=n; i++) x++; 
for (i=1; i<=n; i++) for (j=1; j<=n; j++) x++;  

第一个 for 循环的时间复杂度为 O(n),第二个 for 循环的时间复杂度为 O(n2),整个算法的时间复杂度为 O(n+n2)= O(n2)。

O(1)表示基本语句的执行次数是一个常数,一般来说,只要算法中不存在循环语句,其时间复杂度就是 O(1)。

算法的执行时间取决于控制结构和原操作的综合效果。但一般重点关注原操作的次数。
在一个算法中,执行原操作的次数越少,其执行时间也就相对地越少;执行原操作次数越多,其执行时间也就相对地越多。算法中所有原操作的执行次数称为算法频度,这样一个算法的执行时间可以由算法频度来计量。

例子:

for(i=1; i<=n; ++i)
{j = i;j++;
}

假设每行代码的执行时间都是一样的,我们用 1颗粒时间 来表示,那么这个例子的第一行耗时是1个颗粒时间,第三行的执行时间是 n个颗粒时间,第四行的执行时间也是 n个颗粒时间(第二行和第五行是符号,暂时忽略),那么总时间就是 1颗粒时间 + n颗粒时间 + n颗粒时间 ,即 (1+2n)个颗粒时间,即: T(n) = (1+2n)*颗粒时间,从这个结果可以看出,这个算法的耗时是随着n的变化而变化,如果n无限大的时候,常量1就没有意义了,倍数2也意义不大,因此,我们可以简化的将这个算法的时间复杂度表示为:T(n) = O(n)

又如:求两个n阶方阵相加C=A+B的算法如下,求T(n)。

void matrixadd(int A[N][N],int B[N][N],int C[N][N],int n)
{  for (int i=0;i

T(n)= n+1+n(n+1)+n2 = 2n2+2n+1。公式中低阶、常量、系数三部分并不会左右增长趋势,可以忽略,所以大O表示法只要取f(n)中最大量级的即可, T(n) =2n2+2n+1 =O(n2),或T(n)=O((n2))
也就是只关注T(n)的最高阶,忽略其低阶项和常系数,这样既可简化T(n)的计算,又能比较客观地反映出当n很大时算法的时间性能。
一般地:
• 一个没有循环的算法的执行时间与问题规模n无关,记作O(1),也称作常数阶。
• 一个只有一重循环的算法的执行时间与问题规模n的增长呈线性增大关系,记作O(n),也称线性阶。
• 其余常用的算法时间复杂度还有平方阶O(n2)、立方阶O(n3)、对数阶O(logn)、指数阶O(2n)等。

常见的时间复杂度量级有:
常数阶O(1)
对数阶O(logn) 【:在计算机领域,对数log一般以2为底数,不写】
线性阶O(n)
线性对数阶O(nlogn)
平方阶O(n²)
立方阶O(n³)
K次方阶O(nk)
指数阶(2n)
上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。

下面分别对几个常见的时间复杂度进行示例说明。

常数阶O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1),如:

int i = 1;
int j = 2;
int m = i + j;

上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。一般来说,只要算法中不存在循环语句,其时间复杂度就是 O(1)。

线性阶O(n)
这个在最开始的代码示例中就讲解过了,如:

for(i=1; i<=n; ++i)
{j = i;j++;
}

这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。

对数阶O(logN)
还是先来看代码:

int i = 1;
while(ii = i * 2;
}

从上面代码可以看到,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n
也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)

线性对数阶O(nlogN)
线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。
对上面的代码修改一点:

for(m=1; mi = 1;while(ii = 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²) 去理解立方阶O(n³)、K次方阶O(nk),O(n³)相当于三层n循环,其它的类似。

二、空间复杂度(Space Complexity)
时间复杂度不是用来计算程序具体耗时的,空间复杂度也不是用来计算程序实际占用的空间的。一个算法的存储量包括形参所占空间和临时变量所占空间。在对算法进行存储空间分析时,只考察临时变量所占空间。

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义,记做S(n)=O(g(n))。其中“O”的含义与时间复杂度分析中的相同。
例如:

int Max(int a[],int n)
{int maxi=0;for (int i=1;ia[maxi])maxi=i;return a[maxi];
}

函数体内分配的变量空间为临时空间,不计形参占用的空间,这里的仅计i、maxi变量的空间。空间复杂度为S(n) =1+1 =O(1)

空间复杂度比较常用的有:O(1)、O(n):

空间复杂度 O(1)
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1)
举例:

int i = 1;
int j = 2;
++i;
j++;
int m = i + j;

代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 S(n) = O(1)

空间复杂度 O(n)
我们先看一个代码:

int[] m = new int[n]
for(i=1; i<=n; ++i)
{j = i;j++;
}

这段代码中,第一行new了一个数组出来,这个数据占用的大小为n,这段代码的2-6行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)

对于一个算法,其时间复杂度和空间复杂度往往是相互影响的。当追求较低的时间复杂度时,可能会使空间复杂度增加,即占用较多的存储空间;反之,当追求较低的空间复杂度时,可能会使时间复杂度增加,即耗费较长的运行时间。
另外,算法的所有性能之间都存在着或多或少的相互影响。因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,包括算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等,才能够设计出比较好的算法。

附录
https://segmentfault.com/a/1190000023860213

相关内容

热门资讯

王者定位怎么关安卓系统,轻松实... 你是不是也和我一样,对王者荣耀这款游戏爱得深沉呢?不过,有时候游戏里的设置让人头疼,比如安卓系统的王...
树莓派安卓系统流畅,打造便携式... 亲爱的读者们,你是否曾想过,将树莓派与安卓系统结合,会擦出怎样的火花呢?今天,就让我带你一起探索这个...
安卓系统智能机顶盒,引领家庭娱... 你有没有想过,家里的电视也能变得智能起来?没错,就是那个陪伴我们多年的老电视,现在也能摇身一变,成为...
安卓系统很差了吗现在,性能优劣... 最近是不是有不少朋友在讨论安卓系统的问题呢?有人说它越来越差了,也有人觉得它还是那个熟悉的“老朋友”...
安卓系统uc安装包,Andro... 你有没有发现,手机里的安卓系统越来越强大了?今天,咱们就来聊聊这个话题——安卓系统中的UC安装包。你...
安卓系统谷歌能删吗,谷歌能否删... 你有没有想过,那个一直陪伴你手机生活的安卓系统,它背后的谷歌爸爸,是不是也能被你随意删掉呢?这可不是...
安卓系统会不会更耗电,解析其功... 你有没有发现,手机用着用着,电池就有点不给力了?尤其是那些用安卓系统的手机,有时候感觉电就像流水一样...
安卓系统中无效目录,安卓系统无... 你有没有遇到过在安卓系统中,明明文件夹就在那里,但是就是找不到的情况?别急,今天就来给你揭秘安卓系统...
国产安卓机哪个系统好用,探寻最... 你有没有想过,国产安卓机哪个系统最好用呢?这可是个让人纠结的问题,毕竟每个系统都有它的特色和亮点。今...
安卓系统cpua9,引领性能与... 你有没有发现,最近你的安卓手机运行得是不是比以前顺畅多了?这可多亏了那个强大的安卓系统CPUA9啊!...
安卓系统usb驱动程序,功能、... 你有没有遇到过这种情况:手机里存了那么多宝贝照片和视频,想传输到电脑上保存,结果电脑却像个小顽皮,死...
安卓操作系统怎么关闭,轻松关闭... 手机里的安卓操作系统是不是有时候让你觉得有点儿烦呢?别急,今天就来手把手教你如何轻松关闭安卓操作系统...
追星手机壳推荐安卓系统,盘点热... 你有没有发现,现在追星族们对手机壳的热爱简直到了疯狂的地步?没错,就是那种能让你一秒变身偶像迷妹的手...
ios系统用安卓系统游戏下载软... 你有没有想过,明明是iOS系统的手机,却想玩安卓系统的游戏?这可不是什么天方夜谭,现在就有这么神奇的...
安卓高系统怎么用美化,打造专属... 亲爱的安卓用户们,你是不是也和我一样,对手机系统美化情有独钟呢?想要让你的安卓手机焕然一新,变得个性...
安卓系统怎么开夜间模式,安卓系... 亲爱的手机控们,你是不是在夜晚使用安卓手机时,眼睛感到有些不适?别担心,今天我要给你揭秘一个超级实用...
王者安卓系统用苹果人脸,一场视... 你知道吗?最近在手机圈里可是掀起了一股不小的波澜呢!那就是王者安卓系统竟然用上了苹果人脸识别技术!是...
安卓444怎么升级系统,轻松迈... 你那安卓444的小家伙是不是已经有点儿落伍了?别急,今天就来给你详细说说怎么给它来个系统升级,让它焕...
安卓系统raw修图软件,探索安... 你有没有发现,手机拍照越来越方便了,但有时候拍出来的照片还是不够完美呢?别急,今天就来给你安利几款安...
安卓系统的王者切换苹果,从安卓... 你知道吗?最近身边的朋友圈里掀起了一股热潮,那就是安卓系统的王者们纷纷切换到苹果阵营。这可真是让人大...