0% found this document useful (0 votes)
4K views

JS Algorithms and Data Structures

This document discusses big O notation and how to analyze the time and space complexity of algorithms. It covers common time complexities like constant, linear, quadratic, and logarithmic time. It also discusses space complexity and compares the time and space properties of arrays and objects.

Uploaded by

mangelique9880
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4K views

JS Algorithms and Data Structures

This document discusses big O notation and how to analyze the time and space complexity of algorithms. It covers common time complexities like constant, linear, quadratic, and logarithmic time. It also discusses space complexity and compares the time and space properties of arrays and objects.

Uploaded by

mangelique9880
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

Big O

Introducing....Big O
The Problem with Time
● Big O Notation is a way to formalize fuzzy counting
● Different machines will record different times ● It allows us to talk formally about how the runtime of an algorithm
● The same machine will record different times! grows as the inputs grow
● For fast algorithms, speed measurements may not be precise enough? ● We won't care about the details, only the trends
● Rather than counting seconds, which are so variable…
● Let's count the number of simple operations the computer has to
perform! Big O Definition
We say that an algorithm is O(f(n)) if the number of simple operations the
Counting Operations computer has to do is eventually less than a constant times f(n), as n
increases
● f(n) could be linear (f(n) = n)
● f(n) could be quadratic (f(n) = n )
● f(n) could be constant (f(n) = 1)
● f(n) could be something entirely different!

Example
Big O Shorthands
● Analyzing complexity with big O can get complicated
● There are several rules of thumb that can help
● These rules won't ALWAYS work, but are a helpful starting point
1. Arithmetic operations are constant
2. Variable assignment is constant
3. Accessing elements in an array (by index) or object (by key) is
constant
O(n) operation inside of an O(n) operation 4. In a loop, the the complexity is the length of the loop times the
complexity of whatever happens inside of the loop
0(n * n) => O(n^2)
Example

Simplifying Big O Expressions


● When determining the time complexity of an algorithm, there are
some helpful rule of thumbs for big O expressions.
● These rules of thumb are consequences of the definition of big O
notation.

Constants Don't Matter


X O(2n) O(n)

X O(500) O(1)

X O(13n ) O(n ) Space Complexity


● So far, we've been focusing on time complexity: how can we analyze
the runtime of an algorithm as the size of the inputs increases?
Smaller Terms Don't Matter ● We can also use big O notation to analyze space complexity: how
X O(n + 10) O(n) much additional memory do we need to allocate in order to run the
code in our algorithm?
X O(1000n + 50) O(n)
What about the inputs?

X O(n + 5n + 8 ) O(n )
● Sometimes you'll hear the term auxiliary space complexity to refer to
space required by the algorithm, not including space taken up by the
Big O of Arrays and Objects
inputs. Logarithms
● Unless otherwise noted, when we talk about space complexity, ● We've encountered some of the most common complexities: O(1),
technically we'll be talking about auxiliary space complexity. O(n), O(n )
Space Complexity in JS ● Sometimes big O expressions involve more complex mathematical
expressions
Rules of Thumb
● One that appears more often than you might like is the logarithm!
● Most primitives (booleans, numbers, undefined, null) are constant
space
● Strings require O(n) space (where n is the string length) Wait, what's a log again?
● Reference types are generally O( n), where n is the length (for arrays) log2 (8) = 3 => 2 =8
or the number of keys (for objects)
log2 (value) = exponent => 2^exponent = value

We'll omit the 2.

log === log2


Rule of Thumb
● The logarithm of a number roughly measures the number of times
you can divide that number by 2 before you get a value that's less
than or equal to one.
Example
Arrays
Ordered List

WHEN TO USE ARRAYS


Objects ● When you need order
Unordered, key value pairs ● When you need fast access / insertion and removal (sort of....)

