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

相关内容

热门资讯

安卓10系统省电不,安卓10系... 你有没有发现,自从升级到安卓10系统,手机续航能力好像大不如前了?别急,今天就来给你揭秘安卓10系统...
cm14安卓系统,深度定制与极... 你有没有发现,你的安卓手机最近是不是有点不一样了?是不是觉得系统运行得更加流畅,界面也更加美观了呢?...
平板安卓系统咋样升级,轻松实现... 你那平板安卓系统是不是有点儿卡,想给它来个升级大变身?别急,让我来给你详细说说平板安卓系统咋样升级,...
安卓原系统在哪下载,探索纯净体... 你有没有想过,为什么安卓手机那么受欢迎?那是因为它的系统——安卓原系统,它就像是一个充满活力的魔法师...
安卓系统procreate绘图... 你有没有发现,现在手机上画画变得越来越流行了?尤其是用安卓系统的手机,搭配上那个神奇的Procrea...
电视的安卓系统吗,探索安卓电视... 你有没有想过,家里的电视是不是也在悄悄地使用安卓系统呢?没错,就是那个我们手机上常用的安卓系统。今天...
苹果手机系统操作安卓,苹果iO... 你有没有发现,身边的朋友换手机的时候,总是对苹果和安卓两大阵营争论不休?今天,咱们就来聊聊这个话题,...
安卓系统换成苹果键盘,键盘切换... 你知道吗?最近我在想,要是把安卓系统的手机换成苹果的键盘,那会是怎样的体验呢?想象那是不是就像是在安...
小米操作系统跟安卓系统,深度解... 亲爱的读者们,你是否曾在手机上看到过“小米操作系统”和“安卓系统”这两个词,然后好奇它们之间有什么区...
miui算是安卓系统吗,深度定... 亲爱的读者,你是否曾在手机上看到过“MIUI”这个词,然后好奇地问自己:“这玩意儿是安卓系统吗?”今...
安卓系统开机启动应用,打造个性... 你有没有发现,每次打开安卓手机,那些应用就像小精灵一样,迫不及待地跳出来和你打招呼?没错,这就是安卓...
小米搭载安卓11系统,畅享智能... 你知道吗?最近小米的新机子可是火得一塌糊涂,而且听说它搭载了安卓11系统,这可真是让人眼前一亮呢!想...
安卓2.35系统软件,功能升级... 你知道吗?最近在安卓系统界,有个小家伙引起了不小的关注,它就是安卓2.35系统软件。这可不是什么新玩...
安卓系统设置来电拦截,轻松实现... 手机里总是突然响起那些不期而至的来电,有时候真是让人头疼不已。是不是你也想摆脱这种烦恼,让自己的手机...
专刷安卓手机系统,安卓手机系统... 你有没有想过,你的安卓手机系统是不是已经有点儿“老态龙钟”了呢?别急,别急,今天就来给你揭秘如何让你...
安卓系统照片储存位置,照片存储... 手机里的照片可是我们珍贵的回忆啊!但是,你知道吗?这些照片在安卓系统里藏得可深了呢!今天,就让我带你...
华为鸿蒙系统不如安卓,挑战安卓... 你有没有发现,最近手机圈里又掀起了一股热议?没错,就是华为鸿蒙系统和安卓系统的较量。很多人都在问,华...
安卓系统陌生电话群发,揭秘安卓... 你有没有遇到过这种情况?手机里突然冒出好多陌生的电话号码,而且还是一个接一个地打过来,简直让人摸不着...
ios 系统 安卓系统对比度,... 你有没有发现,手机的世界里,iOS系统和安卓系统就像是一对双胞胎,长得差不多,但细节上却各有各的特色...
安卓只恢复系统应用,重拾系统流... 你有没有遇到过这种情况?手机突然卡顿,或者某个应用突然罢工,你一气之下,直接开启了“恢复出厂设置”大...