├── .gitignore ├── LICENSE ├── lesson-plans ├── 01-big-o.md ├── 02-linear-data.md ├── 03-stacks-queues.md ├── 04-hash-tables.md ├── 05-intro-to-trees.md ├── 06-special-trees.md ├── 07-intro-to-graphs.md ├── 08-graph-implementations.md └── resources │ ├── Array-list.png │ ├── SimpleGraph.png │ ├── Trie.png │ ├── big-o-graph.png │ └── simple-tree.png ├── outline.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | customizations/ 2 | **.DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /lesson-plans/01-big-o.md: -------------------------------------------------------------------------------- 1 | # Introduction To Algorithms & Data Structures 2 | 3 | This lesson should give students a big picture view about why algorithms and computer science is important, as well as delve into the specifics of Big O. 4 | 5 | ## Objectives 6 | 7 | * Students can describe how they might interact with data structures and algorithms in their future careers. 8 | * Students know that algorithms are measured in terms of their input. 9 | * Students can identify important complexity classes. 10 | * Students can classify provided code or descriptions of an algorithm into the proper complexity class. 11 | 12 | ## What is Computer Science/Algorithms/Data Structures (5-10 minutes) 13 | 14 | * Computer science is broadly, the study of computation. 15 | * __What kinds of things do CS degrees focus on?__ 16 | * *efficiency* 17 | * *computability* 18 | * *problem solving* 19 | * *Algorithms & data structures* 20 | * *Operating systems, architecture, compilers, scientific computing, AI/ML, robotics...* 21 | * Computer science is a very broad field -- but the common thread in all of these subdomains is that programs rely on data structures and algorithms to process those data structures. 22 | * __What are data structures? Give some examples?__ 23 | * *systems for holding and processing data* 24 | * *array, hash table, stack, queue....* 25 | * *Does anyone want to say a JavaScript Object is a data structure?* 26 | * *What about a database like Postgres? This is more like a collection of structures...* 27 | * __What about an algorithm, define that word? Give some examples?__ 28 | * *A process for computing something* 29 | * __Who implements the data structures you use most often?__ 30 | * *language & library maintainers, mostly* 31 | * __Why study this stuff?__ 32 | * *For interviews...* 33 | * *Because these fundamental tools are the ones that tie together all the myriad disciplines of programming* 34 | * *Because having strong mental models about how computers work makes you a better programmer* 35 | * *Eventually, you'll have to make a program you wrote faster than it is* 36 | * *Knowing about the available tools helps you choose the right ones* 37 | 38 | ## Big O & Complexity Theory (30 minutes) 39 | 40 | * Complexity theory is a branch of computer science that focuses on measuring the performance of algorithms. 41 | * Complexity is an *abstract* measurement, not a concrete measurement... 42 | * __What does it mean to be an abstract measurement?__ 43 | * *We want to be able to compare algorithms without implementing them -- planning before implementing* 44 | * *When comparing algorithms, we don't always want to let the code get in the way* 45 | * *Our measurements cannot be in concrete units, like seconds, because our algorithms might only exist on whiteboards* 46 | * __What is Big O Notation?__ 47 | * *An estimate of the time or space requirements of an algorithm* 48 | * *In terms of the input to the algorithm* 49 | * There are some really technical definitions that I could give of Big O, involving complex F(G(x)) math jargon but they are honestly not very helpful. 50 | * __Why is Big O measured in terms of the input?__ 51 | * *It allows us to measure how the algorithm scales as the input scales* 52 | * __Why measure time and space requirements?__ 53 | * *Those are our two limiting factors, typically* 54 | * When we use Big O, we drop the constants and all but the "worst" factor involving 'n': 55 | * `3 + 5n + 2n^2` => `O(n^2)` 56 | * __Why do we do this?__ 57 | * *We rarely care about small input sizes. Slow algorithms still finish quickly for small input sizes* 58 | * *Big O only cares about what happens as n gets VERY large* 59 | * *Because this is a measure of how an algorithm scales, not a measurement of specific performance* 60 | * __Is this a weakness of big O?__ 61 | * *YES! Big O is always going to be a fuzzy lens into performance.* 62 | * *But it's still a useful lens* 63 | * __EVERYONE WRITES: a formula that highlights this weakness of big O__ 64 | * *I'm hoping for something like: `9999999999n + n^2` => O(n^2). It's n-squared but the large constant factor on n will clearly drive our performance in most realistic situations. n generally won't be larger than 9999999999* 65 | * __What other weaknesses can you think of for big O?__ 66 | * *Doesn't consider actual hardware at all* 67 | * *Memory usage, cache utilization, hard drive and network use will all have significant impact on performance but are not captured by Big O* 68 | * *Might be worth mentioning that we can time/benchmark our actual programs as well* 69 | * Some closing thoughts: 70 | * You can compute the Big O of a "worst case" input or "best case" input - both are useful measurements! 71 | * It's often worth knowing what your actual dataset is, and computing big O over that case. 72 | * Big O is a "general" tool -- getting more specific by actually measuring your code's performance is a good idea. 73 | 74 | ## Class Exercise 1 -- Order Complexity Classes 75 | 76 | * __Individually__ 77 | * Put the following complexity classes in order from slowest to fastest 78 | * constant, log(n), n, n*log(n) n^2, 2^n, n!, n^n 79 | * __Compare your work with your neighbor__ 80 | * Resolve any disagreements 81 | * __Graph all these functions -- doesn't have to be precise, big O is an estimate anyway right?__ 82 | * ![big o graph](resources/big-o-graph.png) 83 | 84 | * __Go over the answers as a class__ 85 | * *THIS IS A REALLY GOOD TIME TO DEFINE A LOGARITHM, which people rarely understand.* 86 | * __Where do we draw the line between "good enough" and "not good enough"?__ 87 | * *Good question ... it depends!* 88 | * *Usually n^2 is "okay" and 2^n is almost always unacceptable* 89 | * *but it depends on the problem and your specific requirements!* 90 | 91 | ## Class Exercise 2 -- Modeling "Joins" 92 | 93 | * __What is a database join?__ 94 | * *A way of combining two tables based on a shared piece of data* 95 | * *If the class doesn't have a strong idea, it's okay to go into more details* 96 | * __Assume we have two tables with a shared key (draw a representation)__ 97 | * For each of the following situations have the students define an algorithm to compute the join and define the big O speed of that algorithm. 98 | * encourage students to "get the idea" of the algorithm, not the code or even pseudo code. 99 | * Give the answers 1 by 1 with the class 100 | 101 | 1. If we cannot sort either of the tables or use any external data structures, how can we join these tables? What's the big o? 102 | * O(a*b), which is "essentially" O(n^2) 103 | 2. What if we sort the right table? 104 | * What is the cost of sorting (reveal this as n log n where n=size of the right table) 105 | * How can we do better than the above brute force? 106 | * *apply binary search* 107 | * So we get `O(b log b + a log b)`; `b log b` is the cost of sorting table b, then for each item in table a we perform a binary search over table b, which is the `a log b` part 108 | * How can we tell if we SHOULD do the sort? 109 | * `(b log b + a log b < a*b)` 110 | * If b is very small, it's not worth it, but our database program can just do this comparison in constant time if the sizes of the tables are known. 111 | 3. What if we sort both tables? 112 | * What is the cost of sorting? 113 | * `a log a + b log b` 114 | * What is the best algorithm we can think of? 115 | * sort merge join: 116 | * set a pointer at the start of each sorted table. 117 | * advance the pointer on table b until the value is larger than the one at the pointer for table a. 118 | * advance the pointer on table a one place. 119 | * repeat the previous two steps until we're done. 120 | * We look at every item in the two tables exactly once: `a+b` (plus the cost of sorting both tables) 121 | * How can we tell if we SHOULD do the sort? 122 | 4. What if we can use an external hash table? 123 | * Whats the algorithm? (hash join -- hash entries in table b then join to the hash) 124 | * Whats the big O? O(a+b) 125 | * *This algorithm pays a storage cost for building the hash table -- a classic tradeoff between storage and speed* 126 | 127 | ## Class Exercise 3 -- Classify Code Samples 128 | 129 | * __For each short program below, decide it's big O time complexity__ 130 | 131 | ```js 132 | // Assume a and b are integers 133 | function multiply(a, b) { 134 | let sum = 0; 135 | for(let i = 0; i < a; i++) { 136 | sum += b; 137 | } 138 | 139 | return sum; 140 | } 141 | ``` 142 | 143 | O(a) 144 | 145 | ```js 146 | // Assume a and b are integers 147 | function multiplyTwo(a, b) { 148 | let bigger = Math.max(a, b); 149 | let smaller = bigger === a ? b : a; 150 | 151 | let sum = 0; 152 | while(bigger >= 1) { 153 | if(bigger % 2 === 1) { 154 | sum += smaller; 155 | } 156 | 157 | // You may need to explain that this is division and multiplication by 2. 158 | // If you want to avoid that conversation for now, change these to plain 159 | // integer division and multiplication. 160 | bigger = bigger >> 1; 161 | smaller = smaller << 1; 162 | } 163 | 164 | return sum; 165 | } 166 | ``` 167 | 168 | O(log(max(a, b))) 169 | 170 | ```js 171 | // Assume students is an array of integers 172 | function computePairs(students) { 173 | let pairs = []; 174 | for(let outer of students) { 175 | for(let inner of students) { 176 | pairs.push(multiply(outer, inner)); 177 | } 178 | } 179 | } 180 | ``` 181 | 182 | O(n^2 * m) where n is the length of the students array, and m is the average of the values in students. 183 | 184 | ```js 185 | // Assume students is an array of integers 186 | function computePairsTwo(students) { 187 | let pairs = []; 188 | for(let outer of students) { 189 | for(let inner of students) { 190 | pairs.push(multiplyTwo(outer, inner)); 191 | } 192 | } 193 | } 194 | ``` 195 | 196 | O(n^2 * log(m)) where n is the length of the students array, and m is the average of the values in students. 197 | -------------------------------------------------------------------------------- /lesson-plans/02-linear-data.md: -------------------------------------------------------------------------------- 1 | # Implementation Details of Linear Structures 2 | 3 | This lesson teaches students about Arrays (as in C arrays) and Linked Lists. 4 | 5 | # Learning Objectives 6 | 7 | * Students can describe the difference between an Abstract Data Type and an implementation 8 | * Students can describe the difference between an Array and a Linked List 9 | * Students can compare and contrast the speed of operations in Arrays and Linked Lists 10 | * Students can implement and analyze a queue or stack backed by 11 | * A Linked List 12 | * An Array 13 | 14 | ## Abstract Data Type vs Implementation (5 minutes) 15 | 16 | * __What is the difference between an abstract data type and an implementation of a data type?__ 17 | * *Crucial that students can see how we can define desired behavior without specifying HOW that behavior is achieved.* 18 | * *Lean on last class exercise: multiplication is abstract, but your homework had two different implementations of multiplication; one ran in linear time, the other in logarithmic time.* 19 | * __What makes ADT's valuable to us as programmers?__ 20 | * *"Test the interface, not the implementation"* 21 | * *Makes it possible to compare implementations if they share an ADT* 22 | * *Helpful during the design process: describe what you want, THEN build something to do that* 23 | 24 | ## Dynamic Array Sample Implementation (20 minutes) 25 | 26 | * __Socratic: At the C (the language) level, what is an array?__ 27 | * *Computer Memory is a series of consecutive bits (not exactly but this is a good mental model)* 28 | * *An array is some subset of these contiguous bits* 29 | * *We get a pointer to the start of that memory block* 30 | * *We know how big each item is* 31 | * *We know the number of items that we have reserved space for* 32 | * __Everyone Draws: A diagram that represents this concept of an array__ 33 | * __Share: describe your diagram to your neighbor__ 34 | * *Wander the class and listen to the conversations if possible!* 35 | * *Draw your own diagram and explain it to the students, or ask a student to draw theirs on the board for the whole class and discuss it* 36 | * Real implementations of arrays build on this simple idea, and add some other "known memory locations" for example, to store the length of the array. 37 | * __Socratic: So what happens when we try and insert data after the last index?__ 38 | * *We can throw an error* 39 | * *Or we can resize* 40 | * Most array implementations these days reserve more memory and copy the values to the new enlarged memory space! 41 | * __Discuss:__ 42 | * __What are some decisions you'd have to make at resize time?__ 43 | * *how much bigger should we make the new space?* 44 | * *Is there room to expand this array right at the end of this one?* 45 | * __Will the new memory location start at the same place as the old one?__ 46 | * *We can't ALWAYS do this, but if we do we can avoid copying the old data into a new location* 47 | * *But honestly this is an unlikely outcome.* 48 | * __What if there isn't enough memory?__ 49 | * *We can still throw an error! but that's about it.* 50 | * __How much more memory should we get?__ 51 | * *Conventional wisdom used to be to double the size* 52 | * *V8's implementation currently makes new space 9/8ths as big as the old space* 53 | * *Talk about the tradeoffs here, it's a great way to discuss overuse of memory vs less computational cost of resizing more frequently.* 54 | 55 | ## Linked List Implementation Details (20 minutes) 56 | 57 | * A Linked List is a series of nodes connected by pointers. 58 | * Each node is a value and 1 or 2 pointers to other nodes. 59 | * In a singly linked list every node knows which node comes after it. 60 | * In a doubly linked list every node ASLO knows which node. comes before it. 61 | * Reordering data is a matter of changing the pointers 62 | * Head and Tail are our "entry points" to the data--they are pointers that always point to the first and last elements in the list. 63 | * __Discuss:__ 64 | * __would we ever want to not have a tail or head?__ 65 | * *You always need the head, but you don't NEED the tail. Typically, you'll have both.* 66 | * __what are some advantages to single vs double linkage?__ 67 | * *Double links takes more memory* 68 | * *Double links makes some of the functions easier to implement & faster* 69 | * *addRight is an example of something that is much better with double linked and a tail* 70 | * __Compare these to arrays in terms of Big O and important operations.__ 71 | * *Make sure to discuss inserting on both ends, in the middle, access by index, and removal* 72 | * __Pair/Share: Array's are "contiguous blocks of memory", how would you describe a linked lists memory usage?__ 73 | * *They're all over the place, each new node gets created on the fly, so who knows how the memory locations are related to each other. All we know is that if we follow the pointers everything will work out.* 74 | * __Everybody draws: Draw a diagram that contrasts these two models__ 75 | ![Array vs List](resources/Array-list.png) 76 | * __What are the pros and cons of this more 'flexible' memory model?__ 77 | * *Constant time add left is an example of an advantage* 78 | * *Never need to "resize" is an advantage* 79 | * *More memory per item is a disadvantage* 80 | * *Fragmented memory could be a disadvantage or advantage depending on your use* 81 | * *But, due to cache utilization and modern computer architecture, it's probably a disadvantage* 82 | * __Give students a chance to ask questions about this API__ 83 | 84 | ``` 85 | class List { 86 | constructor(); 87 | push(); 88 | pop(); 89 | pushLeft(); 90 | popLeft(); 91 | insert(idx); 92 | size(); 93 | } 94 | ``` 95 | 96 | ## Exercise: Comparing Performance (10-15 minutes) 97 | 98 | * For each, ask students to (in pairs) define the big O of these operations in both Arrays and Linked Lists: 99 | * Students should also describe the operations in pseudocode and draw a step by step diagram for each operation. 100 | * push(val) 101 | * *Array: O(1) (as long as we don't resize. You can introduce the term "Amortized" constant time)* 102 | * *Linked List: O(1) (always, not amortized)* 103 | * pushLeft(val) 104 | * *Array: O(n), we have to shift every item one to the right to make room* 105 | * *Linked List: O(1), just create a node then adjust the head pointer and the first node's prev/next pointer.* 106 | * insert(idx, val) 107 | * *Array: O(n), have to shift items to the right of idx* 108 | * *Linked List: O(n), have to iterate to find the two nodes where you are performing the insert* 109 | * *Both are linear, but for different reasons, which is worth pointing out.* 110 | 111 | ## Some Closing Thoughts 112 | 113 | * Memory usage has a significant impact, even though linked lists have some favorable complexity O(1) for many operations, they are not used because of the way computer memory/caching works. 114 | * This is another example of a place where Big O breaks down a little bit. 115 | * Objects, Arrays, and other data types in JavaScript are *very complex* -- they are combinations of c-arrays, linked lists, hash tables, and more. For example: 116 | 117 | ```js 118 | let a = []; 119 | a[100000000] = 2; 120 | ``` 121 | 122 | * with the V8 engine, a is now a hash table, not an array, because it would be very wasteful to have (100000000-1) empty slots just to store the value 2 at the end. 123 | 124 | ## Class Exercises 125 | 126 | * [Implement a singly linked list](https://leetcode.com/problems/design-linked-list/description/) 127 | -------------------------------------------------------------------------------- /lesson-plans/03-stacks-queues.md: -------------------------------------------------------------------------------- 1 | # Stacks And Queues 2 | 3 | In this lesson students will compare the implementations of stacks and queues based on their backing data structure (array or linked list) and apply these data structures to solve problems. 4 | 5 | ## Learning Objectives 6 | 7 | * Students can describe stacks and queues as abstract data types 8 | * Especially Last In First Out (LIFO) vs First In First Out (FIFO) properties 9 | * Students can implement and analyze a queue or stack backed by: 10 | * A Linked List 11 | * An Array 12 | * Students can use stacks and queues to solve problems. 13 | 14 | #### Stacks (5 minutes) 15 | 16 | * __What are stacks?__ 17 | * *Stacks are a Last In First Out data structure.* 18 | * *Like a stack of plates, once you've put something on top you have to take it out before you can get the bottom plate.* 19 | * __Why are they used?__ 20 | * *They are commonly used when you need to "reverse" something* 21 | * *Also when we need a log of what has happened* 22 | * __what are some examples?__ 23 | * *undo/redo in text editors is a good example* 24 | * *The Call Stack is a great example—we call a series of functions then have to back out* 25 | * *Depth first search uses a stack, as we'll see later in the course* 26 | * A minimal stack abstract data type could have 4 methods: 27 | * Push 28 | * Pop 29 | * Peak 30 | * Size 31 | * We can very easily use an array to create a Stack class in JavaScript: 32 | 33 | ```js 34 | class Stack { 35 | constructor() { 36 | this._array = []; 37 | } 38 | 39 | size() { 40 | return this._array.length; 41 | } 42 | 43 | push(val) { 44 | this._array.push(val); 45 | } 46 | 47 | pop() { 48 | return this._array.pop(); 49 | } 50 | 51 | peak() { 52 | return this._array[this._array.length - 1]; 53 | } 54 | } 55 | ``` 56 | 57 | 58 | ## Queues (5 minutes) 59 | 60 | * __What are queues?__ 61 | * *The opposite of a stack, they maintain the order rather than reverse it* 62 | * *First in first out* 63 | * *Like a line at the store or anywhere humans line up* 64 | * __Why are they used?__ 65 | * *To maintain a list of things and keep them in order* 66 | * __Give some examples?__ 67 | * *The JS Event Queue, and process queuing in general* 68 | * *Breadth First Search, as we'll see* 69 | * *Buffering data* 70 | * *Routers use Queues to prioritize routing internet traffic in the order it arrives* 71 | * A Simple ADT: 72 | * Enqueue 73 | * Dequeue 74 | * Peak 75 | * Size 76 | * Again: easy to implement using a JS array: 77 | 78 | ```js 79 | class Queue { 80 | constructor() { 81 | this._array = []; 82 | } 83 | 84 | size() { 85 | return this._array.length; 86 | } 87 | 88 | enqueue(val) { 89 | this._array.unshift(val); 90 | } 91 | 92 | dequeue() { 93 | return this._array.pop(); 94 | } 95 | 96 | peak() { 97 | return this._array[this._array.length - 1]; 98 | } 99 | } 100 | ``` 101 | 102 | ## Using an Array (5-15 minutes) 103 | 104 | * *Have students answer, then go over the answers as a class* 105 | * __Pair Exercise: Using the implementations above answer the following:__ 106 | * __What are the Big O values of push/pop and enqueue/dequeue in this implementation?__ 107 | * Push: O(1) 108 | * Pop: O(1) 109 | * Enqueue: O(n) (have to shift everything over one) 110 | * Dequeue: O(1) 111 | * *Don't forget: What if the array has to resize? These are all amortized constant time, not strictly constant time.* 112 | 113 | ## Using a Linked List (5-15 minutes) 114 | 115 | * *Have students answer, then go over the answers as a class* 116 | * __Pair Exercise: Describe how you'd use a Linked List to implement a stack and a queue?__ 117 | * *It would look very similar to the code above, because LinkedLists and Arrays share an Abstract Data Type!!* 118 | * __What are the Big O values of push/pop and enqueue/dequeue in this implementation?__ 119 | * Push: O(1) 120 | * Pop: O(1) 121 | * Enqueue: O(1) 122 | * Dequeue: O(1) 123 | * *Unlike the array, these are truly constant, not amortized constant* 124 | 125 | ## Exercises 126 | 127 | * Required: [Valid Parens](https://leetcode.com/problems/valid-parentheses/) 128 | * Required: [Implement a Stack Using Queues](https://leetcode.com/problems/implement-stack-using-queues/description/) 129 | * Easier Bonus: [Trapping rain water](https://leetcode.com/problems/trapping-rain-water/description/) 130 | * Challenging Bonus: [Parsing Arithmetic](https://leetcode.com/problems/basic-calculator/description/) 131 | * Note: Students will have to use queues/stacks to implement BFS/DFS later in the course. Both challenge problems are better suited for stacks. 132 | -------------------------------------------------------------------------------- /lesson-plans/04-hash-tables.md: -------------------------------------------------------------------------------- 1 | # Hash Tables 2 | 3 | This lesson is about hashing and hash tables. 4 | 5 | ## Learning Objectives 6 | 7 | * Students can describe a Hash Function and a Hash Table 8 | * Students can identify common uses of hash tables 9 | * Students can classify the performance of hash table operations using big o 10 | * Students know that collisions will occur, and that there are many ways to handle them. 11 | * Students can specifically define chaining and linear probing (at a high level) 12 | 13 | ## Introduce & Motivate Hash Functions (5 minutes) 14 | 15 | * __Discuss: Whats the Big O of finding a specific *value* in Linked Lists and Arrays?__ 16 | * *Both are linear time—we may have to scan the whole list to find the value or prove that the value does not exist.* 17 | * *Lists are not optimized for this use case, hash tables are!* 18 | * Hash tables are key/value pair based storage. 19 | * Lists are too, but the key is always the index. 20 | * Hash tables use an array to store data and a hash function to create the index values for each key. 21 | * Hash tables can use any kind of data as a key, as long as the data can be run through a hash function. 22 | * Hash Tables will allow us to do O(1) search, insert, and remove by key 23 | * *This is amazing, emphasize that it's pretty amazing.* 24 | * __What is a hash function?__ 25 | * *There are many kinds, like cryptographic hash functions, but lets focus on hash functions for hash tables for now.* 26 | * *Hash functions transform input data into an integer, called the hash code* 27 | * *Hash functions are deterministic--same input always yields the same hash code* 28 | * __How is a hash function used by a hash table?__ 29 | * *We run the key through the hash function to produce an integer* 30 | * *That integer is used as the index in the underlying array* 31 | * It's worth noting that hashing is used in a lot of other places in CS. We're focused on hash tables for now, but the principles of hash functions are used for checksums, cryptocurrency, encryption, database passwords, and more... 32 | 33 | ## Hash Functions (10 minutes) 34 | 35 | * __What features do we want in a hash function?__ 36 | * *Speed, it should be easy to compute* 37 | * *Uniqueness, ideally every input would map to a unique output* 38 | * *Distribution, any output hash value should be equally likely. We want a uniform distribution of possible output values* 39 | * *Distribution matters because true uniqueness is impossible (pigeonhole principle)* 40 | * Pigeonhole principle: there is no way to put 5 things into 4 buckets without putting at least 2 things into one of those buckets. 41 | * In hash tables: we have to make an array with a specific size before we start hashing things, because our hash function needs to produce an index value that can be used in the underlying array. 42 | * Like dynamic arrays, we will have to resize as we add too many things to the table. 43 | * But hold that thought! 44 | * Example of a hash function, "Prime Modulo" strategy: 45 | ```js 46 | function hash(key) { 47 | return (key * 13) % sizeOfArray; 48 | } 49 | ``` 50 | * *Note that we assume the key is an integer in this case* 51 | * *It's possible to interpret any binary data as an integer so this isn't completely insane* 52 | * *Try not to focus on this for now if possible* 53 | * We pick some small prime number, multiply that by the key, then modulo by the size of the array. Then use that value as the index in the underlying array. 54 | * *You may have to describe the modulo operator at this time!* 55 | * The prime number helps to distribute the produced hash_codes uniformly within the range of the modulo. 56 | * There are number theory reasons for this, most critically if the modulus and the size of the hash table share a common factor, the distribution collapses. 57 | 58 | * __What if two keys hash to the same hash code, but we want to associate different values with those 2 keys?__ 59 | * *Because this is possible, we have to store the keys AND values in our hash tables* 60 | * *That's not ideal, but it's also not avoidable* 61 | * There are many other kinds of hash functions, exploring them could be interesting but not critical in understanding hash tables. Curious students can search for: 62 | * [Shift Multiply](https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/) 63 | * [Murmur](https://en.wikipedia.org/wiki/MurmurHash) 64 | * [Tabulation](https://en.wikipedia.org/wiki/Tabulation_hashing) 65 | * *This list is not exhaustive* 66 | 67 | 68 | ## Hash Tables -- Hash Value As An Index (15 minutes) 69 | 70 | * Hash Tables are typically backed by an array. 71 | * We use the hash function to create the index value from the key 72 | * We use modulo so everything is always inside the range of the array 73 | * __Exercise: using the prime modulo hash function above, simulate the insertion of the following 4 values into a hash table with an array size 8__ 74 | * 1, 3, 4, 9 (assume the keys and values are the same, so essentially we're using a "hash set") 75 | * *1 => 5* 76 | * *3 => 7* 77 | * *4 => 4* 78 | * *9 => 5* 79 | * __Uh oh, 1 and 9 both yield 5 as the index for the array. What can we do?__ 80 | * *Let the students squirm on this, some of them might invent chaining or linear probing on their own. They might also suggest resizing, which is a good solution!* 81 | 82 | ## Handling Collisions (20 minutes) 83 | 84 | * Collisions are inevitable, we have to do SOMETHING to handle them. 85 | * Simple solution: 86 | * Resize every time there is a single collision. 87 | * __Upside/downside?__ 88 | * *easy to implement* 89 | * *could lead to lots of wasted space* 90 | * *computationally wasteful as well, copying the array a lot* 91 | * Chaining: instead of storing just the keys/values in the array store a linked list 92 | * When a collision occurs, add the value at the end of the linked list! 93 | * __Can we still say that insert/lookup/remove are constant time?__ 94 | * *Yes and no—traversing the linked list now might take linear time!* 95 | * *But, we can force the array to resize when any chain gets longer than some fixed length. Now, it's still constant because we'll only traverse 3 hops or 5 hops (or whatever number you choose) before resizing. As long as that number is a constant that doesn't scale as the table gets bigger, we can still call our performance amortized constant time.* 96 | * Linear Probing: if a collision occurs, look in the slot to the right of the hash code. If it's empty store the data there. If it's full go one more to the right... repeat! 97 | * __Can we still say insert/lookup/remove are constant time?__ 98 | * *Just like above, yes and no. If we restrict the number of "probes" we do to a fixed constant before we resize, then we can still say amortized constant time* 99 | * __What if we have a collision, then remove the first of the two items that collided?__ 100 | * *Students should notice that we'll get to the hash-code, and it's empty. How do we know to look one to the right? Do we ALWAYS have to search the 3 or so neighboring locations?* 101 | * *No, but we do have to introduce the "tombstone", where we mark places where deletions occurred differently from places where no values have ever existed.* 102 | * __Pair/Share: Which of these two strategies is better?__ 103 | * *Loaded question, they have tradeoffs* 104 | * *Chaining is generally considered slower due to the bad cache utilization of linked lists* 105 | * *Linear probing is a bit harder to implement* 106 | * *Other strategies exist like Cuckoo Hashing!!* 107 | 108 | > Implementing one or both of these strategies would be nice bonus work, but not the focus of this class, so don't spend TOO much time talking about them unless you intend to make students implement a hash table from scratch! 109 | 110 | ## Performance of Hash Tables (5 min) 111 | 112 | * The efficiency of a hash table hinges on: 113 | * The quality of the hash function 114 | * Fewer collisions better, faster to compute better. 115 | * The collision handling strategy: 116 | * cache utilization, avoiding linear lookups 117 | * Another interesting measure is utilization 118 | * Most hash tables are about 50% empty space, to avoid collisions 119 | * Different collision handling strategies perform differently at higher/lower utilization rates. 120 | 121 | ## Exercises 122 | 123 | * [Detect anagrams](https://leetcode.com/problems/valid-anagram/description/) 124 | * [Top K items](https://leetcode.com/problems/top-k-frequent-elements/description/) 125 | * Bonus/Stretch: [Check Summing](https://github.com/Tebs-Lab/check-suming) 126 | -------------------------------------------------------------------------------- /lesson-plans/05-intro-to-trees.md: -------------------------------------------------------------------------------- 1 | # Intro To Trees 2 | 3 | This lesson is about trees. 4 | 5 | ## Learning Objectives 6 | 7 | * Students can define trees 8 | * Students can use important tree terminology 9 | * Students can identify various trees in computer programming 10 | * Students can implement BFS and DFS on trees 11 | 12 | ## Definitions (10 minutes) 13 | 14 | * __What is a tree?__ 15 | * *Define the "single path" restriction* 16 | * *Describe the "no cycles" property* 17 | * __Give some examples?__ 18 | * *The DOM* 19 | * *Filesystems* 20 | * *Org charts, family trees* 21 | * *"Search trees" such as BST, Trie, and B-Tree or others may come up* 22 | * __What are trees good at?__ 23 | * *Representing hierarchical data.* 24 | * Introduce trees and the important terminology: 25 | * node, root, leaf, child/children, parent 26 | 27 | ## Search and Traversal (30 minutes) 28 | 29 | * Search "stops" when you find the element. 30 | * Traversal explores the whole tree before stopping. 31 | * Both use essentially the same Algorithms 32 | * Two main kinds: DFS, BFS. 33 | * Go over an example of each using drawings. 34 | * __How can we implement these algorithms?__ 35 | * *Using stacks and queues of course!* 36 | * Put some pseudocode on the board: 37 | 38 | ```js 39 | function BFS(rootNode) { 40 | // Check that a root node exists. 41 | if (rootNode === undefined) { 42 | return; 43 | } 44 | 45 | let queue = []; 46 | queue.push(rootNode); 47 | 48 | while (queue.length > 0) { 49 | let currentNode = queue.shift(); 50 | console.log(currentNode.value); 51 | for(let child of currentNode.children) { 52 | queue.push(child); 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | * What about DFS? 59 | * *Just change the stack to a queue!* 60 | 61 | ```js 62 | function DFS(rootNode) { 63 | // Check that a root node exists. 64 | if (rootNode === undefined) { 65 | return; 66 | } 67 | 68 | let stack = []; 69 | queue.push(rootNode); 70 | 71 | while (queue.length > 0) { 72 | let currentNode = queue.pop(); 73 | console.log(currentNode.value); 74 | for(let child of currentNode.children) { 75 | queue.push(child); 76 | } 77 | } 78 | } 79 | ``` 80 | 81 | ## Exercise: Simulate BFS/DFS (15 minutes) 82 | 83 | * Consider this tree: 84 | 85 | ![simple-tree](resources/simple-tree.png) 86 | 87 | * __Simulate the execution of BFS and DFS on this tree__ 88 | * __Maintain the stack and queue explicitly!__ 89 | * __What order will the nodes be explored in?__ 90 | * *There are multiple right answers for both BFS and DFS depending on which order children are inserted into the stack/queue. Assuming child nodes are added left to right into the stack of queue:* 91 | * *BFS: 9, 4, 17, 3, 6, 22, 5, 7, 20* 92 | * *DFS: 9, 17, 22, 20, 4, 6, 7, 5, 3* 93 | 94 | ## Stacks, Recursion, and Trees 95 | 96 | * Trees are a recursive data structure, and so they lend themselves to being processed recursively. Check out this recursive implementation of DFS: 97 | 98 | ```js 99 | function DFS(node, val) { 100 | if(node.val === val) { 101 | return node; 102 | } 103 | 104 | for(let node of node.children) { 105 | let returnedBelow = DFS(node); 106 | 107 | if(returnedBelow !== undefined) { 108 | return returnedBelow; 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | * *Walk through the same tree, keeping track of the call stack explicitly* 115 | 116 | 117 | ## Exercises 118 | 119 | * [Path Sum](https://leetcode.com/problems/path-sum/description/) 120 | * [Path Sum II](https://leetcode.com/problems/path-sum-ii/description/) 121 | * Bonus: [Path Sum III](https://leetcode.com/problems/path-sum-iii/description/) 122 | * Bonus: [Duplicate Subtrees](https://leetcode.com/problems/find-duplicate-subtrees/description/) 123 | -------------------------------------------------------------------------------- /lesson-plans/06-special-trees.md: -------------------------------------------------------------------------------- 1 | # Special Trees 2 | 3 | This lesson is about a couple tree specializations; BSTs and Tries. 4 | 5 | ## Learning Objectives 6 | 7 | * Students can define binary search trees 8 | * Students can implement binary search on a BST 9 | * Students can identify the balance problem that AVL trees and Red-Black trees solve 10 | * Students can define Tries 11 | * Students can implement `valueExists` in a Trie 12 | 13 | ## Binary search trees (10 minutes) 14 | 15 | * __What is a binary search tree?__ 16 | * *Binary: every node has at most 2 children* 17 | * *Search property: Left always less than node, right always more than node, no duplicates.* 18 | * __What is the purpose of a BST?__ 19 | * *Provide an efficiently search-able hierarchy for representing sorted data.* 20 | * *Fundamentally, BSTs and Hash Tables solve similar problems: making it easier to find values within a dataset* 21 | * *Databases use B-Trees and Hash tables extensively as indexing strategies (B-Trees are an extension of the BST concept)* 22 | * __everyone draws__: a valid binary search tree with at least 10 nodes. 23 | * *Invite one student to draw theirs on the board* 24 | * __Exercise: Write pseudocode for binary search on a BST.__ 25 | * *The recursive version is super easy:* 26 | 27 | ```js 28 | function binarySearch(node, value) { 29 | if(node.value === value) { 30 | return node; 31 | } 32 | 33 | if(value < node.value && node.left !== undefined) { 34 | return binarySearch(node.left, value); 35 | } 36 | else if(value > node.value && node.right !== undefined) { 37 | return binarySearch(node.right, value); 38 | } 39 | 40 | // Otherwise the value is not in the tree, return undefined. 41 | } 42 | ``` 43 | 44 | * __Exercise: Implement an "In order traversal" of a BST to print the values from smallest to largest__: 45 | * *Once again, recursion is really the easiest way to do this* 46 | 47 | ```js 48 | function inOrderTraversal(node) { 49 | if(node.left !== undefined) { 50 | return inOrderTraversal(node.left); 51 | } 52 | 53 | console.log(node.value) 54 | 55 | if(node.right !== undefined) { 56 | return inOrderTraversal(node.right); 57 | } 58 | } 59 | ``` 60 | 61 | ## Problems with BST, and BigO (10 minutes) 62 | 63 | * __Instructor draws a sorted linked list__ 64 | * __discuss is this a binary search tree?__ 65 | * *Yes, it totally is!* 66 | * __whats the big O of searching in this BST?__ 67 | * *Linear time :(* 68 | * __Does that jive with what we learned about binary search??__ 69 | * *Not really...* 70 | * Being properly "balanced" is crucial to the value of a BST. 71 | * There are two popular kinds of "self balancing" binary search trees called AVL trees and Red Black Trees 72 | * Knowing exactly how they work is not really that important, but they are interesting and cool data structures so feel free to look them up. 73 | * Because those trees are always balanced, they always have the O(log n) search time we're expecting. 74 | 75 | ## Tries (15 minutes) 76 | 77 | * BST's are trees with specially ordered data that make it quick to search for numbers. 78 | * Tries are trees with specially ordered data that make it quick to search for words. 79 | * Each node is a letter, and each edge denotes the next letter in a word. 80 | * *Draw an example with words: cat, came, bat, bar, barge* 81 | 82 | ![example trie](resources/Trie.png) 83 | * *The paths to green nodes are words. The paths to red nodes don't represent words just intermediate states* 84 | * Notice that Tries are also effiecnt at storing data, because each node can be reused by many words. 85 | * Give the example of an english language dictionary that is just a list of all the words in order vs the trie version. 86 | * e.g. every word starting with A gets a unique copy of A in the first version. In the trie all words starting with A share the A node that is a child of the root. 87 | * This is a very popular data structure for auto-complete as well as spell-check algorithms! 88 | * __Exercise: Pseudocode isWord for a Trie__ 89 | 90 | ```js 91 | function isWord(trieNode, word) { 92 | if(word === '') { 93 | return true; 94 | } 95 | 96 | let letter = word[0]; 97 | let nextNode = trieNode.children[letter]; 98 | if(nextNode !== undefined){ 99 | return isWord(nextNode, word.slice(1)); 100 | } 101 | 102 | return false; 103 | } 104 | ``` 105 | 106 | ## Exercises 107 | 108 | * [Validate a BST](https://leetcode.com/problems/validate-binary-search-tree/description/) 109 | * [Implement a prefix tree AKA a trie](https://leetcode.com/problems/implement-trie-prefix-tree/description/) 110 | -------------------------------------------------------------------------------- /lesson-plans/07-intro-to-graphs.md: -------------------------------------------------------------------------------- 1 | # Intro To Graphs 2 | 3 | This lesson is about graphs. 4 | 5 | ## Learning Objectives 6 | 7 | * Students can define graphs and use graph terminology 8 | * node/vertex, edge, directed/undirected, weighted/unweighted 9 | * Students can simulate BFS and DFS over graphs on whiteboard/paper 10 | * Students can write pseudocode for BFS and DFS on graphs 11 | 12 | ## Intro to graphs (10-15 min) 13 | 14 | * Graphs are an incredible way to model relationships between things. 15 | * Define critical graph terminology: 16 | * node/vertex -- the basic unit in a graph, the things being modeled. 17 | * edge -- a connection between two nodes 18 | * directed/undirected 19 | * weighted/unweighted 20 | * And more: nodes can have any kinds properties, edges can be labeled with more than numeric values, a single graph could have many kinds of edges... 21 | * nearly infinite flexibility exists, but restrictions are still powerful! 22 | * Define a process for modeling a problem using graph theory 23 | * What are the nodes? 24 | * Do they need any properties? what properties? 25 | * What are the edges? 26 | * Directed/undirected? 27 | * weighted/unweighted? 28 | * Do they need any other properties? 29 | * What feature of the graph would be a solution to this problem? (often a path) 30 | 31 | ## Practice modeling things with graphs (15-20 minutes) 32 | 33 | * __In Pairs, model each of the following as a graph and draw an example?__ 34 | * *Give students 5 minutes for each one, then discuss them as a class.* 35 | * *Answer the questions from the process above: what are the nodes, what are the edges, directed or not, weighted or not?* 36 | * *There might be lots of permutations for each of these that is "correct" so entertain multiple answers!* 37 | * *Facebook* 38 | * *Twitter* 39 | * *The Internet as a set of webpages with links* 40 | * *The Internet as a set of physically connected computers* 41 | 42 | ## Define BFS and DFS for Graphs. 43 | 44 | * The main difference from trees is that we need an explored list. 45 | * __Why do we need to keep track of where we've been in a graph but not a tree?__ 46 | * *In a tree there is exactly one path between any two nodes or those nodes aren't connected* 47 | * *In a graph, there might be several ways, including cycles. If we don't use an explored list we could search in a cycle forever.* 48 | * Draw a simple graph such as: 49 | 50 | ![simple-graph](resources/SimpleGraph.png) 51 | 52 | * __Exercise: determine which order the nodes will be searched in both BFS and DFS starting at A with a goal of F__ 53 | * *Ties might be broken arbitrarily so there are multiple correct answers* 54 | * *Assuming ties are broken by first exploring the alphabetically first node:* 55 | * *BFS: B, C, D, E, F* 56 | * *DFS: B, D, E, F* 57 | * __What PATH would be returned by BFS and DFS from A to F?__ 58 | * *BFS: A->B->D->F* 59 | * *DFS: A->B->D->E->F* 60 | 61 | ## Psuedocode for BFS/DFS graphs 62 | 63 | ```js 64 | function BFS(startNode, goalNode) { 65 | let frontier = []; 66 | let exploredList = new Set(); 67 | frontier.push(startNode); 68 | 69 | while (frontier.length > 0) { 70 | let currentNode = frontier.shift(); 71 | 72 | // check if it's the goal 73 | if(currentNode === goalNode) { 74 | return true; // found the node 75 | } 76 | 77 | // If we already explored this node, don't add it's children to the frontier 78 | if(exploredList.contains(currentNode)) { 79 | continue; 80 | } 81 | 82 | // Explore the node by adding all it's children 83 | for(let neighbor of currentNode.neighbors) { 84 | frontier.push(neighbor); 85 | } 86 | 87 | // Mark the node explored 88 | exploredList.add(currentNode); 89 | } 90 | 91 | // Never found a path to the goal/ 92 | return false; 93 | } 94 | ``` 95 | 96 | * DFS uses the frontier as a stack instead of a queue, the only difference is that we pop instead of shift on the line `currentNode = frontier.shift()` 97 | * __Exercise: repeat the BFS and DFS from above, but this time explicitly keep track of the frontier and the explored list.__ 98 | * *Follow the pseudocode carefully, note how in DFS the cycle B->D->E->D is avoided by the explored list.* 99 | * __Okay, now the tricky part: our current implementation does not return the path, it just returns true or false indicating the existance of a path. How could we change our algorithm to return the path?__ 100 | * *Either, in the explored list keep track of the 'parent' node when we explore each node* 101 | * *Or maintain a growing list of the path so far for each new node we visit* 102 | * *Next class we'll revisit and look at code that does this* 103 | * __Does DFS return the shortest path?__ 104 | * *It might, but it isn't sure to* 105 | * __What about BFS?__ 106 | * *Yes, always* 107 | 108 | ## Exercises 109 | 110 | Model the following problems as graphs: 111 | 112 | * What are the nodes? 113 | * What are the edges? 114 | * Weighted/unweighted? 115 | * Directed/undirected? 116 | * Define a path in the graph that indicates a solution to the problem. 117 | 118 | ### Word Ladder Problem 119 | 120 | A word ladder is a way to connect 2 words. If you can take a word, changing one letter at a time, turn it into another word, and every intermediate step is still a word, those two words have a word ladder. For example, a ladder can be built between cat and dog: 121 | 122 | ``` 123 | cat 124 | cot 125 | dot 126 | dog 127 | ``` 128 | 129 | Three letter words are pretty easy, four letter words are harder. 130 | 131 | Can you create an algorithm, using you graph library, that can determine if two words have a ladder; and if so return the series of words that makeup the word ladder, in order. 132 | 133 | * *Nodes are words* 134 | * *Edges exist between words that are exactly one letter different from each other.* 135 | * *Edges are undirected, being off by one letter is a mutual relationship.* 136 | * *Edges are unweighted, the relative cost of "choosing" any word is the same as any other word.* 137 | * *The existence of any path between the starting world and the ending word indicates a ladder can be built.* 138 | 139 | ### Pancake Flipping 140 | 141 | You have a stack of pancakes with different widths. They are currently in a strange order, but you'd like to get them ordered from fattest to thinnest in a pancake pyramid. 142 | 143 | Unfortunately, you have very limited grill space so the only thing you can do is insert your spatula somewhere in the stack and flip the pancakes on top of your spatula over. Whatever pancake used to be on the top of the stack will now be wherever you inserted your spatula, and the pancake that was directly on top of your spatula is now on the top of the stack. Everything below your spatula remains unchanged. 144 | 145 | What is the fewest number of flips you can make in order to get the pancakes in order? 146 | 147 | * *This is another state machine* 148 | * *Nodes are the current ordering of the pancakes* 149 | * *Edges represent all of the places you can put the spatula and flip* 150 | * *edges are undirected, any flip can be reversed* 151 | * *edges are unweighted, we care about the number of flips not anything else* 152 | * *WE have to use BFS because the shortest path between the starting state and the sorted state is the answer. There may be several ways to do it and even ties for the fastest way* 153 | -------------------------------------------------------------------------------- /lesson-plans/08-graph-implementations.md: -------------------------------------------------------------------------------- 1 | # Graph Representations/Implementations 2 | 3 | This class is about how to implement a graph API as well as how to implement BFS and DFS over a graph API. 4 | 5 | # Objectives 6 | 7 | * Students can describe how to use an adjacency list to represent a graph. 8 | * Students can describe how to use nested hash tables to implement a graph. 9 | * Students can implement BFS, DFS algorithm based on a graph API. 10 | 11 | ## Representing Graphs (20-30 min) 12 | 13 | * So, we already know what graphs are, abstractly. And we have already seen how they can be useful in modeling a lot of different kinds of problems. But how do we build one of these things? 14 | * __What should our Graph API (or ADT) Be able to do?__ 15 | * *Store nodes and edges somehow* 16 | * *Make new nodes* 17 | * *Create edges between nodes* 18 | * *Fetch neigbors given a node* 19 | * __Pair/Share: Define a set of member functions for a minimal Graph API.__ 20 | * *Absolute minimum: createNode(nodeName), createEdge(nodeNameA, nodeNameB), getNodes(), getNeighbors(nodeName)* 21 | * *If we want edge weights, createEdge needs to take a weight.* 22 | * The essentials are the ability to __create and retrieve__ the relationships between things. 23 | * __What are some ways we could store the nodes and their relationships?__ 24 | * *There are LOTS of answers students could give... entertain those ideas!* 25 | * *If students come up with an Edge List, Adjacency List, Adj. Matrix, or something else with a name, congratulate them for inventing something with a name.* 26 | * *Use probing questions if the students are stumped:* 27 | * __What data-type should the nodes be? Do we need a class for that?__ 28 | * *We do NOT need classes, though it is a sane choice if the nodes have many properties* 29 | * *If we use a list, whats good/bad about that?* 30 | * *If we use a hash table, what's good/bad about that?* 31 | * __How should we represent edges? Do we need a class for that?__ 32 | * *Same, a class might be appropriate if our edges get pretty compelex* 33 | * *Completely overkill if the edge is just an unweighted edge with no properties.* 34 | * __Should the Graph or the individual nodes keep track of the neighbors?__ 35 | * *This is a bit philosophical, either can be made to work* 36 | * *Try to explore some tradeoffs* 37 | * *The GRAPH is really the unit of interest, so it should be in control of the encapsulated data* 38 | * *On the other hand, it's very easy to think of Nodes HAVING edges, rather than the graph having edges* 39 | * At this point, I want to compare two implementations of a directed, unweighted graphs 40 | * One using 2 lists (adjacency list) 41 | * One using 2 hash tables (adjacency hash) 42 | * This is far from an exhaustive set of ways to implement such tables. 43 | * for both, nodes are just a name which is a string. 44 | * With the list: 45 | * Graph contains a list of all the nodes (list of strings) 46 | * Graph contains a parallel list of lists, where the lists contain all the neighbors of the node they represent. 47 | * For the hashes 48 | * Graph contains a hash table mapping node names to HASH MAPS which represent that nodes neighbors! 49 | 50 | ![simple-graph](resources/SimpleGraph.png) 51 | 52 | ```js 53 | nodes = ['A', 'B', 'C', 'D', 'E', 'F'] 54 | edges = [ 55 | ['B', 'C'], // neighbors of A 56 | ['D'], // neighbors of B 57 | ['D'], // neighbors of C 58 | ['E', 'F'], // neighbors of D 59 | ['B', 'F'], // neighbors of E 60 | [] // F has no neighbors 61 | ] 62 | ``` 63 | 64 | ```js 65 | nodes_and_edges = { 66 | // A and its neighbors 67 | 'A': { 68 | 'B': true, 69 | 'C': true 70 | }, 71 | 72 | // B and its neighbors 73 | 'B': { 74 | 'D': true 75 | }, 76 | 77 | // C and its neighbors 78 | 'C': { 79 | 'D': true 80 | }, 81 | 82 | // D and its neighbors 83 | 'D': { 84 | 'E': true, 85 | 'F': true 86 | }, 87 | 88 | // E and its neighbors 89 | 'E': { 90 | 'B': true, 91 | 'F': true 92 | }, 93 | 94 | // F has no neighbors 95 | 'F': {} 96 | } 97 | ``` 98 | 99 | * __Exercise: Compare the Big O cost of these operations on each of our two graphs:__ 100 | * __Determine if a node is in the graph__ 101 | * *Lists: Linear in terms of the number of nodes, scan the node list, O(n)* 102 | * *Graphs: Constant time hash lookup, O(1)* 103 | * __Determine if two nodes are neighbors__ 104 | * *Lists: O(n+e) -- first we scan the list for the node of interest, then we do a constant time lookup based on the index at which we found the node, but we have to perform a linear scan there as well to find if the neighbor is there (which might be all the edges in the worst case).* 105 | * *Hashes: O(1) -- two constant time operations. First, lookup the first node in the main hash, then lookup the neighbor in the nested hash.* 106 | * __Can you think of a way in which the two lists is better?__ 107 | * *The lists take up less memory* 108 | * *If we're iterating over the neighbors a lot, the lists will be more efficient during the iteration process.* 109 | * *But nested hash tables is honestly pretty good!* 110 | 111 | 112 | ## Search Implementations (20 min) 113 | 114 | * The pseudocode was presented last class, students should be ready to look at some real code based on a graph API. 115 | * Walk through this code with the students and let them ask questions. 116 | * A major point of interest is that the frontier has the "reached from" node as well as the current node so that we can rebuild the path from the explored list at the end of the search. 117 | * Related, the explored list is a map of nodeNames to nodeNames where the key is the node of interest, and the value is the "parent" or "reach from" node along the path found by search. 118 | 119 | ```js 120 | function BFS(graph, startNode, goalNode) { 121 | let frontier = []; 122 | let exploredList = {}; 123 | frontier.push([startNode, null]); 124 | 125 | while (frontier.length > 0) { 126 | let [currentNode, reachedFrom] = frontier.shift(); 127 | 128 | // If it's the goal, return the path! 129 | if(currentNode === goalNode) { 130 | return extractPath(exploredList, goalNode) 131 | } 132 | 133 | // If we've seen this node already, ignore it! 134 | else if(exploredList[currentNode] !== undefined) { 135 | continue; 136 | } 137 | 138 | // Add all the neighbors to the frontier 139 | for(let neighbor of graph.getNeighbors(currentNode)) { 140 | frontier.push([neighbor, currentNode]); 141 | } 142 | 143 | exploredList[currentNode] = reachedFrom; 144 | } 145 | 146 | return null; // Indicating no path exists 147 | } 148 | 149 | // Literally the only difference is shift changed to pop! 150 | function DFS(graph, startNode, goalNode) { 151 | let frontier = []; 152 | let exploredList = {}; 153 | frontier.push([startNode, null]); 154 | 155 | while (frontier.length > 0) { 156 | let [currentNode, reachedFrom] = frontier.shift(); 157 | 158 | // If it's the goal, return the path! 159 | if(currentNode === goalNode) { 160 | return extractPath(exploredList, goalNode) 161 | } 162 | 163 | // If we've seen this node already, ignore it! 164 | else if(exploredList[currentNode] !== undefined) { 165 | continue; 166 | } 167 | 168 | // Add all the neighbors to the frontier 169 | for(let neighbor of graph.getNeighbors(currentNode)) { 170 | frontier.pop([neighbor, currentNode]); 171 | } 172 | 173 | exploredList[currentNode] = reachedFrom; 174 | } 175 | 176 | return null; // Indicating no path exists 177 | } 178 | 179 | // Given an explored list as described above, get the path from start to goal. 180 | function extractPath(exploredList, goalNode) { 181 | let currentItr = currentNode; 182 | let shortestPath = []; 183 | while(currentItr !== null) { 184 | shortestPath.push(currentItr); 185 | currentItr = exploredList[currentItr]; 186 | } 187 | 188 | return shortestPath.reverse(); 189 | } 190 | ``` 191 | 192 | #### Exercises 193 | 194 | * [Word Ladder](https://leetcode.com/problems/word-ladder/description/) 195 | * [Number of Islands](https://leetcode.com/problems/number-of-islands/description/) 196 | * Bonus: [Pacific Atlantic Water Flow](https://leetcode.com/problems/pacific-atlantic-water-flow/description/) 197 | -------------------------------------------------------------------------------- /lesson-plans/resources/Array-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tebs-Lab/intro-to-datastructures-algorithms/7646da19d5b59b9751016994b9ae2faa050765c1/lesson-plans/resources/Array-list.png -------------------------------------------------------------------------------- /lesson-plans/resources/SimpleGraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tebs-Lab/intro-to-datastructures-algorithms/7646da19d5b59b9751016994b9ae2faa050765c1/lesson-plans/resources/SimpleGraph.png -------------------------------------------------------------------------------- /lesson-plans/resources/Trie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tebs-Lab/intro-to-datastructures-algorithms/7646da19d5b59b9751016994b9ae2faa050765c1/lesson-plans/resources/Trie.png -------------------------------------------------------------------------------- /lesson-plans/resources/big-o-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tebs-Lab/intro-to-datastructures-algorithms/7646da19d5b59b9751016994b9ae2faa050765c1/lesson-plans/resources/big-o-graph.png -------------------------------------------------------------------------------- /lesson-plans/resources/simple-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tebs-Lab/intro-to-datastructures-algorithms/7646da19d5b59b9751016994b9ae2faa050765c1/lesson-plans/resources/simple-tree.png -------------------------------------------------------------------------------- /outline.md: -------------------------------------------------------------------------------- 1 | # Computer Science Foundations -- Data Structures Short Course 2 | 3 | This course is designed to teach students the fundamentals of data structures. Students are expected to already speak a programming language, and have surely encountered some of these data structures in the wild already. Specifically, this course examines the following data structures: 4 | 5 | * Arrays 6 | * Linked Lists 7 | * Hash Tables 8 | * Trees 9 | * Graphs 10 | 11 | In their general form, these data structures underlie nearly all of computer programming. Complex data structures tend to be specializations of the general form of these structures, combinations of them, or both. 12 | 13 | Conceptual fluency in these data structures provide programmers with the arsenal to tackle a huge range of problems, and equip programmers to understand domain specific specializations of these general concepts. Perhaps more importantly, understanding how these tools helps engineers read, write, debug, and understand code the encounter in the wild. 14 | 15 | From cryptography, to databases, to machine learning, and beyond a strong foundation in data structures equips engineers to move around within the vast computer programming landscape. 16 | 17 | Accompanying each of these data structures is a small selection of algorithms for manipulating these data structures, specializations of the data structure, or algorithms that rely on these data structures. While the focus of this course is on conceptual fluency -- students will be asked to implement simple versions of some of these data structures in service of that goal. 18 | 19 | ## Class 1: Introduction to Complexity & Big O Notation 20 | 21 | This lesson should give students a big picture view about why algorithms and computer science is important, as well as delve into the specifics of Big O. 22 | 23 | ### Objectives 24 | 25 | * Students can describe how they might interact with data structures and algorithms in their future careers. 26 | * Students know that algorithms are measured in terms of their input. 27 | * Students can identify important complexity classes. 28 | * Students can classify provided code or descriptions of an algorithm into the proper complexity class. 29 | 30 | ## Class 2: Linear Data Storage, Arrays and Linked Lists 31 | 32 | This lesson teaches students about Arrays (as in C arrays) and Linked Lists. 33 | 34 | ### Objectives 35 | 36 | * Students can describe the difference between an Abstract Data Type and an implementation 37 | * Students can describe the difference between an Array and a Linked List 38 | * Students can compare and contrast the speed of operations in Arrays and Linked Lists 39 | * Students can implement and analyze a queue or stack backed by 40 | * A Linked List 41 | * An Array 42 | 43 | ## Class 3: Specialized Linear Stores -- Stacks & Queues 44 | 45 | In this lesson students will compare the implementations of stacks and queues based on their backing data structure (array or linked list) and apply these data structures to solve problems. 46 | 47 | ### Objectives 48 | 49 | * Students can describe stacks and queues as abstract data types 50 | * Especially Last In First Out (LIFO) vs First In First Out (FIFO) properties 51 | * Students can implement and analyze a queue or stack backed by: 52 | * A Linked List 53 | * An Array 54 | * Students can use stacks and queues to solve problems. 55 | 56 | ## Class 4: Non-Lienar, Non-Hierarchical Storage -- Hashing and Hash Tables 57 | 58 | This lesson is about hashing and hash tables. 59 | 60 | ## Objectives 61 | 62 | * Students can describe a Hash Function and a Hash Table 63 | * Students can identify common uses of hash tables 64 | * Students can classify the performance of hash table operations using big o 65 | * Students know that collisions will occur, and that there are many ways to handle them. 66 | * Students can specifically define chaining and linear probing (at a high level) 67 | 68 | This lesson is about hashing and hash tables. 69 | 70 | ## Class 5: Hierarchical Storage -- Introduction to Trees 71 | 72 | This lesson is about trees. 73 | 74 | ## Objectives 75 | 76 | * Students can define trees 77 | * Students can use important tree terminology (root, leaf, child...) 78 | * Students can identify various trees in computer programming 79 | * Students can implement BFS and DFS on trees 80 | 81 | ## Class 6: Special Trees 82 | 83 | This lesson is about a couple tree specializations; BSTs and Tries. 84 | 85 | ### Objectives 86 | 87 | * Students can define binary search trees 88 | * Students can implement binary search on a BST 89 | * Students can identify the balance problem that AVL trees and Red-Black trees solve 90 | * Students can define Tries 91 | * Students can implement `valueExists` in a Trie 92 | 93 | ## Class 7: Storing Relational Data -- Introduction To Graphs 94 | 95 | This lesson is about graphs. 96 | 97 | ### Objectives 98 | 99 | * Students can define graphs and use graph terminology 100 | * node/vertex, edge, directed/undirected, weighted/unweighted 101 | * Students can simulate BFS and DFS over graphs on whiteboard/paper 102 | * Students can write pseudocode for BFS and DFS on graphs 103 | 104 | ## Class 8: Representing Graphs and Graph APIs 105 | 106 | This class is about how to implement a graph API as well as how to implement BFS and DFS algorithm over a graph API. 107 | 108 | # Objectives 109 | 110 | * Students can describe how to use an adjacency list to represent a graph. 111 | * Students can describe how to use nested hash tables to implement a graph. 112 | * Students can implement BFS, DFS algorithm based on a graph API. 113 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Teb's Computer Science Foundations Short Course 2 | 3 | This repository contains curriculum materials designed to help schools, educators, companies, non-profits, or anyone at all teach an introductory computer science course. The materials here target concepts at the very core of computer science, and are focused primarily on introducing data structures and algorithms to students who have already been learning to program. 4 | 5 | The materials here are intended for use in a classroom setting. Students who wish to self-study the same material may find the lesson plans, suggested exercises, and solutions useful -- but to get the most out of these materials they should be presented by instructor who already has a strong understanding of data structures and algorithms. 6 | 7 | All of the materials in this repository have been released to the public domain. You may use them for any purpose you wish. If you want to support me, and enable me to create more public domain resources like these, consider [subscribing to my Patreon profile](https://www.patreon.com/tebslab). Additionally, if you want to contact me regarding these materials, please email me at teb@tebs-lab.com. 8 | 9 | ## Using These Resources 10 | 11 | The materials are designed to be used in a classroom setting, and are divided into two main types: lesson plans, and work for the students to complete. Each big picture topic in the syllabus has a lesson plan and *at least* one exercise. Pedagogically, these materials are written from a philosophy that students learn best by actively engaging with the topics at hand. 12 | 13 | ### Lesson Plans 14 | 15 | The lesson plans are written with the socratic method in mind, and with an understanding that no plan survives contact with a classroom. Each lesson plan has a set of stated objectives -- things that the students should know, or be able to do, after the end of each lesson. Following the objectives is a set of notes whose goals are: 16 | 17 | * To help remind the instructor about the topics and objectives 18 | * Provide a scaffold and order for each period of direct instruction 19 | * Provide a series of questions to pose to the students to challenge them to think and check their understanding 20 | * Give a few analogies and examples that I have found powerful in my own teaching 21 | * Provide sign posts to advanced concepts that are connected with these foundational topics 22 | 23 | When I teach these classes myself, I rarely hit every bullet point in the lesson plans. Furthermore, the students will ask insightful questions that take the class down paths not covered in the lesson plans. These student-led tangents are invaluable to student learning, try not to think of these documents as a restrictive set of instructions for the lesson. Instead, I hope these materials empower you to create high quality classroom experiences that speak to your own strengths as an instructor. 24 | 25 | I use the whiteboard, cold calling, pair-share, and socratic discussions extensively when teaching with these materials. Anything in **bold** is intended to be posed as a question to the students. Use your instincts and intuition about what strategies will get your students most engaged with the questions. Inventing object lessons, organizing mini debates, and encouraging open ended discussion are examples of ways to extend these lesson plans. Some of these lesson plans have suggested activities along these lines that I have found to be powerful in my own teaching. 26 | 27 | ### Problems For Students 28 | 29 | Direct instruction can be helpful for students, especially when students are being introduced to new and abstract topics such as Big O notation and complexity theory. However, even the best socratic discussion will be in vein unless students put what they learned into practice. Every lesson plan also has a set of suggested exercises -- mostly programming problems hosted on websites such as leetcode.com. I have selected these problems to be relevant to the lesson objectives and to be appropriately difficult, with stretch/bonus problems for students who excel and want to dive deeper. 30 | 31 | In my own teaching, I strive to get students working on problems early and often. Some of the most valuable direct instruction periods in my classroom have been spent revisiting homework problems, and addressing the most common mistakes/points of confusion as well as examining a particularly good solution. 32 | --------------------------------------------------------------------------------