得物技术谈谈算法入:算法的好坏?复杂度告诉你

什么是算法

百度百科对算法的定义是 “解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法表明着用系统的方法描述解决问题的策略机制。”算法

简单的来说,算法就是解决一个问题所用的方法。好比对于数组排序问题,有多种排序方法能够达到使元素有序排列的目的,每一种正确的排序方法都能称之为一个算法。数组

如何衡量算法的好坏

一个问题既然能够经过多种算法来解决,那么如何衡量哪一个算法更好呢?显然一个算法执行时间越短,占用的内存空间越小,那么它就是更好的算法。一般有两种方法来比较:事前分析和过后统计。过后统计就是指先编写好算法,并运行监控其指标,该方法的问题在于受运行环境、电脑硬件干扰,会掩盖算法自己的优劣。所以科学的方法是经过数学分析的方法来评估算法的好坏。函数

前面说到算法执行时间越短,占用内存空间越小,算法越好。对应的,咱们经常用时间复杂度来表明算法执行时间,空间复杂度表明算法占用的内存空间。.net

时间复杂度

通常来说,一个算法花费的时间与其中执行的语句的次数成正比,一个算法中的语句执行次数称为时间频度,用 T(n) 来表示。好比有 func 函数以下:code

function func(n) {
    for(let i = 0; i < n; i ++) {       // 执行n+1次
        console.log('Hello World!')     // 执行n次
    }
    return                              // 执行1次
}

对于 func 来说,该算法运行时,共执行了 2n+2 次运算,那么该算法的时间频度 T(n) = 2n + 2, n 为算法输入的大小,即输入的数据规模,当 n 不断变化时,T(n) 也会随之变化。blog

假设有某个辅助函数 f(n), 当 n 趋于无穷大时,总有 T(n) <= C f(n) (C 为常数) 成立,即 T(n) 的上界是 C f(n),记做 T(n) = O(f(n)) ,称 O(f(n)) 为算法的渐进时间复杂度,O 表示正比例关系,f(n) 表示代码执行次数之和, 这种表示方法叫作 「 大O符号表示法 」。排序

因此对于函数 func 来说,有 T(n) = O(2n+2),能够简化为 T(n) = O(n)。为何能够简化呢,是由于大O表示法只是用来表明执行时间的增加变化趋势,当 n 无穷大时,2n+2 中的常量就没有意义了,同时由于符号O中隐藏着常数C,因此 n 的系数2能够省略到C中。f(n) 中 n 通常不加系数。同理,咱们能够获得 O(2n²+n+1) = O(2n²) = O(n²)。若是把 T(n) 表示成一棵树,O(f(n)) 表示的就是树干,其余的细枝末节对于复杂度的影响远小于树干。内存

通常来说,衡量一个算法的复杂度,咱们每每只须要判断循环结构里基本操做发生了多少次,就能表明该算法的时间复杂度。get

常见的时间复杂度数学

  • 常数阶 O(1)

加减乘除(eg. i++, i--)、数组寻址(eg. array[10])、赋值操做(eg. a=1)等都属于常数级的复杂度,简言之若是没有循环等复杂结构,不随输入数量级 n 变化的操做,该代码的时间复杂度都属于常数级复杂度,无论这种代码有多少行。

  • 对数阶 O(logn)
function func(n) {
    let i = 0;
    while(i < n) {
        i *= 2;
    }
}

对如上 func 函数来说,在 while 循环中,假设当循环到第 m 次时, 有 i = n ,即 2 的 m 次方等于 n,可知 m = logn (每每将以2为底省略),即 while 循环内的操做运行了 logn 次,该算法的时间复杂度即为logn 。

  • 线性阶 O(n)
function func(n) {
    sum = 0;
    for(let i = 0; i < n; i++) {
        sum += i;
    }
    return sum; 
}

如上 func 函数,for 循环中的原子操纵执行了 n 次,该算法的时间复杂度即为 O(n)。

  • 线性对数阶 O(nlogn)
function func(n) {
    for(let m = 1; m < n; m++)
    {
        let i = 1;
        while(i < n)
        {
            i = i * 2;
        }
    }
}

如上函数,有双层循环结构,显然根据以前的时间复杂度能够推断出来该算法的时间复杂度为 O(nlogn) ,固然上面的函数是为了凑这个复杂度而凑的,并无实际的意义。

  • 平方阶 O(n²)
function func(n) {
    let sum = 0;
    for(let i = 0; i < n; i ++) {
        for(let j = 0; j < n; j ++) {
            sum += i * j;
        }
    }
    return sun;
}

如上函数,有双层循环结构,第5行代码,共循环运行了 n² 次,因此该算法的时间复杂度为 O(n²)。

  • 立方阶 O(n³),K次方阶O(n^k)

和平方阶同理,有多层循环结构,该循环结构中的原子操做循环的次数。

常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(logn)<Ο(n)<Ο(nlogn)<Ο(n²)<Ο(n³)<…<Ο(2^n)<Ο(n!)。

空间复杂度

空间复杂度是对一个算法在运行过程当中辅助变量临时占用存储空间大小的一个量度。空间复杂度和时间复杂度同理,也采用大O表示法,比较经常使用的有:O(1)、O(n)。

  • O(1)

算法执行时所须要的临时空间不随着数据规模 n 的大小变化,则算法的空间复杂度为 O(1),好比对于数组求和,其中 sum 和 i 都是临时变量,所须要的空间不随array长度的大小而变化,因此该函数的空间复杂度为 O(1)。

function func(array) {
    let sum = 0;
    for(let i = 0; i < array.length; i++) {
        sum += array[i];
    }
    return sum;
}
  • O(n)
function func(array) {
    let new_array = [...array];
    return new_array;
}

如上函数是复制了输入 array 数组,返回复制的数组,在函数中,建立了 array.length 长度的新数组 new_array,该算法的空间复杂度和原数组 array 的长度成正比,故该算法的时间复杂度为 O(n),n 表明数组 array 的长度。

至此咱们已经了解了常见的时间复杂度和空间复杂度,时间复杂度的分析须要根据具体的算法来分析,有了这个基础,咱们就能针对实际的算法进行分析啦!

参考

https://www.zhihu.com/question/21387264

https://zhuanlan.zhihu.com/p/50479555

https://www.jianshu.com/p/f4cca5ce055a

http://www.javashuo.com/article/p-awbjfjnb-hb.html

文|hustlmn

关注得物技术,携手走向技术的云端

相关文章
相关标签/搜索