如何理解算法复杂度

算法

算法的定义是这样的:解题方案的准确而完善的描述,是一系列解决问题的清晰指令,就是解决一个问题的完整性描述。javascript

如何衡量一个算法的好坏,能够经过空间复杂度和时间复杂度两个方面来进行衡量。java

  1. 空间复杂度 评估执行程序所需的存储空间。能够估算出程序对计算机内存的使用程度。算法

  2. 时间复杂度 评估执行程序所需的时间。能够估算出程序对处理器的使用程度。数组

设计算法时,时间复杂度要比空间复杂度更容易出问题,因此通常状况下咱们只对时间复杂度进行研究。bash

时间复杂度

时间频度

若是一个算法所花费的时间与算法中代码语句执行次数成正比,那么那个算法执行语句越多,它的花费时间也就越多。咱们把一个算法中的语句执行次数称为时间频度。一般用T(n)表示。n用来表示问题的规模。dom

通常状况下,算法中基本操做重复执行的次数是n的某个函数,用T(n)表示,f(n)用来描述T(n) 函数中增加最快的部分,。记做T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。函数

该表示方法被成为大O表示法。工具

大O表示法

时间复杂度经常使用大O符号——O(f(n))表述,不包括这个函数的低阶项和首项系数。oop

推导大O阶有一下三种规则:ui

  1. 用常数1取代运行时间中的全部加法常数
  2. 只保留最高阶项
  3. 去除最高阶的常数

大O阶的推导方法

大O表示法O(f(n))中的f(n)的值能够为一、n、logn、n^2 等,因此咱们将O(1)、O(n)、O(logn)、O( n^2 )分别称为常数阶、线性阶、对数阶和平方阶。下面咱们来看看推导大O阶的方法:

常数阶

例:段代码的大O是多少?

let sum = 0, n = 100;
复制代码

第一条就说明了全部加法常数给他个O(1)便可

线性阶

通常含有非嵌套循环涉及线性阶,线性阶就是随着问题规模n的扩大,对应计算次数呈直线增加。

let i , n = 100, sum = 0;
    for( i=0; i < n; i++ )
    {
        sum = sum + i;
    }
复制代码

上面这段代码,它的循环的时间复杂度为O(n),由于循环体中的代码须要执行n次。

平方阶

let i, j, n = 100;
    for( i=0; i < n; i++ )
    {
        for( j=0; j < n; j++ )
        {
            console.log('hi')
        }
    }
复制代码

n等于100,也就是说外层循环每执行一次,内层循环就执行100次,那总共程序想要从这两个循环出来,须要执行100*100次,也就是n的平方。因此这段代码的时间复杂度为O(n^2)。

总结:若是有三个这样的嵌套循环就是n^3。因此总结得出,循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。

对数阶

let i = 1, n = 100;
    while( i < n )
    {
        i = i * 2;
    }
复制代码

因为每次i*2以后,就距离n更近一步,假设有x个2相乘后大于或等于n,则会退出循环。 因而由2^x = n获得x = log(2)n,因此这个循环的时间复杂度为O(logn)

举例:冒泡排序

冒泡排序

假设冒泡排序存在三种状况,1.数组所有正序排列;2.数组所有反向排列;3.数组混乱排序;

第一种状况下,数组只需遍历一遍就能够完成排序,假设数组长度为n,须要遍历n-1次,此时:

T(n) = n-1 = T(O(n));
复制代码

第二种状况,数组所有反向排列,那么就须要一次次遍历数组直到顺序正确,第一次排序须要执行n-1步,第二次排序能够排除排序正确的数,只须要执行(n-1)-1)次,以此类推,直到最后执行1次完成排序,该过程次数为:

T(n)=n(n-1)/2 = T(O(n^2);
复制代码

第三种状况,数组混乱排序,可能不须要执行到最后便可完成排序,假设到第i次便可完成排序,该过程执行次数:

T(n)=n(n-i)/2 = T(O(n^2);
复制代码

综上,冒泡排序的平均时间复杂度为

O(n^2)
复制代码

规则解析

推导大O阶有一下三种规则:

  1. 用常数1取代运行时间中的全部加法常数
  2. 只保留最高阶项
  3. 去除最高阶的常数

当n增大到必定程度时,f(n)中最高阶的部分占据了主导地位,低阶变量和常数对结果对影响几乎能够忽略不计。

工具地址:函数生成器

低阶变量影响

低阶变量的影响

图中的两个函数分别为

y=3x^2
复制代码
y=3x^2+2x
复制代码

随着X的变大,两个函数曲线几乎重合,常数10000的影响微乎其微,可忽略不计。

常量的影响

图中的两个函数分别为

y=2x^2
复制代码
y=2x^2+10000
复制代码

随着X的变大,两个函数曲线几乎重合,常数10000的影响微乎其微,可忽略不计。

常数影响

同理,高阶变量前的常量也可忽略不计

高阶常量忽略不计

综合

综上,低阶变量和常量对最终结果影响不大

综合影响

常见时间复杂度的比较

复杂度比较

O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n³)<O(2ⁿ)<O(n!)

大O阶的应用

数组去重

方法1:

var arr = [1,2,2,4,3,4,1,3,2,7,5,6,1]
    var newArr = new Set(arr); //n
复制代码
T(n)=n=T(O(n));
复制代码

该算法时间复杂度为

O(n);
复制代码

方法2:

function fn(arr){
       let newArr = [] // 1
       arr.sort((a,b)=>{
           return a-b
       }) // O(n)=n^2
       
       arr.forEach((val,index)=>{ // n
           if(val != arr[index+1]){ // 1
                newArr.push(val) // 1
           }
       })
       return newArr //1
    }
复制代码
T(n) =T(O(n^2)) +n+2=T(O(n^2));
复制代码

该算法时间复杂度为

O(n^2);
复制代码

方法3:

for(var i=0;i<arr.length;i++){ //n 
        for(var j=i+1;j<arr.length;j++){ // n*n
             if(arr[i]==arr[j]){ // n*n
                  arr.splice(j,1) // n*n*n
             }
        }
    } 
复制代码
T(n)=n^3+2n^2+n=T(O(n^3));
复制代码

该算法时间复杂度为

O(n^3);
复制代码

算法对比

O(n)表示了算法的复杂程度,可是并不表明复杂程度越大的算法,消耗时间越长,具体须要根据n的值来判断。以上面的3种数组去重算法为例,分析n不一样的状况下,不一样算法的消耗时间。

for(var i = 0,arr =[];i<n;i++){
        arr[arr.length]=parseInt(Math.random()*n);
    }    
    function fn1(arr){
       let newArr = [] // 1
       arr.sort((a,b)=>{
           return a-b
       }) // O(n)=n^2
    
       arr.forEach((val,index)=>{ // n
           if(val != arr[index+1]){ // 1
                newArr.push(val) // 1
           }
       })
       return newArr //1
    }
    function fn2(arr){
        for(var i=0;i<arr.length;i++){ //n 
            for(var j=i+1;j<arr.length;j++){ // n*n
                 if(arr[i]==arr[j]){ // n*n
                      arr.splice(j,1) // n*n*n
                 }
            }
        }
    }
    var newArr = new Set(arr)
    console.time("newArr");
    new Set(arr);
    console.timeEnd("newArr");
    console.time("fn1");
    fn1(arr);
    console.timeEnd("fn1");
    console.time("fn2");
    fn2(arr);
    console.timeEnd("fn2");
复制代码

n=500

500结果1

500结果2

500结果3

n=1000

1000结果1

1000结果2

1000结果3

n=2000

2000结果1

2000结果2

2000结果3

n=5000

5000结果1

5000结果2

5000结果3

n=10000

10000结果1

10000结果2

10000结果3

综合以上的对比结果能够得知,当n<1000时,算法3的时间小于算法2的时间,尽管算法2的时间复杂度小于算法3。

当一个算法当T(n)能够获知时,能够经过对比T(n)来决定使用哪一种算法。

综合上文大O阶表示法的推导规则,当n的数值绝对大时,低阶表达式和常数的影响微乎其微,而上述的应用也验证了这一观点,当n超过1000时,O(n^3)的算法耗时大大超过O(n^2)的算法。

所以,当算法应用的状况较为复杂时,利用时间复杂度——O(n)来判断是行之有效的方法。时间复杂度能够是评价一个算法的相对条件,但不是绝对条件。

相关文章
相关标签/搜索