算法复杂度分析(上)

扫码关注公众号,获取更多内容

 

目录

一、为什么需要复杂度分析?

二、大O复杂度表示法

三、时间复杂度分析

1、只看循环执行次数最多的一段代码

2、加法法则

3、乘法法则

四、常见时间复杂度实例

1、O(1)

2、O(㏒n)、O(n㏒n)

3、O(m+n)、O(m*n)

五、空间复杂度


一、为什么需要复杂度分析?

时间、空间复杂度是衡量算法非常重要的考量指标,当我们进行实际运行检测的时候,不同的处理器和数据规模,将会导致算法的性能表现不一致,所以我们需要通过复杂度分析来对算法进行考量。

 

二、大O复杂度表示法

粗略来讲,算法的执行效率就是算法代码执行的时间,以下面这段代码为例(求1,2,3...n的累加和)

function f1(n) {
    let sum = 0;
    for (let i = 0; i < n; i++) {
        sum = sum + i
    }
    return sum
}

从cpu的角度来看,代码执行的每一行都执行着类似的操作:读数据-运算-写数据,因为是粗略估计,所以可以假设每行代码执行的时间都一样,设为 time,在这个假设的基础上,我们来看看上述代码执行的总时间是多少呢?

第2行代码执行需要1个time的时间,第3、4的代码都运行了n遍,所以需要 2n*time 的时间,所以执行总时间就是(2n+1)*time,可以看出,代码执行总时间T(n)与每行代码的执行次数成正比

按照这个分析思路,我们再看下面这段代码:

function f2(n) {
   let sum = 0;
   for (let i = 1; i <= n; i++) {
       for (let j = 1; j <= n; j++) {
           sum = sum + i * j
       }
   }
   return sum
}

上述代码的总执行时间T(n)是多少呢?

第2行代码需要1个time的时间,第3行代码执行了n遍,需要 n*time 的执行时间,第4、5行代码循环执行了n²遍,需要 2n² * time,所以整段代码的执行时间 T(n)=(2n²+n+1)*time。

通过以上例子我们可以得出一个规律:所有代码的执行时间T(n)与每行代码额执行次数f(n)成正比。

我们可以把这个规律总结成一个公式:T(n) = O(f(n))

其中T(n)代表代码执行的时间;n表示数据规模的大小;f(n)表示每行代码执行的次数总和;O表示代码执行时间T(n)与f(n)表达式成正比。

我们回过头来看:第一个例子可以表示为:T(n) = O(2n+1),第二个例子可以表示为:T(n) = O(2n²+n+1),这就是大O时间复杂度表示法,它并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以也叫作渐进时间复杂度,简称时间复杂度

 

三、时间复杂度分析

 

1、只看循环执行次数最多的一段代码

大O只是表示复杂度的一种变化趋势,通常我们会忽略掉公式中的常量、低阶、系数,只需要记录一个最大的量级就可以,所以我们在分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了

以前面的例子为例

function f1(n) {
    let sum = 0;
    for (let i = 0; i < n; i++) {
        sum = sum + i
    }
    return sum
}

其中第2行代码是常量执行时间,与n无关,所以对算法复杂度没有影响,第3、4行都被执行了n次,所以总的时间复杂度就是O(n)。

 

2、加法法则

尝试着分析一下下方代码的时间复杂度

function f3(n) {
   let sum1 = 0;
   for (let p = 1; p <= 100; p++) {
       sum1 = sum1 + p;
   }

   let sum2 = 0;
   for (let q = 0; q < n; q++) {
       sum2 = sum2 + q;
   }

   let sum3 = 0;
   for (let i = 0; i <= n; i++) {
       for (let j = 0; j <= n; j++) {
           sum3 = sum3 + i + j;
       }
   }

   return sum1 + sum2 + sum3
}

上述代码分为三部分,分别是求 sum1、sum2、sum3。我们的思路是:分析每一部分的时间复杂度,然后进行对比,选取一个量级最大的作为整段代码的复杂度。

第一段:代码循环执行了100次,所以是一个常量的执行时间,与n的规模无关。

第二段:O(n)

第三段:O(n²)

所以上述代码块的时间复杂度是O(n²)

 

3、乘法法则

我们可以吧乘法法看成是嵌套循环

function f4(n) {
   let sum = 0;
   for (let i = 0; i < n; i++) {
       sum = sum + f5(i)
   }
}

function f5(n) {
   let sum = 0;
   for (let j = 0; j < n; j++) {
       sum = sum+j;
   }
   return sum
}

单独看f4,假设f5是一个普通操作,那么第一个函数的复杂度为:T1(n) = O(n),但f5本身不是一个简单操作,它的时间复杂度为T2(n) = O(n),所以上述函数f4的时间复杂度为:T(n) = T1(n)*T2(n) = O(n*n) = O(n²)。

 

四、常见时间复杂度实例

 

1、O(1)

O(1)只是常量级时间复杂度的一种表示方法,并不是指只执行了一行代码,如下面这段,即便有三行,它的时间复杂度也是O(1),而不是O(3)。

let a = 1;
let b = 2;
let c = 3;

只要代码的执行时间不随n的增大而增长,这样代码的时间复杂度我们都记作O(1)。

 

2、O(㏒n)、O(n㏒n)

对数时间复杂度非常常见,同时也稍难分析,我们看下一段代码

let i = 1;
while (i <= n) {
    i = i * 2;
}

根据之前的分析方法我们可以知道,第三行的代码是执行次数最多的。变量i从1开始,没循环一次就乘以2。当大于n时,循环结束,实际上i的取值是一个等比数列:2^0  2^1  2^2  ... 2^k ... 2^x = n。所以我们只需要知道x值是多少,就知道代码执行次数了。通过求解 2^x = n,得到x=㏒ 2(n)(以2为底n的对数),所以这段代码的世界复杂度是O(㏒2(n))。因为对数之间可以相互转化,故我们可以忽略对数的底(当成常系数),统一表示为:O(㏒n)。

根据乘法法则,如果一段代码的时间复杂度是O(㏒n),我们循环执行n遍,其时间复杂度就是O(n㏒n)。

 

3、O(m+n)、O(m*n)

下面代码的复杂度是由两个数据规模决定的

function f6(m, n) {
   let sum = 0;
   for (let i = 0; i < m; i++) {
       um = sum + i;
   }

   let sum2 = 0;
   for (let j = 0; j < n; j++) {
       sum2 = sum2 + j;
   }

   return sum + sum2
}

上方代码无法分出m与n的量级,所以时间复杂度为O(m+n),如果是嵌套结构,那么复杂度为O(m*n)(遵循乘法法则)。

 

五、空间复杂度

时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。

类比一下,空间复杂度全称就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。

function f7(n) {
   let a = new Array(n);
   for (let i = 0; i < n; i++) {
       a[i] = i * 2;
   }

   for (let j = n - 1; i >= 0; --j) {
       console.log(a[j])
   }
}

与时间复杂度分析一样,第1行代码申请了一个大小空间为n的数组,for循环申请了一个存储变量i和j,他们属于常量级,跟数据规模n没关系,所以整段代码的空间复杂度是O(n)。

常见的空间复杂度是O(1)、O(n)、O(n²),像O(㏒n)、O(n㏒n)平时用的比较少。

 

 

 

参考:数据结构与算法之美

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值