转自:https://www.jianshu.com/p/59d09b9cee58

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

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

所以究竟什么是算法?

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

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**的复杂度也是线性的。
你不过是使用已经抽象了的循环过程。

O(n²)— 二次方时间

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)—指数时间

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

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

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

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

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

作者:唐先僧
链接:https://www.jianshu.com/p/59d09b9cee58
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

白话算法:时间复杂度和大O表示法的更多相关文章

  1. 1. 时间复杂度(大O表示法)以及使用python实现栈

    1.时间复杂度(大O表示法): O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n! ...

  2. python数据结构与算法学习自修第二天【时间复杂度与大O表示法】

    #!/usr/bin/env python #! _*_ coding:UTF-8 _*_ from Queue import Queue import time que = Queue() time ...

  3. 时间复杂度和大O表示法

    大O表示法:称一个函数g(n)是O(f(n)),当且仅当存在常数c>0和n0>=1,对一切n>n0均有|g(n)|<=c|f(n)|成立,也称函数g(n)以f(n)为界或者称g ...

  4. 算法图解之大O表示法

    什么是大O表示法 大O表示法可以告诉我们算法的快慢. 大O比较的是操作数,它指出了算法运行时间的增速. O(n) 括号里的是操作数. 举例 画一个16个格子的网格,下面分别列举几种不同的画法,并用大O ...

  5. 常见算法的时间复杂度(大O计数法)

    定义 ​ 对于不同的机器环境而言,确切的单位时间是不同的,但是对于算法进行多少个基本操作(即花费多少时间单位)在规模数量级上却是相同的,由此可以忽略机器环境的影响而客观的反应算法的时间效率. 对于算法 ...

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

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  7. 算法时间复杂度、空间复杂度(大O表示法)

    什么是算法? 计算机是人的大脑的延伸,它的存在主要是为了帮助我们解决问题. 而算法在计算机领域中就是为了解决问题而指定的一系列简单的指令集合.不同的算法需要不同的资源,例如:执行时间或消耗内存. 如果 ...

  8. 算法的时间复杂度——"大O分析法"(转载)

    原文地址:https://my.oschina.net/gooke/blog/684026 一下为本人笔记:) 场景:在解决计算机科学领域的问题时,经常有好多个方法都可以,想找到最优的方法,就有了时间 ...

  9. 算法时间复杂度的表示法O(n²)、O(n)、O(1)、O(nlogn)等是什么意思?

    Java中  Set 和 List 集合  的contains()方法,检查数组链表中是否包含某元素检查数组链表中是否包含某元素,使用 Set 而不使用 List  的原因是效率问题, 前者的 set ...

随机推荐

  1. 客户端浏览器向服务器发起http请求的全过程

    http协议的参考:http://blog.csdn.net/hefeng6500/article/details/75081047 (1)浏览器先搜索自身的DNS缓存 (2)操作系统搜索自身的DNS ...

  2. 为Windows编译libobjc2(通过交叉编译的方式)

    前提:Linux系统.git.clang-8.g++-mingw-w64-x86-64.gobjc++-mingw-w64-x86-64. 一.下载源代码 git clone https://gith ...

  3. ContentLoadingProgressBar

    注意必须设置style: <android.support.v4.widget.ContentLoadingProgressBar android:id="@+id/progressB ...

  4. redhat7.2配置yum源

      版权声明:声明:本博客欢迎转发,但请保留原作者信息!本站文章皆基于公开来源信息,仅代表作者个人观点 https://blog.csdn.net/wylfengyujiancheng/article ...

  5. JVM系列2:HotSpot虚拟机对象

    1.对象创建过程: ①.类加载检查:当java虚拟机遇到一条new指令时,首先会去检查该指令的参数能否在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析.初始化过,如果没 ...

  6. T83312 【音乐会】达拉崩吧·上

    T83312 [音乐会]达拉崩吧·上 题解 线段树板子题 把原来的 + 变成 ^ 但是注意一下懒标记,这里讲一下小技巧 代码 #include<bits/stdc++.h> using n ...

  7. 七、创建UcRESTTemplate请求管理器

    一.创建UcRESTTemplate管理器封装 import com.alibaba.fastjson.JSON; import org.apache.http.client.config.Reque ...

  8. C#三种常用的读取XML文件的方法

    下面我将介绍三种常用的读取XML文件的方法.分别是 1: 使用 XmlDocument 2: 使用 XmlTextReader 3: 使用 Linq to Xml 这里我先创建一个XML文件,名为Bo ...

  9. flutter textfield

    释放光标 FocusScope.of(context).unfocus() 输入类型 keyboardType: TextInputType.number, 键盘右下角按键类型 textInputAc ...

  10. MyBatis框架原理3:缓存

    上一篇[MyBatis框架原理2:SqlSession运行过程][1]介绍了MyBatis的工作流程,其中涉及到了MyBatis缓存的使用,首先回顾一下工作流程图: 如果开启了二级缓存,数据查询执行过 ...