大o表示法描述复杂度
This is the first post in my series Data Structures & Algorithms. As a boot camp grad, I found that once I started my professional career in software development, there was a gap in my fundamentals knowledge. Although I am not reversing a binary tree day-in-and-day-out, I do think it is important to learn these fundamentals simply because you will be a better developer by knowing they exist. This week I start things off by discussing Time and Space Complexity, and how you can use Big O notation to determine these metrics.
这是我的系列数据结构和算法中的第一篇文章。 作为一个新兵训练营的毕业生,我发现一旦我开始了软件开发的职业生涯,我的基础知识就会出现空白。 尽管我没有日复一日地逆转二叉树,但我确实认为学习这些基础知识很重要,因为您将因为知道它们的存在而成为更好的开发人员。 本周,我将从讨论时间和空间复杂度以及如何使用大O表示法确定这些指标开始。
时间复杂度 (Time Complexity)
A measurement of computing time that an algorithm takes to complete
一种算法完成计算时间的度量
What causes time complexity?
是什么造成时间复杂性?
Operations (
+
,-
,*
,/
)操作(
+
,-
,*
,/
)Comparisons (
>
,<
,==
)比较(
>
,<
,==
)Looping (
for
,while
)循环播放(
for
,while
)Outside function calls (
function()
)外部函数调用(
function()
)
大O符号 (Big O Notation)
The language and metric we use for talking about how long it takes for an algorithm to run
我们用于讨论算法运行需要多长时间的语言和指标
O(1)恒定时间 (O(1) Constant Time)
Not bound by the size of an input, only one operation is performed
不受输入大小的限制,仅执行一项操作
- Direct query of data you are looking for 直接查询您要查找的数据
- No iterating (loops) are involved 不涉及迭代(循环)
If you know the precise location of data you want to pull out of an Object {}
or Array []
, you can query for that item without having to iterate or perform any additional computation.
如果您知道要从Object {}
或Array []
提取数据的精确位置,则可以查询该项目,而不必进行迭代或执行任何其他计算。
Most of the time, if you’re using Constant Time, you are in good shape from a performance standpoint.
在大多数情况下,如果您使用的是Constant Time ,那么从性能的角度来看,您的状态会很好。
Let me show you an example in which I perform tasks that evaluate to Constant Time:
让我向您展示一个示例,在该示例中,我执行评估为“ 恒定时间”的任务:
First, I use the const
keyword to declare a new variable with the identifier jedi
and give this variable a collection of string
values
首先,我使用const
关键字声明一个标识符为jedi
的新变量,并为该变量提供string
值的集合
Next, I use the function
keyword to create a new function and give it the identifier findAJedi
. This function will have a single parameter with an identifier of jediList
接下来,我使用function
关键字创建一个新函数,并为其赋予标识符findAJedi
。 该函数将具有一个标识符为jediList
的单个参数
Using bracket notation []
I pull out the entry that is in index position 1
使用括号符号[]
拔出索引位置1
的条目
Since we already know where the data we want is, and we do not have to loop to get there, this operation is O(1)
or Constant Time
因为我们已经知道我们想要的数据在哪里,并且我们不必循环到达那里,所以此操作为O(1)
或恒定时间
We call the findAJedi
function with the variable jediList
as the single argument and our findAJedi
function prints anakin
. He is the chosen one, right?
我们称findAJedi
与可变功能jediList
作为单独的参数和我们findAJedi
功能打印anakin
。 他是被选中的人,对吗?
O(n)线性时间 (O(n) Linear Time)
Bound by the input, time increases linearly as input increases
受输入的约束,时间随着输入的增加而线性增加
Involves iteration to find a value (
for
orwhile
loops)涉及迭代以查找值(
for
或while
循环)
Let me show you an example of an operation that evaluates to O(n)
or Linear Time:
让我向您展示一个计算结果为O(n)
或线性时间的示例:
First, we use the const
keyword to create a new variable with the identifier jedi
that is assigned the value of an Array
. We use the fill()
method to populate this Array
with five luke
values that are of type string
首先,我们使用const
关键字创建一个标识符为jedi
的新变量,该变量被分配了Array
的值。 我们使用fill()
方法用五个类型为string
luke
值填充此Array
。
Next, we use the function
keyword to create a new function with an identifier findLuke
. This function will have a single parameter with an identifier of jediList
接下来,我们使用function
关键字创建一个带有findLuke
标识符的新函数。 该函数将具有一个标识符为jediList
的单个参数
function findLuke(jediList) {
Inside of our findLuke
function use the for
keyword to create a for
loop. We iterate through our jediList
and use bracket notation []
to compare each entry to luke
, when we find a match we console.log
it
在我们的findLuke
函数内部,使用for
关键字创建一个for
循环。 我们遍历jediList
并使用方括号[]
将每个条目与luke
进行比较,找到匹配项后,我们进行console.log
for (let i = 0; i < jediList.length; i++) {
if (jediList[i] === "luke") {
console.log("found luke")
}
}
Since we are iterating through the entire Array
, our Big O would be O(n)
. Right now our jediList
only has five entries, but what if we had 10,000, or 1,000,000,000? These are good considerations to think about as you write code.
由于我们要遍历整个Array
,因此我们的Big O将为O(n)
。 现在,我们的jediList
只有五个条目,但是如果我们有10,000或1,000,000,000,该怎么办? 这些是编写代码时要考虑的好考虑因素。
We call our findLuke
function that takes a single argument jedi
and since all of our entries are luke
, we console.log
luke
five times
我们调用带单个参数jedi
findLuke
函数,由于所有条目都是luke
,所以我们console.log
luke
五次
findLuke(jedi)
// found luke
// found luke
// found luke
// found luke
// found luke
O(n²)二次时间 (O(n²) Quadratic Time)
Often thought of as “worst case”, multiple nested iterations occur
通常被认为是“最坏的情况”,发生了多次嵌套迭代
- Involves two nested loops 涉及两个嵌套循环
- Each item in two collections need to be compared to each other 两个集合中的每个项目都需要相互比较
I am sure that you have been here before, I know I sure have. Nesting loops is never a good idea and there is a good reason for that. Speaking in terms of Big O, when you are iterating over a collection, and then iterating again inside of that first iteration that will produce a Big O of O(n²)
我敢肯定你以前来过这里,我知道我确实曾经来过。 嵌套循环从来都不是一个好主意,这是有充分理由的。 说到大O,当您遍历一个集合,然后在第一次迭代内部再次迭代时,将产生O(n²)
的大O。
Let me show you an example of a function that produces a Big O of O(n²)
:
让我向您展示产生O(n²)
的Big O的函数示例:
const jedi = ["mace windu", "yoda", "obi wan"];
function logJediDuos(jediList) {
for (let i = 0; i < jediList.length; i++) {
for (let j = 0; j < jediList.length; j++) {
console.log(jediList[i], jediList[j]);
}
}
}
logJediDuos(jedi);
First, we use the const
keyword to create a new variable with the identifier jedi
that is assigned to an Array
of three string
values
首先,我们使用const
关键字创建一个标识符为jedi
的新变量,该变量分配给三个string
值的Array
const jedi = ["mace windu", "yoda", "obi wan"]
Next, we use the function
keyword to create a new function with an identifier of logJediDuos
. This function has a single parameter jediList
接下来,我们使用function
关键字创建一个具有logJediDuos
标识符的新函数。 该函数具有单个参数jediList
function logJediDuos(jediList) {
Inside of logJediDuos
we use the for
keyword to create our first for
loop. In our for statement
we declare that we want to iterate through the length of jediList
until that length is greater than the value of i
. We increase the value of i
after each iteration
在logJediDuos
内部,我们使用for
关键字创建第一个for
循环。 在for statement
我们声明我们要迭代jediList
的长度,直到该长度大于i
的值。 每次迭代后我们增加i
的值
for (let i = 0; i < jediList.length; i++) {
Inside of the previous for
loop, we create another for
loop. Inside of our for
statement we make sure to give our index variable an identifier of j
to ensure we do not mutate the state of our i
variable.
在前面的for
循环内部,我们创建了另一个for
循环。 在for
语句中,我们确保为索引变量赋予j
的标识符,以确保我们不会使i
变量的状态发生变化。
Using bracket notation []
we use our index variables i
and j
to console.log
each pair inside of our jediList
使用括号符号[]
我们使用索引变量i
和j
来console.log
在jediList
内部的每一对
for (let i = 0; i < jediList.length; i++) {
for (let j = 0; j < jediList.length; j++) {
console.log(jediList[i], jediList[j])
}
}
When we invoke our logJediDuos
function we get this result:
当我们调用我们的logJediDuos
函数时,我们得到以下结果:
logJediDuos(jedi)
// mace windu mace windu
// i = 0, j = 0
// mace windu yoda
// i = 0, j = 1
// mace windu obi wan
// i = 0, j = 2
// yoda mace windu
// i = 1, j = 0
// yoda yoda
// i = 1, j = 1
// yoda obi wan
// i = 1, j = 2
// obi wan mace windu
// i = 2, j = 0
// obi wan yoda
// i = 2, j = 1
// obi wan obi wan
// i = 2, j = 2
I am only covering a handful of common Big O times in this post. If you want to learn more about advanced Big O times you can do so by following the links provided below:
在这篇文章中,我仅介绍几个常见的大O时代。 如果您想了解有关高级大O时间的更多信息,可以通过下面的链接进行操作:
O(n!)阶乘时间 (O(n!) Factorial Time)
Adds a nested loop for every loop
为每个循环添加一个嵌套循环
Read more here
在这里阅读更多
O(log N)对数 (O(log N) Logarithmic)
Involves searching algorithms if sorted
如果排序,则涉及搜索算法
Read more here
在这里阅读更多
O(2 ^ N)指数 (O(2^N) Exponential)
Recursive algorithms that solve a problem of size N
解决大小为N的问题的递归算法
Read more here
在这里阅读更多
简化大O (Simplifying Big O)
- Always assume worst-case scenario 始终假设最坏的情况
- Remove constants 删除常量
- Different terms for inputs 输入的不同术语
- Drop non-dominants 掉落非优势
始终假设最坏的情况 (Always assume worst-case scenario)
It is a very common practice to iterate through a list of data in your program, and lists can vary greatly in size. When I say to always assume worst-case scenario I mean that in a few different ways.
遍历程序中的数据列表是一种非常普遍的做法,列表的大小可能相差很大。 当我说总是假设最坏的情况时,我的意思是说有几种不同的方式。
- If you query for data, assume it is the last item in the list 如果查询数据,则假定它是列表中的最后一项
- Assume the list you’re iterating through will get bigger 假设您要遍历的列表会更大
- Assume some machines will run your algorithm slower than on your machine 假设某些计算机的算法运行速度比计算机上的算法慢
删除常量 (Remove constants)
When we are determining the Big O of an algorithm it helps to remove repeated measurements (constants). This allows us to get a more clear read on the speed of the algorithm by removing unneeded calculation.
当我们确定算法的Big O时,它有助于去除重复的测量值(常数)。 这样,通过删除不需要的计算,我们可以更清楚地了解算法的速度。
Let me show you an example where we remove constants:
让我向您展示一个删除常量的示例:
function printJedi(jediList) {
jediList.forEach((jedi) => {
console.log(jedi)
}
// O(n)
jediList.forEach((jedi) => {
console.log(jedi)
}
// O(n)
}
printJedi(['anakin', 'obi wan', 'yoda'])
// O(n) + O(n) = O(2n)
First, we create a new function
with the identifier printJedi
, this function has a single parameter (jediList
)
首先,我们使用标识符printJedi
创建一个新function
,该函数具有单个参数( jediList
)
function printJedi(jediList) {
Inside of our printJedi
function we call the forEach()
method on jediList
two separate times
我们的内部printJedi
功能我们称之为forEach()
的方法jediList
两个独立的次
jediList.forEach((jedi) => {
console.log(jedi)
}
// O(n)
jediList.forEach((jedi) => {
console.log(jedi)
}
// O(n)
Since we are iterating through the entire jediList
array, each operation is O(n)
. At the end of our function, we add up our Big O (O(n) + O(n)
) which results in O(2n)
. We can simplify this by removing the constants which in this case is 2
. After this, we are left with Big O of O(n)
.
由于我们要遍历整个jediList
数组,因此每个操作都是O(n)
。 在函数的最后,我们将Big O( O(n) + O(n)
)相加,得出O(2n)
。 我们可以通过删除在这种情况下为2
的常量来简化此过程。 之后,我们剩下O(n)
Big O。
输入的不同术语 (Different terms for inputs)
In cases that you iterate through different pieces of data, the Big O calculation will reflect that. Since each collection of data will most likely be different sizes, the consideration of its time complexity comes into play.
如果您遍历不同的数据,Big O计算将反映出这一点。 由于每个数据集合很可能具有不同的大小,因此需要考虑其时间复杂性。
Let me show you an example of calculating Big O while using multiple collections of data:
让我向您展示一个使用多个数据集合计算Big O的示例:
function printJediAndSith(jediList, sithList) {
jediList.forEach(jedi => console.log(jedi))
sithList.forEach(sith => console.log(sith))
}
printJediAndSith(["anakin", "obi wan"], ["vader", "sidious"])
// O(a + b)
Above, we create a new function
with the identifier printJediAndSith
, this function has two parameters: jediList
and sithList
上面,我们用标识符printJediAndSith
创建了一个新function
,该函数有两个参数: jediList
和sithList
function printJediAndSith(jediList, sithList) {
Inside of printJediAndSith
we call the forEach()
method on the jediList
array and the sithList
array
在printJediAndSith
内部,我们在jediList
数组和sithList
数组上调用forEach()
方法
jediList.forEach(jedi => console.log(jedi))
sithList.forEach(sith => console.log(sith))
Now, what do you think the Big O is of the printJediAndSith
function? Since we iterate through a collection of data it should be O(n)
, right? Not in this case.
现在,您认为printJediAndSith
函数的大O是什么? 由于我们遍历数据集合,因此应该为O(n)
,对吗? 在这种情况下不行。
Remember, these parameters will likely have different lengths. It is because of this that we determine the Big O of printJediAndSith
to be O(a + b)
.
请记住,这些参数的长度可能不同。 因此,我们确定printJediAndSith
的Big O为O(a + b)
。
掉落非优势 (Drop non-dominants)
Inside of functions a lot of different things can happen. This includes the range of time complexity as well. When determining the Big O of an algorithm, for the sake of simplifying, it is common practice to drop non-dominants. In short, this means to remove or drop any smaller time complexity items from your Big O calculation.
在函数内部,可能发生许多不同的事情。 这也包括时间复杂度的范围。 在确定算法的Big O时,为了简化起见,通常的做法是舍弃非主要对象 。 简而言之,这意味着从Big O计算中删除或删除任何较小的时间复杂性项。
Let me show you an example of dropping non-dominants:
让我向您展示删除非主要对象的示例:
function printAndSumJediAttendance(jediList) {
jediList.forEach(list => console.log(list))
jediList.forEach(firstList => {
jediList.forEach(secondList => {
console.log(firstList + secondList)
})
})
}
printAndSumJediAttendance([1983, 66, 1138, 94, 1977])
First, we create a new function
with the identifier printAndSumJediAttendance
, this function has a single parameter jediList
首先,我们创建了一个新的function
与标识符printAndSumJediAttendance
,这个函数有一个参数jediList
function printAndSumJediAttendance(jediList) {
Inside of printAndSumJediAttendance
we call the forEach()
method on the jediList
parameter. Because we are iterating through a collection of data this Big O evaluates to O(n)
.
在printAndSumJediAttendance
内部,我们在jediList
参数上调用forEach()
方法。 因为我们正在遍历数据集合,所以这个大O求值为O(n)
。
jediList.forEach(list => console.log(list))
On the next line, we call the forEach()
method on our jediList
parameter. Inside of this forEach
block, we call forEach
on jediList
again. Because we are iterating through nested loops, our Big O evaluates to O(n²)
在下一行,我们在jediList
参数上调用forEach()
方法。 在此forEach
块内部,我们再次在jediList
上调用forEach
。 因为我们要遍历嵌套循环,所以我们的大O求值为O(n²)
jediList.forEach(firstList => {
jediList.forEach(secondList => {
console.log(firstList + secondList)
})
})
Let me break this Big O calculation down a bit:
让我将这个大O的计算分解一下:
function printAndSumJediAttendance(jediList) {
// O(n)
jediList.forEach(list => console.log(list))
// O(n^2)
jediList.forEach(firstList => {
jediList.forEach(secondList => {
console.log(firstList + secondList)
})
})
}
// O(n + n^2) -> simplified -> O(n^2)
As you can see, if we add up the Big O calculations from this function, we are left with a result of O(n + n²)
.
如您所见,如果我们将这个函数的Big O计算结果相加,则会得到O(n + n²)
。
If we analyze this, we see that the part of our calculation with the largest Big O is n²
— because of this, we drop the n
. We do this because n²
is more _dominant_ than n
. Once we have refactored our calculation, we are left with this result: O(n²)
.
如果我们对此进行分析,我们会发现计算中具有最大Big O的部分为n²
因此,我们将n
丢弃。 我们这样做是因为n²
不止_dominant_ n
。 重构计算后,将得到以下结果: O(n²)
。
空间复杂度 (Space Complexity)
Parallel to time complexity, space complexity is the measurement of memory (space) that an algorithm needs
与时间复杂度并行的是,空间复杂度是算法需要的内存(空间)的度量
是什么导致空间复杂性? (What causes Space Complexity?)
- Variables 变数
- Data structures 数据结构
- Function calls 函数调用
- Allocations 分配
Let me show you an example of how we would calculate the space complexity:
让我向您展示如何计算空间复杂度的示例:
function buildALightsaber(pieces) {
let totalPieces = 0 // O(1)
totalPieces = 4 // O(1)
for (let i = 0; i < pieces.length; i++) {
// O(n)
addCrystals() // O(n)
const hasTheForce = true // O(n)
totalPieces++ // O(n)
}
return totalPieces // O(1)
}
// O(3 + 4n) -> simplified -> O(n)
First, we create a new function
with the identifier buildALightsaber
that has a single parameter pieces
首先,我们创建了一个新的function
与标识符buildALightsaber
有一个参数pieces
function buildALightsaber(pieces) {
Inside of buildALightsaber
, we use the let
keyword to create a new variable with the identifier totalPieces
that is assigned to the value 0
. On the following line, we reassign the variable totalPieces
to the value of 4
在buildALightsaber
内部,我们使用let
关键字创建一个标识符为totalPieces
的新变量, totalPieces
其分配给值0
。 在下一行,我们将变量totalPieces
重新分配为值4
Creating and assigning values to variables is O(n)
(constant time); therefore, these two steps are both O(1)
为变量创建和分配值的时间为O(n)
(恒定时间); 因此,这两个步骤都是O(1)
let totalPieces = 0; <-- // O(1)
totalPieces = 4; <-- // O(1)
Next, we create a for
loop and iterate through pieces
接下来,我们创建一个for
循环并遍历各个pieces
Since we are going to be iterating through a collection of data, the Big O of this operation will evaluate to O(n)
由于我们要遍历数据集合,因此此操作的Big O将评估为O(n)
for (let i = 0; i < pieces.length; i++) { <-- // O(n)
Inside of our for
loop, we call a function with an identifier addCrystals()
. Next, we use the const
keyword to create a variable with the identifier hasTheForce
and assign it the value true
. Last, we increment our totalPieces
by one.
在for
循环内部,我们调用一个带有标识符addCrystals()
的函数。 接下来,我们使用const
关键字创建一个标识符为hasTheForce
的变量,并将其赋值为true
。 最后,我们将totalPieces
加1。
In terms of evaluating space complexity while calling functions, creating variables, and updating the values of variables inside of an iteration (for
or while
loops), you have to be mindful of the fact that these actions will occur for each iteration. It is because of this that all actions mentioned will be O(n)
关于在调用函数,创建变量以及在迭代内部( for
或while
循环)中更新变量的值时评估空间复杂性,您必须注意以下事实:每次迭代都会发生这些操作。 因此, 所有提到的动作都是O(n)
addCrystals(); <-- // O(n)
const hasTheForce = true; <-- // O(n)
totalPieces++; <-- // O(n)
After we finish iterating through pieces
we return the value of totalPieces
完成对pieces
迭代之后,我们返回totalPieces
的值
Since this is a single action, the Big O is evaluated to O(1)
or constant time
由于这是单个操作,因此将大O评估为O(1)
或恒定时间
return totalPieces; <-- // O(1)
If we calculate the Big O of this function we originally get (3 + 4n)
. After we apply our principles of simplifying Big O, we know that we can remove constants which will make our final result O(n)
如果我们计算此函数的Big O,则最初得到(3 + 4n)
。 应用简化Big O的原理后,我们知道可以删除常数 ,从而使最终结果为O(n)
综上所述 (In Summary)
I hope after reading this you have a solidified idea of how time and space complexity work, what their importance is in the functions/algorithms we write, and how we can calculate these complexities using Big O notation.
我希望阅读完这篇文章后,您对时间和空间复杂度的工作方式,它们在我们编写的函数/算法中的重要性以及如何使用Big O表示法计算这些复杂度的想法有一个扎实的认识。
Next week I will begin to take a deep dive into arguably the most popular data structure JavaScript developers use, the Array. See you then!
下周,我将开始深入探讨可以说JavaScript开发人员使用的最流行的数据结构Array。 回头见!
翻译自: https://medium.com/dev-genius/time-complexity-space-complexity-and-big-o-notation-500d6104f727
大o表示法描述复杂度