白话算法:时间复杂度和大O表示法

每一个优秀的开发者脑中都有时间概念。他们想给用户更多的时间让用户做他们想做的事情。他们通过最小化时间复杂度来实现这一目的。

在你能理解程序的时间复杂度之前,你需要了解最常使用它的地方:算法设计。

所以究竟什么是算法?

简单来说,算法就是一系列被控制的步骤,你通过按序执行这些步骤可以实现一些目标或者产生一些输出。让我们以你祖母烘烤蛋糕的菜单为例子。等等,这也可以算作算法?当然算!

function 烘烤蛋糕(风味, 加冰){

"
 1. 烤箱预热到350度(华氏)
 2. 混合面粉、酵母粉、盐
 3. 打发黄油和糖直到蓬松

 4. 添加鸡蛋
 5. 将黄油和鸡蛋混到面粉中
 6. 添加牛奶和 " + 风味 + "
 7. 继续搅拌
 8. 放入盘中
 9. 烘烤30分钟
10." 如果要 加冰 ,加点冰 "
10. 大快朵颐
"
}
BakeCake('香草味', 要加冰) => 美味

算法在检查时间复杂度时非常有用,因为它可以以各种形态和大小出现。

就犹如你可以用100中不同的方式来切开一个派一样,你可以用多种不同的算法来解决同一个问题。有的解决方法效率更高,比其他的方法需要更少的时间和更少的空间。

所以问题就是:我们怎么分析确认哪一个算法效率最高?

数学!程序的时间复杂度分析仅是一个极其简单的数学方法,这种方法就是分析一个算法对于给定数量的输入需要多长时间来完成任务。这通常定义为大O表示法

你会问,什么是大 O 表示法?

如果你答应不会放弃并且继续阅读,我会告诉你。

大O表示法就是将算法的所有步骤转换为代数项,然后排除不会对问题的整体复杂度产生较大影响的较低阶常数和系数。

数学家可能会对我的“整体影响”假设有点沮丧,但是开发人员为了节省时间,这种方式更容易简化问题:

规律       Big-O

2             O(1)   --> 就是一个常数

2n + 10       O(n)   --> n 对整体结果会产生最大影响

5n^2         O(n^2) --> n^2 具有最大影响

简而言之,这个例子所要表达的就是:我们只关注表达式中对表达式最终结果会产生最大影响的因子。(当常数非常大而n很小的时候并不是这样的,但是我们现在不要担心这种情况)。

下面是一些常用的时间复杂度以及简单的定义。更完整的定义可以翻阅维基百科

  • **O(1)— **常量时间:给定一个大小为n的输入,概算法只需要一步就可以完成任务。

  • **O(log n)— **对数时间:给定大小为n的输入,该算法每执行一步,它完成任务所需要的步骤数目会以一定的因子减少。

  • **O(n)— **线性时间:给定大小为n的输入,该算法完成任务所需要的步骤直接和n相关(1对1的关系)。

  • O(n²)—二次方时间:给定大小为n的输入,完成任务所需要的步骤是n的平方。

  • **O(C^n)— **指数时间:给定大小为n的输入,完成任务所需要的步骤是一个常数的n次方(非常大的数字)。

有了这些概念,我们一起来看看每一个复杂度完成任务所需要的步骤数:

let n = 16;

O (1) = 1 step "(awesome!)"

O (log n) = 4 steps "(awesome!)" -- assumed base 2

O (n) = 16 steps "(pretty good!)"
O(n^2) = 256 steps "(uhh..we can work with this?)"
O(2^n) = 65,536 steps "(...)"

如你所见,随着算法复杂度的提高,事情的复杂度也会以数量级增长。幸运的是,计算机足够强悍能够相对快速的处理非常复杂的问题。

所以我们怎样使用大O表示法来分析我们的代码?

这里有一些简便的例子向你展示怎么将这些知识运用到已有的或者你自己写的代码。

在我们的例子中将使用如下的数据结构:

var friends = {

 'Mark' : true,
 'Amy' : true,
 'Carl' : false,
 'Ray' : true,
'Laura' : false,
}
var sortedAges = [22, 24, 27, 29, 31]

O(1)— 常量时间

当你知道key(objects)或者index(arrays)时进行查找只需要一步就可以完成,所用时间就是一个常量。

//If I know the persons name, I only have to take one step to check:

function isFriend(name){ //similar to knowing the index in an Array 
 return friends[name]; 
};
isFriend('Mark') // returns True and only took one step
function add(num1,num2){ // I have two numbers, takes one step to return the value
 return num1 + num2
}

O (log n)— 对数时间

如果你知道在一个数组的哪一侧进行查找一个指定值时,你可以排除掉另外一半进而节约时间。

//You decrease the amount of work you have to do with each step

function thisOld(num, array){
 var midPoint = Math.floor( array.length /2 );
 if( array[midPoint] === num) return true;
 if( array[midPoint] < num ) --> only look at second half of the array
 if( array[midpoint] > num ) --> only look at first half of the array
 //recursively repeat until you arrive at your solution
 
}
thisOld(29, sortedAges) // returns true 
//Notes
 //There are a bunch of other checks that should go into this example for it to be truly functional, but not necessary for this explanation.
 //This solution works because our Array is sorted
 //Recursive solutions are often logarithmic
 //We'll get into recursion in another post!