Big O of Arrays
Insertion - It depends....
Removal - It depends....
Searching - O(N)
Access - O(1)
When to use objects Let's see what we mean by that!
● When you don't need order
● When you need fast access / insertion and removal Big O of Array Operations
push - O(1)
Big O of Objects pop - O(1)
Insertion - O(1) shift - O(N)
Removal - O(1) unshift - O(N)
Searching - O(N) concat - O(N)
Access - O(1) slice - O(N)
When you don't need any ordering, objects are an excellent choice! splice - O(N)
sort - O(N * log N)
forEach/map/filter/reduce/etc. - O(N)
Big O of Object Methods
Object.keys - O(N)
Limitations of Arrays
Object.values - O(N)
Object.entries - O(N) ● Inserting at the beginning is not as easy as we might think! There are
hasOwnProperty - O(1) more efficient data structures for that!
Algorithms and Problem Solving Patterns EXPLORE EXAMPLES
● Start with Simple Examples
PROBLEM SOLVING ● Progress to More Complex Examples
● Understand the Problem ● Explore Examples with Empty Inputs
● Explore Concrete Examples ● Explore Examples with Invalid Inputs
● Break It Down Write a function which takes in a string and returns counts of each
● Solve/Simplify character in the string.
● Look Back and Refactor
Note: many of these strategies are adapted from George Polya, whose book
BREAK IT DOWN
How To Solve It is a great resource for anyone who wants to become a
● Explicitly write out the steps you need to take.
better problem solver.
● This forces you to think about the code you'll write before you write
it, and helps you catch any lingering conceptual issues or
UNDERSTAND THE PROBLEM misunderstandings before you dive in and have to worry about details
1. Can I restate the problem in my own words? (e.g. language syntax) as well.
2. What are the inputs that go into the problem?
3. What are the outputs that should come from the solution to the
Simplify
problem?
● Find the core difficulty in what you're trying to do
4. Can the outputs be determined from the inputs? In other words, do I
● Temporarily ignore that difficulty
have enough information to solve the problem? (You may not be able
● Write a simplified solution
to answer this question until you set about solving the problem.
● Then incorporate that difficulty back in
That's okay; it's still worth considering the question at this early
stage.)
5. How should I label the important pieces of data that are a part of the LOOK BACK & REFACTOR
problem? Congrats on solving it, but you're not done!
Write a function which takes two numbers and returns their sum.

REFACTORING QUESTIONS
EXPLORE EXAMPLES
● Can you check the result?
● Coming up with examples can help you understand the problem
● Can you derive the result differently?
better
● Can you understand it at a glance?
● Examples also provide sanity checks that your eventual solution
● Can you use the result or method for some other problem?
works how it should
● Can you improve the performance of your solution?
User Stories! Unit Tests!
● Can you think of other ways to refactor?
● How have other people solved this problem?
Recursion
A process (a function in our case) that calls itself

It's EVERYWHERE!
● JSON.parse / JSON.stringify
● document.getElementById and DOM traversal algorithms
● Object traversal Where things go wrong
● Very common with more complex algorithms ● No base case
● It's sometimes a cleaner alternative to iteration ● Forgetting to return or returning the wrong thing!
● Stack overflow!
How recursive functions work
Invoke the same function with a different input until you reach your base
case!

Base Case
● The condition when the recursion ends.
● This is the most important concept to understand

Two essential parts of a recursive function!


● Base Case Helper Method Recursion
● Different Input
● Our first recursive function

sumRange with the call stack


Pure Recursion productOfArray
● Write a function called productOfArray which takes in an array of
numbers and returns the product of them all.

productOfArray([1,2,3]) // 6
productOfArray([1,2,3,10]) // 60

Tail Call Optimization


● ES2015 allows for tail call optimization, where you can make some
function calls without growing the call stack.
● By using the return keyword in a specific fashion we can extract
Pure Recursion Tips
output from a function without keeping it on the call stack.
● For arrays, use methods like slice, the spread operator, and concat
● Unfortunately this has not been implemented across multiple
that make copies of arrays so you do not mutate them
browsers so it is not reasonable to implement in production code.
● Remember that strings are immutable so you will need to use
methods like slice, substr, or substring to make copies of strings
● To make copies of objects use Object.assign, or the spread operator

What about big O?


● Measuring time complexity is relatively simple. You can measure the
time complexity of a recursive function as then number of recursive
calls you need to make relative to the input
● Measuring space complexity is a bit more challenging. You can
measure the space complexity of a recursive function as the
maximum number of functions on the call stack at a given time, since
the call stack requires memory.

POWER
power(2,4) //16
power(3,2) //9
power(3,3) //27
Sorting Algorithms
Sorting is the process of rearranging the items in a collection (e.g. an array)
so that the items are in some kind of order.

Examples
Sorting numbers from smallest to largest
Sorting names alphabetically
Sorting movies based on release year
Sorting movies based on revenue

BubbleSort Pseudocode
Why do we need to learn this?
● Start looping from with a variable called i the end of the array
● Sorting is an incredibly common task, so it's good to know how it
towards the beginning
works
● Start an inner loop with a variable called j from the beginning until i -
● There are many different ways to sort things, and different techniques
1
have their own advantages and disadvantages
● If arr[j] is greater than arr[j+1], swap those two values!
● Sorting sometimes has quirks, so it's good to understand how to
● Return the sorted array
navigate them

