本文将详细介绍算法复杂度算法
大O表示法是描述算法的性能和复杂程度。 分析算法时,时常遇到如下几类函数数组
符号 名称 O(1) 常数的 O(log(n)) 对数的 O((log(n))c) 对数多项式的 O(n) 线性的 O(n2) 二次的 O(nc) 多项式的 O(cn) 指数的
如何衡量算法的效率?一般是用资源,例如CPU(时间)占用、内存占用、硬盘占用和网络占用。当讨论大O表示法时,通常考虑的是CPU(时间)占用网络
下面用一些例子来理解大O表示法的规则数据结构
【O(1)】机器学习
function increment(num){ return ++num; }
假设运行increment(1)函数,执行时间等于X。若是再用不一样的参数(例如2)运行一次increment函数,执行时间依然是X。和参数无关,increment函数的性能都同样。所以,咱们说上述函数的复杂度是O(1)(常数)函数
【O(n)】性能
如今以顺序搜索算法为例:学习
function sequentialSearch(array, item){ for (var i=0; i<array.length; i++){ if (item === array[i]){ //{1} return i; } } return -1; }
若是将含10个元素的数组([1, ..., 10])传递给该函数,假如搜索1这个元素,那么,第一次判断时就能找到想要搜索的元素。在这里咱们假设每执行一次行{1} ,开销是1。spa
如今,假如要搜索元素11。行{1}会执行10次(遍历数组中全部的值,而且找不到要搜索的元素,于是结果返回 -1)。若是行{1}的开销是1,那么它执行10次的开销就是10,10倍于第一种假设code
如今,假如该数组有1000个元素([1, ..., 1000])。搜索1001的结果是行{1}执行了1000次(而后返回-1)
sequentialSearch函数执行的总开销取决于数组元素的个数(数组大小),并且也和搜索的值有关。若是是查找数组中存在的值,行{1}会执行几回呢?若是查找的是数组中不存在的值,那么行{1}就会执行和数组大小同样屡次,这就是一般所说的最坏状况
最坏状况下,若是数组大小是10,开销就是10;若是数组大小是1000,开销就是1000。能够得出sequentialSearch函数的时间复杂度是O(n),n是(输入)数组的大小
回到以前的例子,修改一下算法的实现,使之计算开销:
function sequentialSearch(array, item){ var cost = 0; for (var i=0; i<array.length; i++){ cost++; if (item === array[i]){ //{1} return i; } } console.log('cost for sequentialSearch with input size ' + array.length + ' is ' + cost); return -1; }
用不一样大小的输入数组执行以上算法,能够看到不一样的输出
【O(n2)】
用冒泡排序作O(n2)的例子:
function swap(array, index1, index2){ var aux = array[index1]; array[index1] = array[index2]; array[index2] = aux; } function bubbleSort(array){ var length = array.length; for (var i=0; i<length; i++){ //{1} for (var j=0; j<length-1; j++ ){ //{2} if (array[j] > array[j+1]){ swap(array, j, j+1); } } } }
假设行{1}和行{2}的开销分别是1。修改算法的实现使之计算开销:
function bubbleSort(array){ var length = array.length; var cost = 0; for (var i=0; i<length; i++){ //{1} cost++; for (var j=0; j<length-1; j++ ){ //{2} cost++; if (array[j] > array[j+1]){ swap(array, j, j+1); } } } console.log('cost for bubbleSort with input size ' + length + ' is ' + cost); }
若是用大小为10的数组执行bubbleSort,开销是100(102)。若是用大小为100的数组执 行bubbleSort,开销就是10 000(1002)。须要注意,咱们每次增长输入的大小,执行都会愈来愈久
时间复杂度O(n)的代码只有一层循环,而O(n2)的代码有双层嵌套循环。如 果算法有三层遍历数组的嵌套循环,它的时间复杂度极可能就是O(n3)
下图比较了前述各个大O符号表示的时间复杂度:
下表是经常使用数据结构的时间复杂度
下表是图的时间复杂度:
下表是排序算法的时间复杂度:
下表是搜索算法的时间复杂度:
通常来讲,若是一个算法的复杂度为O(nk),其中k是常数,咱们就认为这个算法是高效的,这就是多项式算法
对于给定的问题,若是存在多项式算法,则计为P(polynomial,多项式)
还有一类NP(nondeterministicpolynomial,非肯定性多项式)算法。若是一个问题能够在多项式时间内验证解是否正确,则计为NP。若是一个问题存在多项式算法,天然能够在多项式时间内验证其解。所以,全部的P都是NP。然而,P=NP是否成立,仍然不得而知。NP问题中最难的是NP彻底问题,它知足如下两个条件:(1)是NP问题,也就是说,能够在多项式时间内验证解,但尚未找到多项式算法;(2)全部的NP问题都能在多项式时间内归约为它。为了理解问题的归约,考虑两个决策问题L和M。假设算法A能够解决问题L,算法B能够验证输入y是否为M的解。目标是找到一个把L转化为M的方法,使得算法B能够用于构造算法A
还有一类问题,只需知足NP彻底问题的第二个条件,称为NP困难问题。所以,NP彻底问题也是NP困难问题的子集
下面是知足P < > NP时,P、NP、NP彻底和NP困难问题的欧拉图:
非NP彻底的NP困难问题的例子有停机问题和布尔可知足性问题(SAT)。 NP彻底问题的例子有子集和问题、旅行商问题、顶点覆盖问题等等
咱们提到的有些问题是不可解的。然而,仍然有办法在符合要求的时间内找到一个近似解。启发式算法就是其中之一。启发式算法获得的未必是最优解,但足够解决问题了。启发式算法的例子有局部搜索、遗传算法、启发式导航、机器学习等