算法复杂度的表示法
by Michael Olorunnisola
通过Michael Olorunnisola
用简单的英语算法:时间复杂度和Big-O表示法 (Algorithms in plain English: time complexity and Big-O notation)
Every good developer has time on their mind. They want to give their users more of it, so they can do all those things they enjoy. They do this by minimizing time complexity.
每个优秀的开发人员都有自己的时间。 他们想给用户更多的东西,以便他们可以做自己喜欢的所有事情。 他们通过最小化时间复杂度来做到这一点。
Before you can understand time complexity in programming, you have to understand where it’s most commonly applied: in the design of algorithms.
在您了解编程中的时间复杂性之前,您必须了解它最常应用的地方:在算法设计中。
那么,什么是算法? (So what’s an algorithm, anyway?)
Simply put, an algorithm is a series of contained steps, which you follow in order to achieve some goal, or to produce some output. Let’s take for example your grandma’s recipe for baking a cake. Wait, does that count as an algorithm? Sure it does!
简而言之,算法是一系列包含的步骤,为了实现某些目标或产生某些输出,必须遵循这些步骤。 让我们以您奶奶的烤蛋糕食谱为例。 等等,算作算法吗? 当然可以!
function BakeCake(flavor, icing){
"1. Heat Oven to 350 F2. Mix flour, baking powder, salt3. Beat butter and sugar until fluffy4. Add eggs.5. Mix in flour, baking powder, salt6. Add milk and " + flavor + "7. Mix further8. Put in pan9. Bake for 30 minutes
10." + if(icing === true) return 'add icing' + "
10. Stuff your face
"
}BakeCake('vanilla', true) => deliciousness
Algorithms are useful in our examination of time complexity because they come in all shapes and sizes.
由于算法具有各种形状和大小,因此在我们检查时间复杂度方面很有用。
In the same way you can slice a pie a 100 different ways, you can solve a single problem with many different algorithms. Some solutions are just more efficient, taking less time and requiring less space than others.
以相同的方式,您可以用100种不同的方式对一个饼进行切片,可以使用许多种不同的算法来解决一个问题。 与其他解决方案相比,某些解决方案效率更高,所需时间更少,占用的空间更少。
So the main question is: how do we go about analyzing which solutions are most efficient?
因此,主要问题是:我们如何分析哪些解决方案最有效?
Math to the rescue! Time complexity analysis in programming is just an extremely simplified mathematical way of analyzing how long an algorithm with a given number of inputs (n) will take to complete it’s task. It’s usually defined using Big-O notation.
求救数学! 编程中的时间复杂度分析只是一种极其简化的数学方法,用于分析具有给定数量输入(n)的算法完成任务所需的时间。 通常使用Big-O表示法定义。
您问什么是大O符号? (What’s Big O notation, you ask?)
If you promise you won’t give up and stop reading, I will tell you.
如果您保证不会放弃并停止阅读,我会告诉您。
Big-O notation is a way of converting the overall steps of an algorithm into algebraic terms, then excluding lower order constants and coefficients that don’t have that big an impact on the overall complexity of the problem.
Big-O表示法是一种将算法的整体步骤转换为代数项,然后排除对问题的整体复杂性影响不大的低阶常量和系数的方法。
Mathematicians will probably cringe a bit at my “overall impact” assumption there, but for developers to save time, it’s easier to simplify things this way:
数学家可能会对我的“总体影响”假设有些畏缩,但是对于开发人员来说,为了节省时间,用这种方式简化事情会更容易:
Regular Big-O2 O(1) --> It's just a constant number2n + 10 O(n) --> n has the largest effect5n^2 O(n^2) --> n^2 has the largest effect
In short, all this example is saying is: we only look at the factor in our expression that has the potential greatest impact on the value that our expression will return. (This changes as the constant gets extremely large and n gets small, but let’s not worry about that for now).
简而言之,此示例说明的是:我们仅查看表达式中对表达式返回的值具有最大潜在影响的因素。 (随着常数变得非常大而n变得很小,这种情况会发生变化,但是现在我们不必担心这一点)。
Below are some common time complexities with simple definitions. Feel free to check out Wikipedia, though, for more in-depth definitions.
以下是一些具有简单定义的常见时间复杂性。 请随意查看Wikipedia ,以获得更深入的定义。
- O(1) — Constant Time: Given an input of size n, it only takes a single step for the algorithm to accomplish the task. O(1)-恒定时间:给定大小为n的输入,算法只需一步即可完成任务。
- O(log n) — Logarithmic time: given an input of size n, the number of steps it takes to accomplish the task are decreased by some factor with each step. O(log n)-对数时间:给定大小为n的输入,完成任务所需的步骤数会因每个步骤而减少一些。
- O(n) — Linear Time: Given an input of size n, the number of of steps required is directly related (1 to 1) O(n)—线性时间:给定大小为n的输入,所需的步数直接相关(1到1)
- O(n²) — Quadratic Time: Given an input of size n, the number of steps it takes to accomplish a task is square of n. O(n²)—二次时间:给定大小为n的输入,完成一项任务所需的步骤数为n的平方。
- O(C^n) — Exponential Time: Given an input of size n, the number of steps it takes to accomplish a task is a constant to the n power (pretty large number). O(C ^ n)—指数时间:在输入大小为n的情况下,完成一项任务所需的步骤数是n次幂的常数(相当大的数字)。
With this knowledge in hand, lets see the number of steps that each of these time complexities entails:
掌握了这些知识之后,让我们看一下这些时间复杂度所需要的步骤数:
let n = 16;O (1) = 1 step "(awesome!)"O (log n) = 4 steps "(awesome!)" -- assumed base 2O (n) = 16 steps "(pretty good!)"O(n^2) = 256 steps "(uhh..we can work with this?)"O(2^n) = 65,536 steps "(...)"
As you can see, things can easily become orders of magnitude more complex depending on the complexity of your algorithm. Luckily, computers are powerful enough to still handle really large complexities relatively quickly.
如您所见,根据算法的复杂性,事情很容易变得复杂几个数量级。 幸运的是,计算机功能强大,仍然可以相对快速地处理非常大的复杂性。
So how do we go about analyzing our code with Big-O notation?
那么,我们如何使用Big-O表示法分析代码?
Well here are some quick and simple examples of how you can apply this knowledge to algorithms you might encounter in the wild or code up yourself.
好了,这里有一些快速简单的示例,说明如何将这些知识应用于野外可能遇到的算法或自己编写代码。
We’ll use the data structures below for our examples:
我们将使用以下数据结构作为示例:
var friends = {'Mark' : true,'Amy' : true,'Carl' : false,'Ray' : true,
'Laura' : false,
}
var sortedAges = [22, 24, 27, 29, 31]
O(1)-恒定时间 (O(1) — Constant Time)
Value look ups when you know the key (objects) or the index (arrays) always take one step, and are thus constant time.
当您知道键(对象)或索引(数组)总是迈出一步,并且时间恒定时,就会进行值查找。
//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 stepfunction add(num1,num2){ // I have two numbers, takes one step to return the valuereturn num1 + num2
}
O(log n)-对数时间 (O(log n) — Logarithmic Time)
If you know which side of the array to look on for an item, you save time by cutting out the other half.
如果您知道要在阵列的哪一侧查找某项,则可以省去另一半来节省时间。
//You decrease the amount of work you have to do with each stepfunction 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 arrayif( 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)—线性时间 (O(n) — Linear Time)
You have to look at every item in the array or list to accomplish the task. Single for loops are almost always linear time. Also array methods like indexOf are also linear time. You’re just abstracted away from the looping process.
您必须查看数组或列表中的每个项目才能完成任务。 单次for循环几乎总是线性时间。 像indexOf这样的数组方法也是线性时间。 您只是从循环过程中抽象出来。
//The number of steps you take is directly correlated to the your input sizefunction addAges(array){var sum = 0;for (let i=0 ; i < array.length; i++){ //has to go through each valuesum += array[i]}return sum;
}addAges(sortedAges) //133
O(n²)—二次时间 (O(n²) — Quadratic Time)
Nested for loops are quadratic time, because you’re running a linear operation within another linear operation (or n*n = n²).
嵌套的for循环是二次时间,因为您正在另一个线性运算(或n * n =n²)内运行线性运算。
//The number of steps you take is your input size squaredfunction addedAges(array){var addedAge = [];for (let i=0 ; i < array.length; i++){ //has to go through each valuefor(let j=i+1 ; j < array.length ; j++){ //and go through them againaddedAge.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)—指数时间 (O(2^n) — Exponential Time)
Exponential time is usually for situations where you don’t know that much, and you have to try every possible combination or permutation.
指数时间通常用于您不太了解的情况,并且您必须尝试所有可能的组合或排列。
//The number of steps it takes to accomplish a task is a constant to the n power//Thought example//Trying to find every combination of letters for a password of length n
You should do time complexity analysis anytime you write code that has to run fast.
每当您编写必须快速运行的代码时,都应该进行时间复杂度分析。
When you have various routes to solve a problem, it is definitely wiser to create a solution that just works first. But in the long run, you’ll want a solution that runs as quickly and efficiently as possible.
当您有各种解决问题的途径时,明智的做法是首先创建一个解决方案。 但是从长远来看,您将需要一个尽可能快速高效地运行的解决方案。
To help you with the problem solving process, here are some simple questions to ask:
为了帮助您解决问题,以下是一些简单的问题:
1. Does this solve the problem? Yes =>
1.这样可以解决问题吗? 是 =>
1. Does this solve the problem? Yes =>
1.这样可以解决问题吗? 是 =>
2. Do you have time to work on this
2.您有时间从事此工作吗
2. Do you have time to work on this
2.您有时间从事此工作吗
2. Do you have time to work on thisYes => go to step 3
2.您是否有时间进行此工作, 是 =>转到步骤3
2. Do you have time to work on thisYes => go to step 3
2.您是否有时间进行此工作, 是 =>转到步骤3
2. Do you have time to work on thisYes => go to step 3No => Come back to it later and go to step 6 for now.
2.您是否有时间进行此工作是 =>转到步骤3 否 =>稍后再回到步骤6,现在转到步骤6。
2. Do you have time to work on thisYes => go to step 3No => Come back to it later and go to step 6 for now.
2.您是否有时间进行此工作是 =>转到步骤3 否 =>稍后再回到步骤6,现在转到步骤6。
3. Does it cover all edge cases? Yes =>
3.它涵盖所有边缘情况吗? 是 =>
3. Does it cover all edge cases? Yes =>
3.它涵盖所有边缘情况吗? 是 =>
4. Are my complexities as low as possible ?
4.我的复杂程度是否尽可能低?
4. Are my complexities as low as possible ?
4.我的复杂程度是否尽可能低?
4. Are my complexities as low as possible ?No => rewrite or modify into a new solution –>go back to step 1
4.我的复杂程度是否尽可能低? 否 =>重写或修改为新解决方案–>返回步骤1
4. Are my complexities as low as possible ?No => rewrite or modify into a new solution –>go back to step 1
4.我的复杂程度是否尽可能低? 否 =>重写或修改为新解决方案–>返回步骤1
4. Are my complexities as low as possible ?No => rewrite or modify into a new solution –>go back to step 1Yes => go to step 5
4.我的复杂程度是否尽可能低? 否 =>重写或修改为新解决方案–>返回步骤1 是 =>转到步骤5
4. Are my complexities as low as possible ?No => rewrite or modify into a new solution –>go back to step 1Yes => go to step 5
4.我的复杂程度是否尽可能低? 否 =>重写或修改为新解决方案–>返回步骤1 是 =>转到步骤5
5. Is my code D.R.Y ? Yes =>
5.我的代码是DRY吗? 是 =>
5. Is my code D.R.Y ? Yes =>
5.我的代码是DRY吗? 是 =>
6. Rejoice!
6.欢喜!
6. Rejoice!
6.欢喜!
6. Rejoice!No => Make it D.R.Y, then rejoice!
6.欢喜! 否 =>使其干燥,然后高兴!
Analyze time complexity any and all times you are trying to solve a problem. It’ll make you a better developer in the log run. Your teammates and users will love you for it.
您尝试解决问题的所有时间都要分析时间复杂度。 它可以使您成为日志运行中更好的开发人员。 您的队友和用户将为此而爱您。
Again, most problems you will face as programmer — whether algorithmic or programmatic — will have tens if not hundreds of ways of solving it. They may vary in how they solve the problem, but they all still solve that problem.
同样,您将以编程人员的身份遇到的大多数问题,无论是算法上还是程序上的问题,都有数十种甚至数百种解决方法。 他们解决问题的方式可能有所不同,但是他们仍然都能解决问题。
You could be making decisions between whether to use sets or graphs to store data. You could be deciding whether or not to use Angular, React, or Backbone for a team project. All of these solutions solve the same problem in a different way.
您可能在决定使用集还是使用图来存储数据之间做出决定。 您可能正在决定是否为团队项目使用Angular,React或Backbone。 所有这些解决方案都以不同的方式解决了相同的问题。
Given this, it’s hard to say there is a single “right” or “best” answer to these problems. But it is possible to say there are “better” or “worse” answers to a given problem.
鉴于此,很难说对这些问题有一个“正确”或“最佳”答案。 但是可以说,对于给定的问题有“更好”或“更差”的答案。
Using one of our previous examples, it might be better to use React for a team project if half your team has experience with it, so it’ll take less time to get up and running.
使用我们之前的示例之一,如果您的团队有一半的经验,将React用于团队项目可能会更好,因此启动和运行将花费更少的时间。
The ability to describe a better solution usually springs from some semblance of time complexity analysis.
描述更好的解决方案的能力通常源于某种时间复杂度分析。
In short, if you’re going to solve a problem, solve it well. And use some Big-O to help you figure out how.
简而言之,如果您要解决问题,请很好地解决。 并使用一些Big-O来帮助您找出方法。
Here’s a final recap:
这是最后的总结:
O(1) — Constant Time: it only takes a single step for the algorithm to accomplish the task.
O(1)—恒定时间:算法仅需一步即可完成任务。
O(log n) — Logarithmic Time: The number of steps it takes to accomplish a task are decreased by some factor with each step.
O(log n)-对数时间:完成一项任务所需要执行的步骤数,每一步都会减少一些。
O(n) — Linear Time: The number of of steps required are directly related (1 to 1).
O(n)—线性时间:所需步骤数直接相关(1到1)。
O(n²) — Quadratic Time: The number of steps it takes to accomplish a task is square of n.
O(n²)—二次时间:完成一项任务所需的步骤数为n的平方。
O(C^n) — Exponential: The number of steps it takes to accomplish a task is a constant to the n power (pretty large number).
O(C ^ n)—指数:完成一项任务所需的步骤数是n次幂的常数(非常大的数字)。
And here are some helpful resources to learn more:
以下是一些有用的资源,以了解更多信息:
Wikipedia
维基百科
The Big O Cheat Sheet is a great resource with common algorithmic time complexities and a graphical representation. Check it out!
Big O备忘单是一个很好的资源,具有常见的算法时间复杂度和图形表示形式。 看看这个!
翻译自: https://www.freecodecamp.org/news/time-is-complex-but-priceless-f0abd015063c/
算法复杂度的表示法