Selection Sort
Telling JavaScript how to sort
● The built-in sort method accepts an optional comparator function Similar to bubble sort, but instead of first placing large values into sorted position, it
● You can use this comparator function to tell JavaScript how you want places small values into sorted position
it to sort
● The comparator looks at pairs of elements (a and b), determines their
sort order based on the return value
● If it returns a negative number, a should come before b
● If it returns a positive number, a should come after b,
● If it returns 0, a and b are the same as far as the sort is concerned

BubbleSort
A sorting algorithm where the largest values bubble up to the top!
Selection Sort Pseudocode
● Store the first element as the smallest value you've seen so far.
● Compare this item to the next item in the array until you find a
smaller number.
● If a smaller number is found, designate that smaller number to be the
new "minimum" and continue until the end of the array.
● If the "minimum" is not the value (index) you initially began with,
swap the two values.
● Repeat this with the next element until the array is sorted.

Insertion Sort
Builds up the sort by gradually creating a larger left half which is always sorted

Insertion Sort Pseudocode


● Start by picking the second element in the array
● Now compare the second element with the one before it and swap if
necessary.
● Continue to the next element and if it is in the incorrect order, iterate
through the sorted portion (i.e. the left side) to place the element in
the correct place.
● Repeat until the array is sorted.
Intermediate Sorting Algorithms Merging Arrays
● In order to implement merge sort, it's useful to first implement a
WHY LEARN THIS?
function responsible for merging two sorted arrays
● The sorting algorithms we've learned so far don't scale well
● Given two arrays which are sorted, this helper function should create
● Try out bubble sort on an array of 100000 elements, it will take quite
a new array which is also sorted, and consists of all of the elements in
some time!
the two input arrays
● We need to be able to sort large arrays more quickly
● This function should run in O(n + m) time and O(n + m) space and
should not modify the parameters passed to it.
FASTER SORTS
● There is a family of sorting algorithms that can improve time Merging Arrays Pseudocode
complexity from O(n ) to O(n log n)
● Create an empty array, take a look at the smallest values in each input
● There's a tradeoff between efficiency and simplicity
array
● The more efficient algorithms are much less simple, and generally
● While there are still values we haven't looked at...
take longer to understand
● If the value in the first array is smaller than the value in the second
array, push the value in the first array into our results and move on to
Merge Sort the next value in the first array
● It's a combination of two things - merging and sorting! ● If the value in the first array is larger than the value in the second
● Exploits the fact that arrays of 0 or 1 element are always sorted array, push the value in the second array into our results and move on
● Works by decomposing an array into smaller arrays of 0 or 1 to the next value in the second array
elements, then building up a newly sorted array ● Once we exhaust one array, push in all remaining values from the
other array

mergeSort Pseudocode
● Break up the array into halves until you have arrays that are empty or
have one element
● Once you have smaller sorted arrays, merge those arrays with other
sorted arrays until you are back at the full length of the array
● Once the array has been merged back together, return the merged
(and sorted!) array
Quicksort Pseudocode
● Call the pivot helper on the array
● When the helper returns to you the updated pivot index, recursively
call the pivot helper on the subarray to the left of that index, and the
subarray to the right of that index
● Your base case occurs when you consider a subarray with less than 2
elements

