算法的复杂度介绍
创始人
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

相关内容

热门资讯

安卓原生系统时间校准,基于安卓... 手机时间不准了?别急,我来教你如何轻松搞定安卓原生系统时间校准! 话题引入:手机时间不准,是不是让你...
主机系统内存和安卓联机,主机系... 你有没有想过,为什么你的手机在玩大型游戏时总是卡得要命?又或者,为什么你的电脑在处理复杂任务时,反应...
安卓如何手机上刷系统,轻松升级... 你有没有想过,你的安卓手机是不是已经有点儿“老态龙钟”了呢?别急,别急,今天就来教你怎么给它来个“青...
苹果系统观战安卓好友,观战新体... 亲爱的读者,你是否也有过这样的经历:一边享受着苹果系统的优雅与流畅,一边又忍不住好奇地观战安卓好友们...
安卓系统最好是哪个,最佳生成方... 你有没有想过,手机里的安卓系统哪个才是最适合你的呢?在这个信息爆炸的时代,手机已经成为了我们生活中不...
改时间安卓系统vivo,探索v... 你有没有发现,最近你的vivo手机有点儿“慢吞吞”的?别急,别急,让我来给你支个招儿,让你的安卓系统...
安卓系统的旋钮在哪,旋钮生成位... 你有没有发现,有时候手机上的小细节也能让人头疼不已?比如说,安卓系统的旋钮在哪?这问题看似简单,但不...
安卓手机app系统软件,探索安... 你有没有发现,现在手机里的app简直就像是个小宇宙,各种功能应有尽有,让人眼花缭乱。尤其是安卓手机,...
win111安卓子系统,开启跨... 哇,你有没有听说最近的大新闻?那就是Windows 11的安卓子系统!是的,你没听错,Windows...
游戏摇杆连安卓系统电视,畅享游... 你有没有想过,家里的安卓系统电视也能玩起游戏来?没错,就是那种让你手舞足蹈、热血沸腾的游戏摇杆!今天...
nokia平板系统兼容安卓,尽... 你有没有想过,那些曾经陪伴我们度过无数时光的诺基亚手机,现在竟然也能摇身一变,成为平板电脑的得力助手...
安卓原生系统是什么品牌,探索安... 你有没有想过,为什么你的手机那么流畅,界面那么美观?这背后,可是有一个强大的“大脑”在默默支撑着呢!...
安卓3大操作系统,从三大分支看... 你知道吗?在安卓的世界里,操作系统可是有着三大巨头呢!它们就像安卓世界的三驾马车,各自有着独特的魅力...
开源文件管理系统安卓,打造个性... 你有没有想过,手机里那些乱糟糟的文件,要是能有个好帮手,生活该有多轻松啊?今天,就让我带你走进一个神...
手机删除了系统安卓市场,手机系... 手机里的安卓市场突然不见了,这可怎么办呢?别急,让我来给你详细说说这个棘手的问题,让你轻松应对!一、...
安卓系统写脚本软件下载,基于安... 你有没有想过,你的安卓手机或者平板电脑,除了用来刷剧、玩游戏,还能变成一个强大的工作助手呢?没错,就...
安卓系统有哪些机型好,探索顶级... 你有没有想过,安卓系统里的手机型号那么多,哪一款才是最适合你的呢?别急,今天我就来给你好好盘点看看安...
安卓系统之间如何互传,安卓设备... 你是不是也和我一样,手机里存了那么多好东西,却苦于不能和好友分享呢?别急,今天就来教你怎么用安卓系统...
安卓系统启动修改工具,安卓系统... 你有没有想过,你的安卓手机启动速度竟然可以像火箭一样快?没错,这就是今天我要跟你分享的神秘工具——安...
安卓系统版本号历史,从初生到繁... 你有没有发现,每次打开手机,那系统版本号总是一闪而过,好像在悄悄告诉你:“我可是有故事的哦!”今天,...