有循环就是O(n)时间复杂度吗?

Posted 前端自学驿站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有循环就是O(n)时间复杂度吗?相关的知识,希望对你有一定的参考价值。

时间复杂度

如何推导对应的时间复杂度

O(logn)

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

O(nlogn)

function test() {
  for (let i = 0; i < n; i += i) {
    for (let j = 0; j < n; j++) {
      console.log(北歌);
    }
  }
}

O(n^2)

function test(n) {
  let sum = 0
  let i = 1
  let j = 1

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

算法中的时间复杂度

时间复杂度的好坏决定了当前这段代码(算法)的优劣,在工业中使用到的算法一般不会超过O(n^2),所以打上flag的时间复杂度都是不太好的,当数据规模足够大的时候,所实现的算法是非常消耗时间的。最好的是O(1),最差的是。。。没有最差,只有更差

评估代码(算法)时间复杂度可以忽略的部分

  • 系数
  • 底数
  • 低阶

不懂什么意思没关系,后面会一一解释 ,只需要记住在评估算法时间复杂度过程中会忽略那些对算法的时间复杂度增长趋势不起绝对性因素的部分

大O表示法

T(n)=O(fn(n))
  • T(n):代码执行的时间
  • n:数据规模的大小
  • f(n): 每行代码执行的次数总和(也就是当前算法的时间复杂度)

此处需要举个栗子,n代表的是数据规模的大小,我们把n代数进去看一下

n = 1000
T(1000) = O(f(1000))

n(100) = O(f(100))

当1000的数据规模在当前这块代码需要的时间就是当前这段代码执行1000次,100次也是同理 ; 所以说他两是成正比的关系,当数据规模越大代码执行的次数也就越多。所以如果你的时间复杂度很高,那你代码的执行时间肯定就会越高,根据数据规模大小导致代码执行时间递增或递减 ,所以时间复杂度的全称叫"渐进式时间复杂度"

看下面这两段代码

function f1(n) {
  while (n--) {
    console.log(北歌)
  }
}

function f2(n) {
  let i = 1
  while (i < n) {
    i *= 2
  }
}
f1(1000)
f2(1000)

哪个更快?相信就算不了解时间复杂度推导的小伙伴也清楚是f2更快,并且数据量越大两者之间的优劣就会越明显。

O(1)「常数阶」

function f() {
  let i = 1 // 1(unit-time)
  let j = 1 // 1(unit-time)
  console.log(北歌) // 1(unit-time)
}
f(100)
T(100) = O(f(100))

这里的数据规模大小对当前代码的时间复杂度有影响吗?

在评估算法时间复杂度中,不管是赋值语句、输出数据、分支语句都算1(unit-time),简称1,所以这段代码的时间复杂度就是O(3)?

很显然我们并没有见过这样时间的复杂度,在评估时间复杂度中,我们会忽律那些对代码时间复杂度的增长趋势不起绝对性因素的部分,这里O(3)就是O(1), 就算有1000条输出语句也还是O(1)

有循环就一定不是O(1)?

function f() {
  for (let i = 0; i < 100; i++) {
    console.log(前端自学驿站)
  }
}
f(100)
f(10000000)
循环语句: 
    i = 1 // 1
    i < 100 // 100
  i++ // 100
循环体:
     console.log(前端自学驿站) // 100

这段代码中出现了循环语句,但是还是O(1), 为什么?

O(logn)「对数阶」

这块需要回忆下高中知识,对数(log)函数

function test(n) {
  let a = 1 // 1 unit-time
  while (a < n) {
    // 循环体执行多少次?
    /* 
      当a不小于n就跳出循环,假设循环x次,a不小于n;循环x次, a每次都乘以2

      x: 1 => a: 2 * 1 => 2的一次方(2^1)
      x: 2 => a: 2 * 2 => 2的二次方(2^2)
      x: 3 => a: 4 * 2 => 2的三次方(2^3)
      从推导过程得知a^x = n, x = loga^n,最后我们代数进去算一下
      n: 2 => log2^2(log以2为底的2次方) = 1
      打住,并不是为了解题,从推导过程中得出时间复杂度是: log2^n, 前面说过底数、系数、低阶都可以忽略,
      最后得出O(logn)
    */
    a *= 2  // a *= 2 => a = a * 2
  }
}
时间复杂度推导: 1 + log2^n 

在数学中我们是log10^n, 当底数为10的时候忽略底数,在程序中不管底数是多少都可以直接忽略,因为它对时间复杂度的增长趋势不起绝对性作用

忽略底数后: 1 + logn

忽略低价,只保留对时间增长趋势起绝对性作的最高阶部分

忽略低阶后:logn

代数来验证数据规模大小对算法时间复杂度的影响

这是我们最开始推导出来的公式: 1 + log2^n 

T(1000) = O(f(1+log2^1000))
T(1000000) = O(f(1+log2^1000000))

是不是数据规模越大,当前算法的时间复杂度所花费的时间就越多?在当前公式中1就是低阶,数据规模变大,他并没有增长趋势,log2^1000中起绝对性的是1000(真数),并不是底数2, 所以也可以忽略底数

所以下面这段代码的时间复杂度是多少?

function test(n) {
  let i = 1 // 1
  while (i < n) {
    // i += i => i = i + i => i = i * 2
    i += i
  }
}
i = i * 2,跟上面是一样的,假设循环x次后,i不小于n, 那么 i^x = n, x = log(2^n)。
结果也是O(logn)
function test(n) {
  let i = 1 // 1
  while (i < n) {
    i *= 3
  }
}
i = i * 3,假设循环x次后,i不小于n, 那么 i^x = n, x = log(3^n)。
结果也是O(logn)

O(n)「线性阶」

function test(n) {
  /*   
     i = 0 => 1
     i < n => n
     i++ => n
     外层循环执行:1+n+n => 1+2*n => 1+2n
   */
  for (let i = 0; i < n; i++) {

    /* 
      循环的循环体执行多少次取决于什么时候跳出循环, 循环n次,每次都只可能走一个分支也就是
      n
    */
    if (n % 2 === 0) { // n
      console.log(北歌)
    } else { // n
      console.log(前端自学驿站)
    }
  }
}
1+2n(循环) + n(循环体)
   结果: 1+3n
   忽律系数后: 1 + n
   忽律底数后: 没有底数
   忽律低阶后: n
   最终结果: n

线性阶应该是最好推导的吧,没啥好说的

以上是关于有循环就是O(n)时间复杂度吗?的主要内容,如果未能解决你的问题,请参考以下文章

for 循环的大 O 表示法简单问题

如何计算C++的复杂度?

以下代码片段的算法复杂度

为啥以下算法(循环排序?!)的时间复杂度是 O(n)?

在循环内使用 indexOf 是一个坏主意吗?

算法复杂度O(logn)详解