​```

###O(n)— 线性时间
你必须通过遍历整个数组(或者list)来完成这一任务。单一**for循环**的时间复杂度通常都是线性的。此外数组方法比如**indexOf**的复杂度也是线性的。你不过是使用已经抽象了的循环过程。

//The number of steps you take is directly correlated to the your input size

function addAges(array){
var sum = 0;
for (let i=0 ; i < array.length; i++){ //has to go through each value
sum += array[i]
}
return sum;
}
addAges(sortedAges) //133
​```

O()— 二次方时间

for循环嵌套的复杂度就是二次方的,因为你在一个线性操作里执行另外一个线性操作(或者说: n*n =n² )

//The number of steps you take is your input size squared

function addedAges(array){
 var addedAge = [];
   for (let i=0 ; i < array.length; i++){ //has to go through each value
     for(let j=i+1 ; j < array.length ; j++){ //and go through them again
       addedAge.push(array[i] + array[j]);
     }
   }
 return addedAge;
}
addedAges(sortedAges); //[ 46, 49, 51, 53, 51, 53, 55, 56, 58, 60 ]
//Notes
 //Nested for loops. If one for loop is linear time (n)
 //Then two nested for loops are (n * n) or (n^2) Quadratic!

O(2^n)—指数时间

指数复杂度通常出现在这种情况:你对数据了解甚少,但是你必须尝试其所有的排列或者组合。

//The number of steps it takes to accomplish a task is a constant to the n power

//实例

 //尝试找出长度为n的密码中所以字符的组合

每当你在编写需要快速运行的代码时都应该做代码时间复杂度分析。

当你有不同的方法来解决一个问题时,比较明智的做法是先实现一个能够工作的解决方案。但是从长远来看,你需要一个尽可能快速且高效的解决方案。

为了帮助你完成解决问题的过程,可以问这些简单的问题:

1、这个可以解决问题吗? =>

2、你有时间考虑时间复杂度吗
=>进入第三步
=>稍后再回来,现在就跳转到第六步

3、它覆盖了所有的边界条件? =>

4、我的复杂度已经尽可能低?
=>重写或者修改解决方案 -> 回到步骤1
=>进入步骤5

5、我的代码D.R.Y(Don't Repeat Yourself) =>

6、欢呼吧!
=>将代码重构到D.R.Y,然后再欢呼!

在任何(和所有)尝试解决问题的时候都应该进行时间复杂度分析。长此以往将你成为优秀的开发者。你的同事和用户都会因此而喜欢你。

再次说明,作为一个程序员你所面临的绝大多数问题-不管是算法问题还是编程问题-没有几百个也有几十个中解决办法。他们在怎么解决问题的方法上会不一样,但是它们都能解决那个问题。

你可以在使用集合还是图来存储数据之间做出选择。你也可以决定是否在团队项目中使用Angular、React或者Backbone。所有这些解决方案以不同的方式解决同一个问题。

鉴于这一点,很难说某一个答案对于这些问题是“正确的”或者“最好的”。但是可以说对于给定的问题存在“更好”或者“更糟糕”的答案。

就我们前面的一个例子,如果你的团队中一半的人都有使用React的经验,那么使用React是一个更佳的答案,这个方案可以花更少的时间就能搭建起来并运行起来。

描述一个更好的解决方案的能力通常源于一些类似的时间复杂度分析。
简而言之,如果你尝试解决一个问题,那就解决的完美一些。并且使用一些大O表示法来帮助你指出怎么做。

最终总结:

  • **O(1)— **常数时间:该算法仅仅使用一步就可以完成任务

  • **O(log n)— **对数时间:该算法每执行一步,它完成任务所需要的步骤数目会以一定的因子减少。

  • **O(n)— **线性时间:该算法完成任务所需要的步骤直接和n相关(1对1的关系)。

  • **O(n²)— **二次方时间:完成任务所需要的步骤是n的平方。

  • **O(C^n)— **指数时间:完成任务所需要的步骤是一个常数的n次方(非常大的数字)。

这里还有一些有用的资源,可以了解更多信息:

  • 维基百科

  • 大O小抄是一个非常棒的资源,它提供了常用的算法时间复杂度,并以图表的形式呈现。

本文译自 Algorithms in plain English: time complexity and Big-O notation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 通常,对于一个给定的算法,我们要做 两项分析。第一是从数学上证明算法的正确性,这一步主要用到形式化证明的方法及相关...
    西域小码阅读 1,840评论 0 11
  • 算法复杂度 时间复杂度 空间复杂度 什么是时间复杂度 算法执行时间需通过依据该算法编制的程序在计算机上运行时所消耗...
    KODIE阅读 3,238评论 0 9
  • 算法是计算机处理信息的本质,因为计算机程序本质上是一个算法来告诉计算机确切的步骤来执行一个指定的任务。算法是独立存...
    LittlePy阅读 1,412评论 0 0
  • 算法的时间复杂度和空间复杂度-总结通常,对于一个给定的算法,我们要做 两项分析。第一是从数学上证明算法的正确性,这...
    Explorer_Mi阅读 1,444评论 0 1
  • 在没有互动和交流的环境里,人就像是一个物品,感受不到生命的存在,同时很多负面的感情就没有地方可以甩掉只能自己默默接...
    萧楠身阅读 303评论 0 0