Quick Sort
● Like merge sort, exploits the fact that arrays of 0 or 1 element are
always sorted COMPARISON SORTS
● Works by selecting one element (called the "pivot") and finding the Average Time Complexity
index where the pivot should end up in the sorted array
● Once the pivot is positioned appropriately, quick sort can be applied
on either side of the pivot
Pivot Helper ● Swap the starting element (i.e. the pivot) with the pivot index
● In order to implement merge sort, it's useful to first implement a ● Return the pivot index
function responsible arranging elements in an array on either side of a
RADIX SORT
pivot
● Radix sort is a special sorting algorithm that works on lists of
● Given an array, this helper function should designate an element as
numbers
the pivot
● It exploits the fact that information about the size of a number is
● It should then rearrange elements in the array so that all values less
encoded in the number of digits.
than the pivot are moved to the left of the pivot, and all values greater
● More digits means a bigger number!
than the pivot are moved to the right of the pivot
● The order of elements on either side of the pivot doesn't matter! RADIX SORT HELPERS
● The helper should do this in place, that is, it should not create a new ● In order to implement radix sort, it's helpful to build a few helper
array functions first:
● When complete, the helper should return the index of the pivot ● getDigit(num, place) - returns the digit in num at the given place
Picking a pivot value
● The runtime of quick sort depends in part on how one selects the ● digitCount(num) - returns the number of digits in num
pivot ● mostDigits(nums) - Given an array of numbers, returns the number of
● Ideally, the pivot should be chosen so that it's roughly the median digits in the largest numbers in the list
value in the data set you're sorting
​RADIX SORT PSEUDOCODE
● For simplicity, we'll always choose the pivot to be the first element
● Define a function that accepts list of numbers
(we'll talk about consequences of this later)
● Figure out how many digits the largest number has
● All that matters is for 5 to be at the index 4, for smaller values to be
● Loop from k = 0 up to this largest number of digits
to the left, and for larger values to be to the right.
● For each iteration of the loop:
Pivot Pseudocode ● Create buckets for each digit (0 to 9)
● It will help to accept three arguments: an array, a start index, and an ● place each number in the corresponding bucket based on its kth digit
end index (these can default to 0 and the array length minus 1, ● Replace our existing array with values in our buckets, starting with 0
respectively) and going up to 9
● Grab the pivot from the start of the array ● return list at the end!
● Store the current pivot index in a variable (this will keep track of
where the pivot should end up)
● Loop through the array from the start until the end
● If the pivot is greater than the current element, increment the pivot
index variable and then swap the current element with the element at
the pivot index
Data Structures Instance Methods
● Data structures are collections of values, the relationships among
them, and the functions or operations that can be applied to the data
● Different data structures excel at different things. Some are highly
specialized, while others (like arrays) are more generally used.

What is a class?
● Does JavaScript really have them?
● A blueprint for creating objects with pre-defined properties and
methods
● We're going to implement data structures as classes!

Class Methods
THE SYNTAX

● The method to create new objects must be called constructor


● The class keyword creates a constant, so you can not redefine it.
Watch out for the syntax as well!

Creating objects from classes Recap


We use the new keyword ● Classes are blueprints that when created make objects known as
instances
● Classes are created with the new keyword
● The constructor function is a special function that gets run when the
class is instantiated
● Instance methods can be added to classes similar to methods in objects
● Class methods can be added using the static keyword
5. Increment the length by one
6. Return the linked list
Singly Linked List
Popping
What is a linked list?
Removing a node from the end of the Linked List!
● A data structure that contains a head, tail and length property.
● Linked Lists consist of nodes, and each node has a value and a Popping pseudocode
pointer to another node or null 1. If there are no nodes in the list, return undefined
2. Loop through the list until you reach the tail
3. Set the next property of the 2nd to last node to be null
4. Set the tail to be the 2nd to last node
5. Decrement the length of the list by 1
6. Return the value of the node removed

Shifting
Removing a new node from the beginning of the Linked List!
Comparisons with Arrays Shifting pseudocode
Lists 1. If there are no nodes, return undefined
2. Store the current head property in a variable
● Do not have indexes!
3. Set the head property to be the current head's next property
● Connected via nodes with a next pointer
4. Decrement the length by 1
● Random access is not allowed
5. Return the value of the node removed
Arrays
● Indexed in order! Unshifting
● Insertion and deletion can be expensive Adding a new node to the beginning of the Linked List!
● Can quickly be accessed at a specific index Pseudocode
1. This function should accept a value
Pushing
2. Create a new node using the value passed to the function
Adding a new node to the end of the Linked List! 3. If there is no head property on the list, set the head and tail to be the
Pseudocode newly created node
1. This function should accept a value 4. Otherwise set the newly created node's next property to be the current
2. Create a new node using the value passed to the function head property on the list
3. If there is no head property on the list, set the head and tail to be the 5. Set the head property on the list to be that newly created node
newly created node 6. Increment the length of the list by 1
4. Otherwise set the next property on the tail to be the new node and set 7. Return the linked list
the tail property on the list to be the newly created node
Pseudocode
Get 1. If the index is less than zero or greater than the length, return
Retrieving a node by it's position in the Linked List! undefined
2. If the index is the same as the length-1, pop
Pseudocode
3. If the index is 0, shift
1. This function should accept an index
4. Otherwise, using the get method, access the node at the index - 1
2. If the index is less than zero or greater than or equal to the length of
5. Set the next property on that node to be the next of the next node
the list, return null
6. Decrement the length
3. Loop through the list until you reach the index and return the node at
7. Return the value of the node removed
that specific index
REVERSE
Set
Reversing the Linked List in place!
Changing the value of a node based on it's position in the Linked List
Pseudocode
1. This function should accept a value and an index
2. Use your get function to find the specific node.
3. If the node is not found, return false
4. If the node is found, set the value of that node to be the value passed
to the function and return true Reverse pseudocode
1. Swap the head and tail
Insert 2. Create a variable called next
Adding a node to the Linked List at a specific position 3. Create a variable called prev
Pseudocode 4. Create a variable called node and initialize it to the head property
1. If the index is less than zero or greater than the length, return false 5. Loop through the list
2. If the index is the same as the length, push a new node to the end of 6. Set next to be the next property on whatever node is
the list 7. Set the next property on the node to be whatever prev is
3. If the index is 0, unshift a new node to the start of the list 8. Set prev to be the value of the node variable
4. Otherwise, using the get method, access the node at the index - 1 9. Set the node variable to be the value of the next variable
5. Set the next property on that node to be the new node 10. Once you have finished looping, return the list
6. Set the next property on the new node to be the previous next
Big O of Singly Linked List
7. Increment the length
8. Return true Insertion - O(1)
Removal - O(1) | | O(N)
Remove Searching - O(N)
Removing a node from the Linked List at a specific position Access - O(N)
Doubly Linked List SHIFTING
Almost identical to Singly Linked Lists, except every node has another Removing a node from the beginning of the Doubly Linked List
pointer, to the previous node!
Pseudocode
More memory === More Flexibility
1. If length is 0, return undefined
It's almost always a tradeoff!
2. Store the current head property in a variable (we'll call it old head)
3. If the length is one
4. set the head to be null
5. set the tail to be null
6. Update the head to be the next of the old head
7. Set the head's prev property to null
8. Set the old head's next to null
9. Decrement the length
PUSHING
10. Return old head
Adding a node to the end of the Doubly Linked List
Pseudocode UNSHIFTING
1. Create a new node with the value passed to the function Adding a node to the beginning of the Doubly Linked List
2. If the head property is null set the head and tail to be the newly created Pseudocode
node 1. Create a new node with the value passed to the function
3. If not, set the next property on the tail to be that node 2. If the length is 0
4. Set the previous property on the newly created node to be the tail 3. Set the head to be the new node
5. Set the tail to be the newly created node 4. Set the tail to be the new node
6. Increment the length 5. Otherwise
7. Return the Doubly Linked List 6. Set the prev property on the head of the list to be the new node
7. Set the next property on the new node to be the head property
Popping
8. Update the head to be the new node
Removing a node from the end of the Doubly Linked List
9. Increment the length
Pseudocode 10. Return the list
1. If there is no head, return undefined
2. Store the current tail in a variable to return later GET
3. If the length is 1, set the head and tail to be null Accessing a node in a Doubly Linked List by its position
4. Update the tail to be the previous Node. Pseudocode
5. Set the newTail's next to null 1. If the index is less than 0 or greater or equal to the length, return null
6. Decrement the length 2. If the index is less than or equal to half the length of the list
7. Return the value removed
3. Loop through the list starting from the head and loop towards the Pseudocode
middle 1. If the index is less than zero or greater than or equal to the length
4. Return the node once it is found return undefined
5. If the index is greater than half the length of the list 2. If the index is 0, shift
6. ​Loop through the list starting from the tail and loop towards the 3. If the index is the same as the length-1, pop
middle 4. Use the get method to retrieve the item to be removed
7. Return the node once it is found 5. Update the next and prev properties to remove the found node from
the list
SET 6. Set next and prev to null on the found node
Replacing the value of a node to the in a Doubly Linked List 7. Decrement the length
Pseudocode 8. Return the removed node.
1. Create a variable which is the result of the get method at the index
passed to the function Reverse
2. If the get method returns a valid node, set the value of that node to be Reversing a Doubly Linked List in place!
the value passed to the function
Pseudocode
3. Return true
1. Create a variable called current and set it to be the head of the list
4. Otherwise, return false
2. Create a variable called tail and set it to be the head of the list
3. Loop through the list and set the next property of the current node to
INSERT be the prev property of the current node
Adding a node in a Doubly Linked List by a certain position 4. If there is no next property, set the tail to be the head and the head to
Pseudocode be the current variable
1. If the index is less than zero or greater than or equal to the length 5. Return the list
return false
2. If the index is 0, unshift Big Oof Doubly Linked Lists
3. If the index is the same as the length, push Insertion - O(1)
4. Use the get method to access the index -1 Removal - O(1)
5. Set the next and prev properties on the correct nodes to link Searching - O(N)
everything together Access - O(N)
6. Increment the length Technically searching is O(N / 2), but that's still O(N)
7. Return true

REMOVE
Removing a node in a Doubly Linked List by a certain position

You might also like