时间复杂度

重拾算法之复杂度分析(大O表示法)

在论坛里经常会看到一句话:学不会算法就去做网页开发。当然,从某种层面来看,前端对算法的要求确实不高,毕竟想写一个级联选择器会找到一大把的组件库。但是呢,算法又是一个优秀的工程师必须具备的的基础内功。程序员之间流传这么一句话:“一流程序员靠数学,二流靠算法,三流靠逻辑,四流靠SDK,五流靠Google和StackOverFlow,六流靠百度和CSDN。低端的看高端的就是黑魔法!”。对于面试一些大公司而言,尤其是国外的互联网企业,算法也是绕不过的一道坎。

复杂度分析,会贯穿整个算法学习过程中。王争(前google工程师)说,掌握了复杂度分析,就掌握了算法学习的一半。因此,今天我们来聊复杂度分析——大O复杂度表示法。

 

一个简单的公式

引用算法图解一个有趣的例子:Bob要写一个算法计算月球登陆前执行帮助计算着陆点的算法,他用两种算法:简单查找跟二分查找。假设查找一个元素要1ms,查找100的元素简单查找要100ms,二分查找只需要检查7个元素( log2(7)),大概7ms。而如果要查找10亿个元素,用二分查找就是30ms左右,简单查找却要10亿毫秒。为什么会这样呢?因为两种算法运行时间的增速不同。在高中数学中学概率问题都是基于大量数据统计,同样,估算算法的执行效率,也是基于对运行时间与数据的渐进式增长的粗略分析,我们的大O表示法该登场了!

假设每个语句执行时间为t,则以下代码:

function sum (){
  let sum = 0;     /*执行时间t*/
  let i = 0;       /*执行时间t*/
  let j = 0;       /*执行时间t*/

  for(; i < n; i++){     /*执行时间n*t */
    j = 1;                        /*执行时间n*t*/
    for(; j < n; j++){   /*执行时间n的平方*t*/
      sum = sum + i * j;          /*执行时间n的平方*/
    }
  }
}

整段代码整的执行时间为: T(n) = (2n²+2n+3)*t ,所有代码的执行时间T(n)与每行代码的执行次数成正相关!大O表示法计算的并不是代码的正式运行时间,而是代码执行时间随数据规模增长的变化趋势,即 时间复杂度

 

时间复杂度分析

1. 只关注循环执行次数最多的一段代码

在分析代码复杂度的时候,只关注循环执行次数最多的那一段代码就可以,如下代码:

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

上述代码我们只需要关注for循环代码执行次数n,所以中的时间复杂度为O(n)。

2. 加法法则:总复杂度等于量级最大的那段代码的复杂度

我们回到这篇文章的第一段代码,该代码中的执行时间为 T(n) = (2n²+2n+3)*t ,所以量级最大的那段代码运行时间为n的平方,则这段代码的时间复杂度为:O(n²).

3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

function cal(n) {
   let ret = 0; 
   for (let i + 0; i < n; i++) {
     ret = ret + f(i);
   } 
 } 

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

以上代码在cal函数中嵌套f函数,cal函数不看f,时间复杂度为O(n),f函数的时间复杂度为O(n),整个cal函数的时间复杂度为O(n²)。

 

常见的时间复杂度量级

1. O(1)

 let i = 8;        /*  时间复杂度: O(1)  */
 let j = 6;        /*  时间复杂度: O(1)  */
 let sum = i + j;  /*  时间复杂度: O(1)  */

2. O(logn)、O(nlogn)

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

上述代码,当i = 1时,进入while后,i 赋值为2,下一次赋值为4,依次指数增长,那么他在while中执行的次数为log2(n),忽略系数,则时间复杂度为O(logn)。在排序中如归并排序,快速排序的时间复杂度为O(nlogn)。

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

int cal(int m, int n) {
  int sum_1 = 0;
  int i = 1;
  for (; i < m; ++i) {
    sum_1 = sum_1 + i;
  }

  int sum_2 = 0;
  int j = 1;
  for (; j < n; ++j) {
    sum_2 = sum_2 + j;
  }

  return sum_1 + sum_2;
}

上述代码的复杂度为 m + n。

4. O(n2)----选择排序

关于排序算法的详解及时间复杂度分析

5. O(n!)

著名的旅行商问题,有一位旅行商,要去5个城市,要求找出路程总和最少的路线,我们只能把所有可能的路线可能5!= 120中方法算一遍来找出,这是计算机领域无最优解的问题

 

不同的大O运行时间

1. 最好情况时间复杂度与最坏时间复杂度

// n 表示数组 array 的长度
function find(array, n, x) {
  let pos = -1;
  for ( let i = 0; i < n; i++) {
    if (array[i] == x) {
       pos = i;
       break;
    }
  }
  return pos;
}

在一个长度为n的数组中找一个数,如果在遍历第一次就找到,则时间复杂度为O(1),如果整个数组遍历了一遍,那么时间复杂度为O(n)。

最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间。

最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间。

2. 平均情况时间复杂度

高中时,我们都学过数学期望,平均时间复杂度的实质就是数学期望。
以上代码中,查找X在数组中的位置有n+1中情况,即在0~n-1或者不在数组中,把查找的每种情况遍历的元素除以查找次数,就是遍历元素的平均值。

忽略系数,则时间复杂度为O(n)。但是,在概率统计中,查找元素时,要么可以查找到,要么差找不到,我们假设他们的概率各位一半,那么计算如下:

最终得到的时间复杂度为O(n)。

3. 均摊时间复杂度

 /* array 表示一个长度为 n 的数组
  代码中的 array.length 就等于 n */
 let array = new Array[n];

 function insert(val) {
    if (count == array.length) {
       let sum = 0;
       for (let i = 1; i < array.length; i++) {
          sum = sum + array[i];
       }
       array[0] = sum;
       count = 1;
    }

    array[count] = val;
    count++;
 }

以上代码有两种情况:

  1. 当count不等于数组长度时,每次调用函数的时候,时间复杂度为O(1),这种情况出现的次数为n次;

  2. 当count等于数组长度时,调用函数时,走进for循环,此时时间复杂度为:O(n)

  3. 平均时间复杂度计算:

该例子较上面的例子的特殊之处在于,函数在大多数情况下的时间复杂度为O(1),而当最坏时间复杂度的时候出现的几率很小,这时候我们需要引入一种分析的方法叫:摊还分析法,通过该分析法得到的时间复杂度为均摊时间复杂度

在上面的例子里,每一次O(n)的插入都会有n-1次时间复杂度为O(1)的插入,我们吧耗时多的这次的操作均摊到n-1次耗时少的上面,实际的均摊复杂度就为O(1)。
均摊复杂度的出现情况很少,可以把它认为是一种特殊的平均时间复杂度。它的应用场景都是在一系列连续操纵中,大部分情况复杂度低,个别复杂度高,这时候就可以平摊下来。而且,一般情况下,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。


参考文档:
1: 王争(前google工程师)专栏 ----《数据结构与算法之美》
2: 《算法图解》------大O表示法部分


转载自:

https://www.cnblogs.com/ylweb/p/10708382.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值