diff --git a/AVL Tree/README.markdown b/AVL Tree/README.markdown index 9e3a2cdc6..744d9a272 100644 --- a/AVL Tree/README.markdown +++ b/AVL Tree/README.markdown @@ -8,7 +8,7 @@ This is an example of an unbalanced tree: ![Unbalanced tree](Images/Unbalanced.png) -All the children are in the left branch and none are in the right. This is essentially the same as a [linked list](../Linked List/) and as a result, searching takes **O(n)** time instead of the much faster **O(log n)** that you'd expect from a binary search tree. +All the children are in the left branch and none are in the right. This is essentially the same as a [linked list](../Linked List/). As a result, searching takes **O(n)** time instead of the much faster **O(log n)** that you'd expect from a binary search tree. A balanced version of that tree would look like this: @@ -16,7 +16,7 @@ A balanced version of that tree would look like this: One way to make the binary search tree balanced is to insert the nodes in a totally random order. But that doesn't guarantee success, nor is it always practical. -The other solution is to use a *self-balancing* binary tree. This type of data structure adjusts the tree to keep it balanced after you insert or delete nodes. The height of such a tree is guaranteed to be *log(n)* where *n* is the number nodes. On a balanced tree all insert, remove, and search operations take **O(log n)** time. That means fast. ;-) +The other solution is to use a *self-balancing* binary tree. This type of data structure adjusts the tree to keep it balanced after you insert or delete nodes. The height of such a tree is guaranteed to be *log(n)* where *n* is the number nodes. On a balanced tree all insert, remove, and search operations take only **O(log n)** time. That means fast. ;-) ## Introducing the AVL tree @@ -32,7 +32,7 @@ As mentioned, in an AVL tree a node is balanced if its left and right subtree ha ![Balanced trees](Images/BalanceOK.png) -But these are trees that are unbalanced, because the height of the left subtree is too large compared to the right subtree: +But the following are trees that are unbalanced, because the height of the left subtree is too large compared to the right subtree: ![Unbalanced trees](Images/BalanceNotOK.png) @@ -52,7 +52,7 @@ Insertion never needs more than 2 rotations. Removal might require up to *log(n) ## The code -Most of the code in [AVLTree.swift](AVLTree.swift) is just regular [binary search tree](../Binary Search Tree/) stuff. You'll find this in any implementation of a binary search tree. For example, searching the tree is exactly the same. The only things that an AVL tree does slightly differently is inserting and deleting the nodes. +Most of the code in [AVLTree.swift](AVLTree.swift) is just regular [binary search tree](../Binary Search Tree/) stuff. You'll find this in any implementation of a binary search tree. For example, searching the tree is exactly the same. The only things that an AVL tree does slightly differently are inserting and deleting the nodes. > **Note:** If you're a bit fuzzy on the regular operations of a binary search tree, I suggest you [catch up on those first](../Binary Search Tree/). It will make the rest of the AVL tree easier to understand. @@ -66,6 +66,6 @@ The interesting bits are in the following methods: [AVL tree on Wikipedia](https://en.wikipedia.org/wiki/AVL_tree) -AVL tree was the first self-balancing binary tree. These days, the [red-black tree](../Red-Black Tree/) seems to be more common. +AVL tree was the first self-balancing binary tree. These days, the [red-black tree](../Red-Black Tree/) seems to be more popular. *Written for Swift Algorithm Club by Mike Taghavi and Matthijs Hollemans* diff --git a/Algorithm Design.markdown b/Algorithm Design.markdown index d9376f82e..23f8ad70e 100644 --- a/Algorithm Design.markdown +++ b/Algorithm Design.markdown @@ -4,9 +4,9 @@ What to do when you're faced with a new problem and you need to find an algorith ### Is it similar to another problem? -One thing I like about [The Algorithm Design Manual](http://www.algorist.com) by Steven Skiena is that it includes a catalog of problems and solutions you can try. +If you can frame your problem in terms of another, more general problem, then you might be able to use an existing algorithm. Why reinvent the wheel? -If you can frame your problem in terms of another, more general problem, then you might be able to use an existing algorithm. +One thing I like about [The Algorithm Design Manual](http://www.algorist.com) by Steven Skiena is that it includes a catalog of problems and solutions you can try. (See also his [algorithm repository](http://www3.cs.stonybrook.edu/~algorith/).) ### It's OK to start with brute force @@ -14,7 +14,7 @@ Naive, brute force solutions are often too slow for practical use but they're a Once you have a brute force implementation you can use that to verify that any improvements you come up with are correct. -And if you only work with small datasets, then a brute force approach may actually be good enough on its own. +And if you only work with small datasets, then a brute force approach may actually be good enough on its own. Don't fall into the trap of premature optimization! ### Divide and conquer diff --git a/Array2D/README.markdown b/Array2D/README.markdown index 32cde4d67..e73d453a6 100644 --- a/Array2D/README.markdown +++ b/Array2D/README.markdown @@ -1,6 +1,6 @@ # Array2D -In C and Objective-C you can write this, +In C and Objective-C you can write the following line, int cookies[9][7]; @@ -61,7 +61,7 @@ var threeDimensions = dim(2, dim(3, dim(4, 0))) The downside of using multi-dimensional arrays in this fashion -- actually, multiple nested arrays -- is that it's easy to lose track of what dimension represents what. -So instead let's create our own type that acts like a 2-D array and that is more convenient to use. Here it is: +So instead let's create our own type that acts like a 2-D array and that is more convenient to use. Here it is, short and sweet: ```swift public struct Array2D { @@ -100,8 +100,14 @@ Thanks to the `subscript` function, you can do the following to retrieve an obje let myCookie = cookies[column, row] ``` +Or change it: + +```swift +cookies[column, row] = newCookie +``` + Internally, `Array2D` uses a single one-dimensional array to store the data. The index of an object in that array is given by `(row x numberOfColumns) + column`. But as a user of `Array2D` you don't have to worry about that; you only have to think in terms of "column" and "row", and let `Array2D` figure out the details for you. That's the advantage of wrapping primitive types into a wrapper class or struct. And that's all there is to it. -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Binary Search Tree/README.markdown b/Binary Search Tree/README.markdown index f08cc0ad1..25d56803b 100644 --- a/Binary Search Tree/README.markdown +++ b/Binary Search Tree/README.markdown @@ -1,12 +1,12 @@ # Binary Search Tree (BST) -A binary search tree is a special kind of [binary tree](../Binary Tree/) (a tree in which a node only has two children) that performs insertions and deletions such that the tree is always sorted. +A binary search tree is a special kind of [binary tree](../Binary Tree/) (a tree in which each node has at most two children) that performs insertions and deletions such that the tree is always sorted. If you don't know what a tree is or what it is for, then [read this first](../Tree/). ## "Always sorted" property -This is an example of a valid binary search tree: +Here is an example of a valid binary search tree: ![A binary search tree](Images/Tree1.png) @@ -33,7 +33,7 @@ There is always only one possible place where the new element can be inserted in > **Note:** The *height* of a node is the number of steps it takes to go from that node to its lowest leaf. The height of the entire tree is the distance from the root to the lowest leaf. Many of the operations on a binary search tree are expressed in terms of the tree's height. -By following this simple rule -- smaller values on the left, larger values on the right -- we keep the tree sorted in a way that whenever we query it, we can quickly check if a value is in the tree. +By following this simple rule -- smaller values on the left, larger values on the right -- we keep the tree sorted in a way such that whenever we query it, we can quickly check if a value is in the tree. ## Searching the tree @@ -49,7 +49,7 @@ If we were looking for the value `5` in the example, it would go as follows: ![Searching the tree](Images/Searching.png) -Thanks to the structure of the tree, searching is really fast. It runs in **O(h)** time. If you have a tree with a million nodes, it only takes about 20 steps to find anything in this tree. (The idea is very similar to [binary search](../Binary Search) in an array.) +Thanks to the structure of the tree, searching is really fast. It runs in **O(h)** time. If you have a well-balanced tree with a million nodes, it only takes about 20 steps to find anything in this tree. (The idea is very similar to [binary search](../Binary Search) in an array.) ## Traversing the tree @@ -57,7 +57,7 @@ Sometimes you don't want to look at just a single node, but at all of them. There are three ways to traverse a binary tree: -1. *In-order* (or *depth-first*): first look at the left child node, then at the node itself, and finally at the right child. +1. *In-order* (or *depth-first*): first look at the left child of a node, then at the node itself, and finally at its right child. 2. *Pre-order*: first look at a node, then its left and right children. 3. *Post-order*: first look at the left and right children and process the node itself last. @@ -148,9 +148,9 @@ Here's how you'd use it: let tree = BinarySearchTree(value: 7) ``` -The `count` property determines how many nodes are in the subtree described by this node. This doesn't just count the node's immediate children but also their children and their children's children, and so on. If this is the root node, then it counts how many nodes are in the entire tree. Initially, `count = 0`. +The `count` property determines how many nodes are in the subtree described by this node. This doesn't just count the node's immediate children but also their children and their children's children, and so on. If this particular object is the root node, then it counts how many nodes are in the entire tree. Initially, `count = 0`. -> **Note:** Because `left`, `right`, and `parent` are optionals, we can make good use of Swift's optional chaining (`?`) and nil-coalescing operators (`??`). You could also write this sort of thing with `if let` but that takes up more space. +> **Note:** Because `left`, `right`, and `parent` are optionals, we can make good use of Swift's optional chaining (`?`) and nil-coalescing operators (`??`). You could also write this sort of thing with `if let` but that is less concise. ### Inserting nodes @@ -182,7 +182,7 @@ A tree node by itself is pretty useless, so here is how you would add new nodes Like so many other tree operations, insertion is easiest to implement with recursion. We compare the new value to the values of the existing nodes and decide whether to add it to the left branch or the right branch. -If there is no more left or right child to look at, we create a new `BinarySearchTree` object for the new node and connect it to the tree by setting its `parent` property. +If there is no more left or right child to look at, we create a `BinarySearchTree` object for the new node and connect it to the tree by setting its `parent` property. > **Note:** Because the whole point of a binary search tree is to have smaller nodes on the left and larger ones on the right, you should always insert elements at the root, to make to sure this remains a valid binary tree! @@ -197,7 +197,7 @@ tree.insert(9) tree.insert(1) ``` -> **Note:** For reasons that will become clear later, you should insert the numbers in a somewhat random order. If you insert them in sorted order, then the tree won't have the right shape. +> **Note:** For reasons that will become clear later, you should insert the numbers in a somewhat random order. If you insert them in sorted order, the tree won't have the right shape. For convenience, let's add an init method that calls `insert()` for all the elements in an array: @@ -243,11 +243,11 @@ When you do a `print(tree)`, you should get something like this: ((1) <- 2 -> (5)) <- 7 -> ((9) <- 10) -With some imagination, you should see now that this indeed corresponds to the following tree: +The root node is in the middle. With some imagination, you should see that this indeed corresponds to the following tree: ![The tree](Images/Tree2.png) -By the way, you may be wondering what happens when you insert duplicate items? We always insert those kinds of items in the right branch. Try it out! +By the way, you may be wondering what happens when you insert duplicate items? We always insert those in the right branch. Try it out! ### Searching @@ -269,7 +269,7 @@ Here is the implementation of `search()`: I hope the logic is clear: this starts at the current node (usually the root) and compares the values. If the search value is less than the node's value, we continue searching in the left branch; if the search value is greater, we dive into the right branch. -Of course, if there are no more nodes to look at -- when `left` or `right` is nil -- then we return nil to indicate the search value is not in the tree. +Of course, if there are no more nodes to look at -- when `left` or `right` is nil -- then we return `nil` to indicate the search value is not in the tree. > **Note:** In Swift that's very conveniently done with optional chaining; when you write `left?.search(value)` it automatically returns nil if `left` is nil. There's no need to explicitly check for this with an `if` statement. @@ -299,7 +299,7 @@ Here's how to test searching: tree.search(5) tree.search(2) tree.search(7) -tree.search(6) // nil +tree.search(6) // nil ``` The first three lines all return the corresponding `BinaryTreeNode` object. The last line returns `nil` because there is no node with value `6`. @@ -426,7 +426,7 @@ We won't need it for deleting, but for completeness' sake, here is the opposite } ``` -It returns the rightmost descendent of this node. We find it by following `right` pointers until we get to the end. In the above example, the rightmost descendent of node `2` is `5`. The maximum value in the entire tree is `11`, because that is the rightmost descendent of the root node `7`. +It returns the rightmost descendent of the node. We find it by following `right` pointers until we get to the end. In the above example, the rightmost descendent of node `2` is `5`. The maximum value in the entire tree is `11`, because that is the rightmost descendent of the root node `7`. Finally, we can write the code the remove a node from the tree: @@ -460,8 +460,6 @@ It doesn't look so scary after all. ;-) Here is what it does: The only tricky situation here is `// 1`. Rather than deleting the current node, we give it the successor's value and remove the successor node instead. -> **Note:** What would happen if you deleted the root node? Specifically, how would you know which node becomes the new root node? It turns out you don't have to worry about that because the root never actually gets deleted. We simply give it a new value. - Try it out: ```swift @@ -482,6 +480,8 @@ But after `remove()` you get: As you can see, node `5` has taken the place of `2`. In fact, if you do `print(node2)` you'll see that it now has the value `5`. We didn't actually remove the `node2` object, we just gave it a new value. +> **Note:** What would happen if you deleted the root node? Specifically, how would you know which node becomes the new root node? It turns out you don't have to worry about that because the root never actually gets deleted. We simply give it a new value. + Like most binary search tree operations, removing a node runs in **O(h)** time, where **h** is the height of the tree. ### Depth and height @@ -516,7 +516,7 @@ You can also calculate the *depth* of a node, which is the distance to the root. var edges = 0 while case let parent? = node.parent { node = parent - ++edges + edges += 1 } return edges } @@ -624,6 +624,8 @@ We've implemented the binary tree node as a class but you can also use an enum. The difference is reference semantics versus value semantics. Making a change to the class-based tree will update that same instance in memory. But the enum-based tree is immutable -- any insertions or deletions will give you an entirely new copy of the tree. Which one is best totally depends on what you want to use it for. +Here's how you'd make a binary search tree using an enum: + ```swift public enum BinarySearchTree { case Empty @@ -638,9 +640,9 @@ The enum has three cases: - `Leaf` for a leaf node that has no children. - `Node` for a node that has one or two children. This is marked `indirect` so that it can hold `BinarySearchTree` values. Without `indirect` you can't make recursive enums. -> **Note:** The nodes in this binary tree don't have a reference to their parent node. That will make certain operations slightly more cumbersome to implement. +> **Note:** The nodes in this binary tree don't have a reference to their parent node. It's not a major impediment but it will make certain operations slightly more cumbersome to implement. -A usual, we'll implement most functionality recursively. We'll treat each case slightly differently. For example, this is how you could calculate the number of nodes in the tree and the height of the tree: +A usual, we'll implement most functionality recursively. We'll treat each case of the enum slightly differently. For example, this is how you could calculate the number of nodes in the tree and the height of the tree: ```swift public var count: Int { @@ -696,7 +698,7 @@ tree = tree.insert(9) tree = tree.insert(1) ``` -Notice that each time you insert something, you get back a completely new tree. That's why you need to assign the result back to the `tree` variable. +Notice that each time you insert something, you get back a completely new tree object. That's why you need to assign the result back to the `tree` variable. Here is the all-important search function: @@ -748,13 +750,13 @@ When you do `print(tree)` it will look something like this: ((1 <- 2 -> 5) <- 7 -> (9 <- 10 -> .)) -The root node is in the middle. A dot means there is no child at that position. +The root node is in the middle; a dot means there is no child at that position. ## When the tree becomes unbalanced... A binary search tree is *balanced* when its left and right subtrees contain roughly the same number of nodes. In that case, the height of the tree is *log(n)*, where *n* is the number of nodes. That's the ideal situation. -However, if one branch is significantly longer than the other, searching becomes very slow. We need to check way more values than we'd ideally have to. In the worst case, the height of the tree can become *n*. Such a tree acts more like a [linked list](../Linked List/) than a binary search tree, with performance degrading to **O(n)**. Not good! +However, if one branch is significantly longer than the other, searching becomes very slow. We end up checking way more values than we'd ideally have to. In the worst case, the height of the tree can become *n*. Such a tree acts more like a [linked list](../Linked List/) than a binary search tree, with performance degrading to **O(n)**. Not good! One way to make the binary search tree balanced is to insert the nodes in a totally random order. On average that should balance out the tree quite nicely. But it doesn't guarantee success, nor is it always practical. diff --git a/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift b/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift index f509195f9..7ef0280cf 100644 --- a/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift +++ b/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift @@ -212,7 +212,7 @@ extension BinarySearchTree { var edges = 0 while case let parent? = node.parent { node = parent - ++edges + edges += 1 } return edges } diff --git a/Binary Search Tree/Solution 1/BinarySearchTree.swift b/Binary Search Tree/Solution 1/BinarySearchTree.swift index f509195f9..7ef0280cf 100644 --- a/Binary Search Tree/Solution 1/BinarySearchTree.swift +++ b/Binary Search Tree/Solution 1/BinarySearchTree.swift @@ -212,7 +212,7 @@ extension BinarySearchTree { var edges = 0 while case let parent? = node.parent { node = parent - ++edges + edges += 1 } return edges } diff --git a/Binary Search/README.markdown b/Binary Search/README.markdown index 792b15c4a..8c5d8f0f1 100644 --- a/Binary Search/README.markdown +++ b/Binary Search/README.markdown @@ -31,7 +31,7 @@ And you'd use it like this: linearSearch(numbers, 43) // returns 15 ``` -So what's the problem? `linearSearch()` loops through the entire array from the beginning until it finds the element you're looking for. In the worst case, the value isn't in the array and all that work is done for nothing. +So what's the problem? `linearSearch()` loops through the entire array from the beginning, until it finds the element you're looking for. In the worst case, the value isn't even in the array and all that work is done for nothing. On average, the linear search algorithm needs to look at half the values in the array. If your array is large enough, this starts to become very slow! @@ -41,12 +41,12 @@ The classic way to speed this up is to use a *binary search*. The trick is to ke For an array of size `n`, the performance is not **O(n)** as with linear search but only **O(log n)**. To put that in perspective, binary search on an array with 1,000,000 elements only takes about 20 steps to find what you're looking for, because `log_2(1,000,000) = 19.9`. And for an array with a billion elements it only takes 30 steps. (Then again, when was the last time you used an array with a billion items?) -Sounds great, but there is one downside to using binary search: the array must be sorted. In practice, this usually isn't a problem. +Sounds great, but there is a downside to using binary search: the array must be sorted. In practice, this usually isn't a problem. Here's how binary search works: - Split the array in half and determine whether the thing you're looking for, known as the *search key*, is in the left half or in the right half. -- How do you determine in which half the search key is? This is why you sorted the array first, so you can do a simple less-than or greater-than comparison. +- How do you determine in which half the search key is? This is why you sorted the array first, so you can do a simple `<` or `>` comparison. - If the search key is in the left half, you repeat the process there: split the left half into two even smaller pieces and look in which piece the search key must lie. (Likewise for when it's the right half.) - This repeats until the search key is found. If the array cannot be split up any further, you must regrettably conclude that the search key is not present in the array. @@ -92,11 +92,9 @@ binarySearch(numbers, key: 43, range: 0 ..< numbers.count) // gives 13 Note that the `numbers` array is sorted. The binary search algorithm does not work otherwise! -I said that binary search works by splitting the array in half, but we don't actually create two new arrays. Instead, we keep track of these splits using a Swift `Range` object. +I said that binary search works by splitting the array in half, but we don't actually create two new arrays. Instead, we keep track of these splits using a Swift `Range` object. Initially, this range covers the entire array, `0 ..< numbers.count`. As we split the array, the range becomes smaller and smaller. -Initially, this range covers the entire array, `0 ..< numbers.count`. As we split the array, the range becomes smaller and smaller. - -> **Note:** One thing to be aware of is that `range.endIndex` always points one beyond the last element. In the example, the range is `0..<19` because there are 19 numbers in the array, and so `range.startIndex = 0` and `range.endIndex = 19`. But in our array the last element is at index 18, since we start counting from 0. Just keep this in mind when working with ranges: the `endIndex` is always one more than the index of the last element. +> **Note:** One thing to be aware of is that `range.endIndex` always points one beyond the last element. In the example, the range is `0..<19` because there are 19 numbers in the array, and so `range.startIndex = 0` and `range.endIndex = 19`. But in our array the last element is at index 18, not 19, since we start counting from 0. Just keep this in mind when working with ranges: the `endIndex` is always one more than the index of the last element. ## Stepping through the example @@ -133,7 +131,7 @@ Now binary search will determine which half to use. The relevant section from th } ``` -In this case, `a[midIndex] = 29`. That's less than the search key, so we can safely conclude that the search key will never be in the left half of the array, since that only contains numbers smaller than `29`. Hence, the search key must be in the right half somewhere (or not in the array at all). +In this case, `a[midIndex] = 29`. That's less than the search key, so we can safely conclude that the search key will never be in the left half of the array. After all, the left half only contains numbers smaller than `29`. Hence, the search key must be in the right half somewhere (or not in the array at all). Now we can simply repeat the binary search, but on the array interval from `midIndex + 1` to `range.endIndex`: @@ -167,13 +165,13 @@ Again, the search key is greater, so split once more and take the right side: [ x, x, x, x, x, x, x, x, x, x | x, x | x | 43 | x, x, x, x, x ] * -And now we're done. The search key equals the array element we're looking at, so we've finally found what we were looking for. Number `43` is at array index `13`. w00t! +And now we're done. The search key equals the array element we're looking at, so we've finally found what we were searching for: number `43` is at array index `13`. w00t! It may have seemed like a lot of work, but in reality it only took four steps to find the search key in the array, which sounds about right because `log_2(19) = 4.23`. With a linear search, it would have taken 14 steps. -What would happen if we were to look for `42` instead of `43`? In that case, we can't split up the array any further.The `range.endIndex` becomes smaller than `range.startIndex` and that tells the algorithm the search key is not in the array and it returns `nil`. +What would happen if we were to search for `42` instead of `43`? In that case, we can't split up the array any further. The `range.endIndex` becomes smaller than `range.startIndex`. That tells the algorithm the search key is not in the array and it returns `nil`. -> **Note:** Many implementations of binary search calculate `midIndex = (start + end) / 2`. This contains a subtle bug that only appears with very large arrays, because `start + end` may overflow the maximum number an integer can hold. This situation is unlikely to happen on a 64-bit CPU, but it definitely can on 32-bit machines. +> **Note:** Many implementations of binary search calculate `midIndex = (startIndex + endIndex) / 2`. This contains a subtle bug that only appears with very large arrays, because `startIndex + endIndex` may overflow the maximum number an integer can hold. This situation is unlikely to happen on a 64-bit CPU, but it definitely can on 32-bit machines. ## Iterative vs recursive @@ -214,4 +212,4 @@ Is it a problem that the array must be sorted first? It depends. Keep in mind th See also [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_algorithm). -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Binary Tree/README.markdown b/Binary Tree/README.markdown index ed829ec0c..24498b035 100644 --- a/Binary Tree/README.markdown +++ b/Binary Tree/README.markdown @@ -4,7 +4,7 @@ A binary tree is a [tree](../Tree/) where each node has 0, 1, or 2 children. Thi ![A binary tree](Images/BinaryTree.png) -The child nodes are usually called the "left" child and the "right" child. If a node doesn't have any children, it's called a *leaf* node. The *root* is the node at the very top of the tree (programmers like their trees upside down). +The child nodes are usually called the *left* child and the *right* child. If a node doesn't have any children, it's called a *leaf* node. The *root* is the node at the very top of the tree (programmers like their trees upside down). Often nodes will have a link back to their parent but this is not strictly necessary. @@ -104,9 +104,9 @@ On the tree from the example, `tree.count` should be 12. Something you often need to do with trees is traverse them, i.e. look at all the nodes in some order. There are three ways to traverse a binary tree: -1. *In-order* (or *depth-first*): first look at the left child node, then at the node itself, and finally at the right child. -2. *Pre-order*: first look at a node, then its left and right children. -3. *Post-order*: first look at the left and right children and process the node last. +1. *In-order* (or *depth-first*): first look at the left child of a node, then at the node itself, and finally at its right child. +2. *Pre-order*: first look at a node, then at its left and right children. +3. *Post-order*: first look at the left and right children and process the node itself last. Here is how you'd implement that: diff --git a/Bit Set/README.markdown b/Bit Set/README.markdown index 380463d33..85d55e06e 100644 --- a/Bit Set/README.markdown +++ b/Bit Set/README.markdown @@ -10,7 +10,7 @@ Since manipulating individual bits is a little tricky, you can use `BitSet` to h ## The code -A bit set is simply a wrapper around an array. The array doesn't store individual bits but larger integers that we call the "words". The `BitSet` maps the bits to the right word. +A bit set is simply a wrapper around an array. The array doesn't store individual bits but larger integers called the "words". The main job of `BitSet` is to map the bits to the right word. ```swift public struct BitSet { @@ -38,9 +38,9 @@ If you write, var bits = BitSet(size: 140) ``` -then the `BitSet` allocates an array of three words. Each word holds 64 bits and therefore three words can hold 192 bits. We only use 140 of those bits so we're wasting a bit of space (but of course we can never use less than a whole word.) +then the `BitSet` allocates an array of three words. Each word has 64 bits and therefore three words can hold 192 bits. We only use 140 of those bits so we're wasting a bit of space (but of course we can never use less than a whole word). -> **Note:** The first entry in the `words` array is the least-significant word, so these words are stored in little endian order. +> **Note:** The first entry in the `words` array is the least-significant word, so these words are stored in little endian order in the array. ## Looking up the bits @@ -56,9 +56,9 @@ Most of the operations on `BitSet` take the index of the bit as a parameter, so } ``` -The `indexOf()` function returns the array index of the word, as well as a "mask" that shows where the bit sits inside that word. +The `indexOf()` function returns the array index of the word, as well as a "mask" that shows exactly where the bit sits inside that word. -For example, `indexOf(2)` returns the tuple `(0, 4)` because bit 2 is in the first word (index 0). The mask is 4. In binary that looks like: +For example, `indexOf(2)` returns the tuple `(0, 4)` because bit 2 is in the first word (index 0). The mask is 4. In binary the mask looks like the following: 0010000000000000000000000000000000000000000000000000000000000000 @@ -94,7 +94,7 @@ Clearing the bit -- i.e. changing it to 0 -- is just as easy: } ``` -Instead of a bitwise OR we now do a bitwise AND with the inverse of the mask. So if the mask was `00100000...0`, then the inverse is `11011111...1`. All the bits are 1, except for the bit we want to set to 0. Due to the way `&` works, this leaves all other bits alone and only changes that one to 0. +Instead of a bitwise OR we now do a bitwise AND with the inverse of the mask. So if the mask was `00100000...0`, then the inverse is `11011111...1`. All the bits are 1, except for the bit we want to set to 0. Due to the way `&` works, this leaves all other bits alone and only changes that single bit to 0. To see if a bit is set we also use the bitwise AND but without inverting: @@ -185,7 +185,7 @@ But this is incorrect... Since we don't use most of the last word, we should lea Instead of 192 one-bits we now have only 140 one-bits. The fact that the last word may not be completely filled up means that we always have to treat this last word specially. -Setting those "leftover" bits to 0 is what the `clearUnusedBits()` helper function does. If the size is not a multiple of `N` (i.e. 64), then we have to clear out the bits that we're not using. If we don't do this, bitwise operations between two differently sized `BitSet`s will go wrong (an example follows). +Setting those "leftover" bits to 0 is what the `clearUnusedBits()` helper function does. If the `BitSet`'s size is not a multiple of `N` (i.e. 64), then we have to clear out the bits that we're not using. If we don't do this, bitwise operations between two differently sized `BitSet`s will go wrong (an example follows). This uses some advanced bit manipulation, so pay close attention: @@ -209,7 +209,7 @@ Here's what it does, step-by-step: 1) `diff` is the number of "leftover" bits. In the above example that is 52 because `3*64 - 140 = 52`. -2) Create a mask that is all 0's. Except the highest bit that's still valid is a 1. In our example, that would be: +2) Create a mask that is all 0's, except the highest bit that's still valid is a 1. In our example, that would be: 0000000000010000000000000000000000000000000000000000000000000000 @@ -230,7 +230,7 @@ An example of where this is important is when you combine two `BitSet`s of diffe 10001111 size=4 00100011 size=8 -The first one only uses the first 4 bits; the second one uses 8 bits. The first one should really be `10000000` but let's pretend we forgot to clear out those 1's. Then a bitwise or between the two results in: +The first one only uses the first 4 bits; the second one uses 8 bits. The first one should really be `10000000` but let's pretend we forgot to clear out those 1's at the end. Then a bitwise OR between the two results in: 10001111 00100011 @@ -299,7 +299,7 @@ To count the number of bits that are set to 1 we could scan through the entire a } ``` -When you write `x & ~(x - 1)`, it gives you a new value with a single bit set. This is the lowest bit that is one. For example take this 8-bit value (again, I'm showing this with the least significant bit on the left): +When you write `x & ~(x - 1)`, it gives you a new value with only a single bit set. This is the lowest bit that is one. For example take this 8-bit value (again, I'm showing this with the least significant bit on the left): 00101101 @@ -325,7 +325,9 @@ The only value they have in common is the lowest (or least significant) 1-bit. T -------- XOR 00001101 -We keep repeating this until the value consists of all zeros. The time complexity is **O(s)** where **s** is the number of 1-bits. +This is the original value but with the lowest 1-bit removed. + +We keep repeating this process until the value consists of all zeros. The time complexity is **O(s)** where **s** is the number of 1-bits. ## See also diff --git a/Bloom Filter/README.markdown b/Bloom Filter/README.markdown index c0c06400b..3e37ee50b 100644 --- a/Bloom Filter/README.markdown +++ b/Bloom Filter/README.markdown @@ -16,11 +16,11 @@ An advantage of the Bloom Filter over a hash table is that the former maintains > **Note:** Unlike a hash table, the Bloom Filter does not store the actual objects. It just remembers what objects you’ve seen (with a degree of uncertainty) and which ones you haven’t. -## How it works +## Inserting objects into the set A Bloom Filter is essentially a fixed-length [bit vector](../Bit Set/), an array of bits. When we insert objects, we set some of these bits to `1`, and when we query for objects we check if certain bits are `0` or `1`. Both operations use hash functions. -To insert an element in the filter, the element is hashed with several different hash functions. Each hash function returns a value that we map to an index in the array. We set the bits at these indices to `1` or true. +To insert an element in the filter, the element is hashed with several different hash functions. Each hash function returns a value that we map to an index in the array. We then set the bits at these indices to `1` or true. For example, let's say this is our array of bits. We have 17 bits and initially they are all `0` or false: @@ -34,9 +34,11 @@ Then we hash the original string again but this time with a different hash funct [ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 ] -These two bits are enough to tell the Bloom Filter that it now contains the string `"Hello world!"`. +These two 1-bits are enough to tell the Bloom Filter that it now contains the string `"Hello world!"`. Of course, it doesn't contain the actual string, so you can't ask the Bloom Filter, "give me a list of all the objects you contain". All it has is a bunch of ones and zeros. -Querying, similarly, is accomplished by first hashing the expected value, which gives several array indices, and then checking to see if all of the bits at those indices are `true`. If even one of the bits is not `true`, the element could not have been inserted and the query returns `false`. If all the bits are `true`, the query returns likewise. +## Querying the set + +Querying, similarly to inserting, is accomplished by first hashing the expected value, which gives several array indices, and then checking to see if all of the bits at those indices are `1`. If even one of the bits is not `1`, the element could not have been inserted and the query returns `false`. If all the bits are `1`, the query returns `true`. For example, if we query for the string `"Hello WORLD"`, then the first hash function returns 5383892684077141175, which modulo 17 is 12. That bit is `1`. But the second hash function gives 5625257205398334446, which maps to array index 9. That bit is `0`. This means the string `"Hello WORLD"` is not in the filter and the query returns `false`. @@ -50,7 +52,7 @@ If you query for `"Hello WORLD"` again, the filter sees that bit 12 is true and You can fix such issues by using an array with more bits and using additional hash functions. Of course, the more hash functions you use the slower the Bloom Filter will be. So you have to strike a balance. -Deletion is not possible with a Bloom Filter, since any one bit might have been set by multiple elements inserted. Once you add an element, it's in there for good. +Deletion is not possible with a Bloom Filter, since any one bit might belong to multiple elements. Once you add an element, it's in there for good. Performance of a Bloom Filter is **O(k)** where **k** is the number of hashing functions. @@ -96,6 +98,6 @@ public func query(value: T) -> Bool { } ``` -If you're coming from another imperative language, you might notice the unusual syntax in the `exists` assignment. Swift makes use of functional paradigms when it makes code more consise and readable, and in this case, `reduce` is a much more consise way to check if all the required bits are `true` than a `for` loop. +If you're coming from another imperative language, you might notice the unusual syntax in the `exists` assignment. Swift makes use of functional paradigms when it makes code more consise and readable, and in this case `reduce` is a much more consise way to check if all the required bits are `true` than a `for` loop. *Written for Swift Algorithm Club by Jamil Dhanani. Edited by Matthijs Hollemans.* diff --git a/Boyer-Moore/README.markdown b/Boyer-Moore/README.markdown index dce0430f2..73a7943a2 100644 --- a/Boyer-Moore/README.markdown +++ b/Boyer-Moore/README.markdown @@ -2,7 +2,7 @@ Goal: Write a string search algorithm in pure Swift without importing Foundation or using `NSString`'s `rangeOfString()` method. -In other words, implement an `indexOf(pattern: String)` extension on `String` that returns the `String.Index` of the first occurrence of the search pattern, or `nil` if the pattern could not be found inside the string. +In other words, we want to implement an `indexOf(pattern: String)` extension on `String` that returns the `String.Index` of the first occurrence of the search pattern, or `nil` if the pattern could not be found inside the string. For example: @@ -24,9 +24,9 @@ animals.indexOf("🐮") > **Note:** The index of the cow is 6, not 3 as you might expect, because the string uses more storage per character for emoji. The actual value of the `String.Index` is not so important, just that it points at the right character in the string. -The [brute-force approach](../Brute-Force Search Search/) works OK, but it's not very efficient, especially on large chunks of text. As it turns out, you don't need to look at *every* character from the source string -- you can often skip ahead multiple characters. +The [brute-force approach](../Brute-Force String Search/) works OK, but it's not very efficient, especially on large chunks of text. As it turns out, you don't need to look at *every* character from the source string -- you can often skip ahead multiple characters. -That skip-ahead algorithm is called [Boyer-Moore](https://en.wikipedia.org/wiki/Boyer–Moore_string_search_algorithm) and it has been around for a long time. It is considered the benchmark for all string search algorithms. +The skip-ahead algorithm is called [Boyer-Moore](https://en.wikipedia.org/wiki/Boyer–Moore_string_search_algorithm) and it has been around for a long time. It is considered the benchmark for all string search algorithms. Here's how you could write it in Swift: @@ -133,7 +133,7 @@ The amount to skip ahead at any given time is determined by the "skip table", wh The closer a character is to the end of the pattern, the smaller the skip amount. If a character appears more than once in the pattern, the one nearest to the end of the pattern determines the skip value for that character. -A caveat: If the search pattern consists of only a few characters, it's faster to do a brute-force search. There's a trade-off between the time it takes to build the skip table and doing brute-force for short patterns. +> **Note:** If the search pattern consists of only a few characters, it's faster to do a brute-force search. There's a trade-off between the time it takes to build the skip table and doing brute-force for short patterns. Credits: This code is based on the article ["Faster String Searches" by Costas Menico](http://www.drdobbs.com/database/faster-string-searches/184408171) from Dr Dobb's magazine, July 1989 -- Yes, 1989! Sometimes it's useful to keep those old magazines around. diff --git a/Breadth-First Search/README.markdown b/Breadth-First Search/README.markdown index 584b37c1d..e75d2b913 100644 --- a/Breadth-First Search/README.markdown +++ b/Breadth-First Search/README.markdown @@ -1,6 +1,6 @@ # Breadth-First Search -Breadth-first search (BFS) is an algorithm for traversing or searching [tree](../Tree/) or [graph](../Graph/) data structures. It starts at the tree source and explores the immediate neighbor nodes first, before moving to the next level neighbors. +Breadth-first search (BFS) is an algorithm for traversing or searching [tree](../Tree/) or [graph](../Graph/) data structures. It starts at a source node and explores the immediate neighbor nodes first, before moving to the next level neighbors. ## Animated example @@ -129,10 +129,10 @@ This will output: `["a", "b", "c", "d", "e", "f", "g", "h"]` ## Applications -Breadth-first search can be used to solve many problems, for example: +Breadth-first search can be used to solve many problems. A small selection: -* Computing the shortest path between a source node and each of the other nodes (only for unweighted graphs) -* Calculating the minimum spanning tree on an unweighted graph +* Computing the shortest path between a source node and each of the other nodes (only for unweighted graphs). +* Calculating the minimum spanning tree on an unweighted graph. ## Shortest path example diff --git a/Combinatorics/README.markdown b/Combinatorics/README.markdown index d491aab28..2d7a6ac83 100644 --- a/Combinatorics/README.markdown +++ b/Combinatorics/README.markdown @@ -10,20 +10,20 @@ This is another permutation: For a collection of `n` objects, there are `n!` possible permutations, where `!` is the "factorial" function. So for our collection of five letters, the total number of permutations you can make is: - 5! = 5 x 4 x 3 x 2 x 1 = 120 + 5! = 5 * 4 * 3 * 2 * 1 = 120 -A collection of six items has `6! = 720` permutations. For ten items, it is `10! = 3.628.800`. That adds up quick! +A collection of six items has `6! = 720` permutations. For ten items, it is `10! = 3,628,800`. That adds up quick! Where does this `n!` come from? The logic is as follows: we have a collection of five letters that we want to put in some order. To do this, you need to pick up these letters one-by-one. Initially, you have the choice of five letters: `a, b, c, d, e`. That gives 5 possibilities. -After picking the first letter, you only have four letters left to choose from. That gives `5 x 4 = 20` possibilities: +After picking the first letter, you only have four letters left to choose from. That gives `5 * 4 = 20` possibilities: a+b b+a c+a d+a e+a a+c b+c c+b d+b e+b a+d b+d c+d d+c e+c a+e b+e c+e d+e e+d -After picking the second letter, there are only three letters left to choose from. And so on... When you get to the last letter, you don't have any choice because there is only one letter left. That's why the total number of possibilities is `5 x 4 x 3 x 2 x 1`. +After picking the second letter, there are only three letters left to choose from. And so on... When you get to the last letter, you don't have any choice because there is only one letter left. That's why the total number of possibilities is `5 * 4 * 3 * 2 * 1`. To calculate the factorial in Swift: @@ -47,7 +47,7 @@ factorial(5) // returns 120 Note that `factorial(20)` is the largest number you can calculate with this function, or you'll get integer overflow. -Let's say that from that collection of five letters you want to choose only 3 elements. How many possible ways can you do this? Well, that works the same way as before, except that you stop after the third letter. So now the number of possibilities is `5 x 4 x 3 = 60`. +Let's say that from that collection of five letters you want to choose only 3 elements. How many possible ways can you do this? Well, that works the same way as before, except that you stop after the third letter. So now the number of possibilities is `5 * 4 * 3 = 60`. The formula for this is: @@ -83,11 +83,13 @@ permutations(9, 4) // returns 3024 This function takes advantage of the following algebra fact: - 9 x 8 x 7 x 6 x 5 x 4 x 3 x 2 x 1 - P(9, 4) = --------------------------------- = 9 x 8 x 7 x 6 = 3024 - 5 x 4 x 3 x 2 x 1 + 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 + P(9, 4) = --------------------------------- = 9 * 8 * 7 * 6 = 3024 + 5 * 4 * 3 * 2 * 1 -The denominator cancels out part of the numerator, so there's no need to perform a division and you're not dealing with intermediate results that are potentially too large. However, there are still limits to what you can calculate; for example the number of groups of size 15 that you can make from a collection of 30 objects -- i.e. `P(30, 15)` -- is ginormous and breaks Swift. Huh, you wouldn't think it would be so large but combinatorics is funny that way. +The denominator cancels out part of the numerator, so there's no need to perform a division and you're not dealing with intermediate results that are potentially too large. + +However, there are still limits to what you can calculate; for example the number of groups of size 15 that you can make from a collection of 30 objects -- i.e. `P(30, 15)` -- is ginormous and breaks Swift. Huh, you wouldn't think it would be so large but combinatorics is funny that way. ## Generating the permutations @@ -211,7 +213,7 @@ Of course, the larger your array is, the more swaps it performs and the deeper t If the above is still not entirely clear, then I suggest you give it a go in the playground. That's what playgrounds are great for. :-) -For fun, here is an alternative algorithm, by Sedgewick: +For fun, here is an alternative algorithm, by Robert Sedgewick: ```swift func permuteSedgewick(a: [Int], _ n: Int, inout _ pos: Int) { @@ -259,7 +261,7 @@ Try to figure out for yourself how this algorithm works! ## Combinations -A combination is like a permutation where the order does not matter. So the following are six different permutations of the letters `k` `l` `m` but they all count as the same combination: +A combination is like a permutation where the order does not matter. The following are six different permutations of the letters `k` `l` `m` but they all count as the same combination: k, l, m k, m, l m, l, k l, m, k l, k, m m, k, l @@ -276,15 +278,15 @@ The formula for `C(n, k)` is: n! P(n, k) C(n, k) = ------------- = -------- - (n - k)! x k! k! + (n - k)! * k! k! As you can see, you can derive it from the formula for `P(n, k)`. There are always more permutations than combinations. You divide the number of permutations by `k!` because a total of `k!` of these permutations give the same combination. -Above I showed that the number of permutations of `k` `l` `m` is 6 but, if you pick only two of those letters, the number of combinations is 3. If we use the formula we should get the same answer. We want to calculate `C(3, 2)` because we choose 2 letters out of a collection of 3. +Above I showed that the number of permutations of `k` `l` `m` is 6, but if you pick only two of those letters the number of combinations is 3. If we use the formula we should get the same answer. We want to calculate `C(3, 2)` because we choose 2 letters out of a collection of 3. - 3 x 2 x 1 6 + 3 * 2 * 1 6 C(3, 2) = --------- = --- = 3 - 1! x 2! 2 + 1! * 2! 2 Here's a simple function to calculate `C(n, k)`: @@ -300,7 +302,7 @@ Use it like this: combinations(28, 5) // prints 98280 ``` -Because this uses the `permutations()` and `factorial()` functions under the hood, you're still limited by how large these numbers can get. For example, `combinations(30, 15)` is "only" `155.117.520` but because the intermediate results don't fit into a 64-bit integer, you can't calculate it with the given function. +Because this uses the `permutations()` and `factorial()` functions under the hood, you're still limited by how large these numbers can get. For example, `combinations(30, 15)` is "only" `155,117,520` but because the intermediate results don't fit into a 64-bit integer, you can't calculate it with the given function. Here is an algorithm that uses dynamic programming to overcome the need for calculating factorials. It is based on Pascal's triangle: @@ -312,7 +314,7 @@ Here is an algorithm that uses dynamic programming to overcome the need for calc 5: 1 5 10 10 5 1 6: 1 6 15 20 15 6 1 -Each number in the next row is made up by adding two numbers from the previous row. For example, in row 6, the number 15 is made by adding the 5 and 10 from row 5. These numbers are called the binomial coefficients and as it happens they are the same as `C(n, k)`. +Each number in the next row is made up by adding two numbers from the previous row. For example in row 6, the number 15 is made by adding the 5 and 10 from row 5. These numbers are called the binomial coefficients and as it happens they are the same as `C(n, k)`. For example, for row 6: @@ -360,6 +362,6 @@ You may wonder what the point is in calculating these permutations and combinati ## References -Wirth's and Sedgewick's permutation algorithms and the code for counting permutations and combinations is based on the Algorithm Alley column from Dr.Dobb's Magazine, June 1993. The dynamic programming binomial coefficient algorithm is from The Algorithm Design Manual by Skiena. +Wirth's and Sedgewick's permutation algorithms and the code for counting permutations and combinations are based on the Algorithm Alley column from Dr.Dobb's Magazine, June 1993. The dynamic programming binomial coefficient algorithm is from The Algorithm Design Manual by Skiena. *Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Count Occurrences/README.markdown b/Count Occurrences/README.markdown index d22609025..be8c2061e 100644 --- a/Count Occurrences/README.markdown +++ b/Count Occurrences/README.markdown @@ -2,7 +2,7 @@ Goal: Count how often a certain value appears in an array. -The obvious way to do this is with a [linear search](../Linear Search/) from the beginning of the array until the end, keeping count of how often you come across the value. This is an **O(n)** algorithm. +The obvious way to do this is with a [linear search](../Linear Search/) from the beginning of the array until the end, keeping count of how often you come across the value. That is an **O(n)** algorithm. However, if the array is sorted you can do it much faster, in **O(log n)** time, by using a modification of [binary search](../Binary Search/). @@ -10,7 +10,7 @@ Let's say we have the following array: [ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ] -If we want to know how often the value `3` occurs, we can do a binary search for `3`. That could give us any of these four indices: +If we want to know how often the value `3` occurs, we can do a regular binary search for `3`. That could give us any of these four indices: [ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ] * * * * @@ -55,7 +55,7 @@ func countOccurrencesOfKey(key: Int, inArray a: [Int]) -> Int { } ``` -Notice that the helper functions `leftBoundary()` and `rightBoundary()` are very similar to the binary search algorithm. The big difference is that they don't stop when they find the search key, but keep going. +Notice that the helper functions `leftBoundary()` and `rightBoundary()` are very similar to the [binary search](../Binary Search/) algorithm. The big difference is that they don't stop when they find the search key, but keep going. To test this algorithm, copy the code to a playground and then do: @@ -65,7 +65,7 @@ let a = [ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ] countOccurrencesOfKey(3, inArray: a) // returns 4 ``` -Remember: If you use your own array, make sure it is sorted first! +> **Remember:** If you use your own array, make sure it is sorted first! Let's walk through the example. The array is: @@ -111,10 +111,10 @@ Now let's start over and try to find the right boundary. This is very similar, s The right boundary is at index 7. The difference between the two boundaries is 7 - 3 = 4, so the number `3` occurs four times in this array. -Each binary search took 4 steps, so in total this algorithm took 8 steps. Not a big gain on an array of only 12 items, but the bigger the array, the more efficient this algorithm becomes. For a sorted array with 1,000,000 items, it only takes 2x20 = 40 steps to count the number of occurrences for any particular value. +Each binary search took 4 steps, so in total this algorithm took 8 steps. Not a big gain on an array of only 12 items, but the bigger the array, the more efficient this algorithm becomes. For a sorted array with 1,000,000 items, it only takes 2 x 20 = 40 steps to count the number of occurrences for any particular value. By the way, if the value you're looking for is not in the array, then `rightBoundary()` and `leftBoundary()` return the same value and so the difference between them is 0. This is an example of how you can modify the basic binary search to solve other algorithmic problems as well. Of course, it does require that the array is sorted. -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Deque/README.markdown b/Deque/README.markdown index 87b5d2791..3329adca0 100644 --- a/Deque/README.markdown +++ b/Deque/README.markdown @@ -2,7 +2,7 @@ A double-ended queue. For some reason this is pronounced as "deck". -A regular [queue](../Queue/) adds new elements to the back and removes from the front. The deque also allows enqueuing at the front and dequeuing from the back, and peeking at both ends. +A regular [queue](../Queue/) adds elements to the back and removes from the front. The deque also allows enqueuing at the front and dequeuing from the back, and peeking at both ends. Here is a very basic implementation of a deque in Swift: @@ -52,7 +52,7 @@ public struct Deque { } ``` -This uses an array internally. Enqueuing and dequeuing is simply a matter of adding and removing items from the front or back of the array. +This uses an array internally. Enqueuing and dequeuing are simply a matter of adding and removing items from the front or back of the array. An example of how to use it in a playground: @@ -90,7 +90,7 @@ Likewise, inserting an element at the front of the array is expensive because it [ 5, 2, 3, 4 ] -First, the elements `2`, `3`, and `4`, are move up by one position in the computer's memory, and then the new element `5` is inserted at the position where `2` used to be. +First, the elements `2`, `3`, and `4` are moved up by one position in the computer's memory, and then the new element `5` is inserted at the position where `2` used to be. Why is this not an issue at for `enqueue()` and `dequeueBack()`? Well, these operations are performed at the end of the array. The way resizable arrays are implemented in Swift is by reserving a certain amount of free space at the back. @@ -102,7 +102,7 @@ where the `x`s denote additional positions in the array that are not being used [ 1, 2, 3, 4, 6, x, x ] -And `dequeueBack()` uses `array.removeLast()` to read that item and decrement `array.count` by one. There is no shifting of memory involved here. So operations at the back of the array are fast, **O(1)**. +The `dequeueBack()` function uses `array.removeLast()` to delete that item. This does not shrink the array's memory but only decrements `array.count` by one. There are no expensive memory copies involved here. So operations at the back of the array are fast, **O(1)**. It is possible the array runs out of free spots at the back. In that case, Swift will allocate a new, larger array and copy over all the data. This is an **O(n)** operation but because it only happens once in a while, adding new elements at the end of an array is still **O(1)** on average. @@ -110,7 +110,7 @@ Of course, we can use this same trick at the *beginning* of the array. That will [ x, x, x, 1, 2, 3, 4, x, x, x ] -There is now a chunk of free space at the start of the array, which allows adding or removing elements at the front of the queue to be **O(1)** as well. +There is now also a chunk of free space at the start of the array, which allows adding or removing elements at the front of the queue to be **O(1)** as well. Here is the new version of `Deque`: @@ -217,7 +217,7 @@ Notice how the array has resized itself. There was no room to add the `1`, so Sw | head -> **Note:** You won't see those empty spots at the back when you `print(deque.array)`. This is because Swift hides them from you. Only the ones at the front of the array show up. +> **Note:** You won't see those empty spots at the back of the array when you `print(deque.array)`. This is because Swift hides them from you. Only the ones at the front of the array show up. The `dequeue()` method does the opposite of `enqueueFront()`, it reads the value at `head`, sets the array element back to `nil`, and then moves `head` one position to the right: @@ -232,7 +232,7 @@ The `dequeue()` method does the opposite of `enqueueFront()`, it reads the value } ``` -There is one tiny problem... If you enqueue a lot of objects at the front, you're going to run out of empty spots at the front at some point. When this happens at the back of the array, Swift automatically resizes it. But at the front of the array we have to handle this ourselves with some extra logic in `enqueueFront()`: +There is one tiny problem... If you enqueue a lot of objects at the front, you're going to run out of empty spots at the front at some point. When this happens at the back of the array, Swift automatically resizes it. But at the front of the array we have to handle this situation ourselves, with some extra logic in `enqueueFront()`: ```swift public mutating func enqueueFront(element: T) { @@ -252,11 +252,11 @@ If `head` equals 0, there is no room left at the front. When that happens, we ad > **Note:** We also multiply the capacity by 2 each time this happens, so if your queue grows bigger and bigger, the resizing happens less often. This is also what Swift arrays automatically do at the back. -We have to do something similar for `dequeue()`. If you mostly enqueue a lot of elements at the back and mostly dequeue from the front, then you may end up with an array that looks like this: +We have to do something similar for `dequeue()`. If you mostly enqueue a lot of elements at the back and mostly dequeue from the front, then you may end up with an array that looks as follows: [ x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, 1, 2, 3 ] - | - head + | + head Those empty spots at the front only get used when you call `enqueueFront()`. But if enqueuing objects at the front happens only rarely, this leaves a lot of wasted space. So let's add some code to `dequeue()` to clean this up: @@ -285,7 +285,7 @@ For example, this: | | capacity head -becomes this after trimming: +becomes after trimming: [ x, x, x, x, x, 1, 2, 3 ] | @@ -294,7 +294,7 @@ becomes this after trimming: This way we can strike a balance between fast enqueuing and dequeuing at the front and keeping the memory requirements reasonable. -> **Note:** We don't perform this trimming on very small arrays. It's not worth it for saving just a few bytes of memory. +> **Note:** We don't perform trimming on very small arrays. It's not worth it for saving just a few bytes of memory. ## See also diff --git a/Fixed Size Array/README.markdown b/Fixed Size Array/README.markdown index ec72852a8..8a87f077c 100644 --- a/Fixed Size Array/README.markdown +++ b/Fixed Size Array/README.markdown @@ -2,7 +2,7 @@ Early programming languages didn't have very fancy arrays. You'd create the array with a specific size and from that moment on it would never grow or shrink. Even the standard arrays in C and Objective-C are still of this type. -When you define an array like this, +When you define an array like so, int myArray[10]; @@ -14,7 +14,7 @@ That's your array. It will always be this size. If you need to fit more than 10 To get an array that grows when it gets full you need to use a [dynamic array](https://en.wikipedia.org/wiki/Dynamic_array) object such as `NSMutableArray` in Objective-C or `std::vector` in C++, or a language like Swift whose arrays increase their capacity as needed. -A big downside of the old-style arrays is that they need to be big enough or you run out of space. But if they are too big you're wasting memory. And you need to be careful about security flaws and crashes due to buffer overflows. In summary, fixed-size arrays are not flexible and they leave no room for error. +A major downside of the old-style arrays is that they need to be big enough or you run out of space. But if they are too big you're wasting memory. And you need to be careful about security flaws and crashes due to buffer overflows. In summary, fixed-size arrays are not flexible and they leave no room for error. That said, **I like fixed-size arrays** because they are simple, fast, and predictable. @@ -38,7 +38,7 @@ These two operations have complexity **O(1)**, meaning the time it takes to perf For an array that can grow, appending is more involved: if the array is full, new memory must be allocated and the old contents copied over to the new memory buffer. On average, appending is still an **O(1)** operation, but what goes on under the hood is less predictable. -The expensive operations are inserting and deleting. When you insert an element somewhere that's not at the end, it requires moving up the remainder of the array by one position. That involves a relatively costly memory copy operation: +The expensive operations are inserting and deleting. When you insert an element somewhere that's not at the end, it requires moving up the remainder of the array by one position. That involves a relatively costly memory copy operation. For example, inserting the value `7` in the middle of the array: ![Insert requires a memory copy](Images/insert.png) @@ -48,7 +48,7 @@ Deleting requires a copy the other way around: ![Delete also requires a memory copy](Images/delete.png) -This, by the way, is also true for `NSMutableArray` or Swift arrays. Inserting and deleting are **O(n)** operations -- the larger the array is the more time it takes. +This, by the way, is also true for `NSMutableArray` or Swift arrays. Inserting and deleting are **O(n)** operations -- the larger the array the more time it takes. Fixed-size arrays are a good solution when: @@ -87,7 +87,7 @@ This copies the last element on top of the element you want to remove, and then This is why the array is not sorted. To avoid an expensive copy of a potentially large portion of the array we copy just one element, but that does change the order of the elements. -There are now two copies of element "6" in the array, but what was previously the last element is no longer part of the active array. It's just junk data -- the next time you append an new element, this old version of "6" will be overwritten. +There are now two copies of element `6` in the array, but what was previously the last element is no longer part of the active array. It's just junk data -- the next time you append an new element, this old version of `6` will be overwritten. Under these two constraints -- a limit on the number of elements and an unsorted array -- fixed-size arrays are still perfectly suitable for use in modern software. @@ -138,4 +138,4 @@ var a = FixedSizeArray(maxSize: 10, defaultValue: 0) Note that `removeAtIndex()` overwrites the last element with this `defaultValue` to clean up the "junk" object that gets left behind. Normally it wouldn't matter to leave that duplicate object in the array, but if it's a class or a struct it may have strong references to other objects and it's good boyscout practice to zero those out. -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Fizz Buzz/README.markdown b/Fizz Buzz/README.markdown index 066cbe2de..0ce1e2c42 100644 --- a/Fizz Buzz/README.markdown +++ b/Fizz Buzz/README.markdown @@ -18,38 +18,40 @@ The modulus operator returns the remainder after an integer division. Here is an | Division | Division Result | Modulus | Modulus Result | | ------------- | -------------------------- | --------------- | ---------------:| -| 1 `/` 3 | `0` with a remainder of 3 | 1 `%` 3 | `3` | -| 5 `/` 3 | `1` with a remainder of 2 | 5 `%` 3 | `2` | -| 16 `/` 3 | `5` with a remainder of 1 | 16 `%` 3 | `1` | +| 1 / 3 | 0 with a remainder of 3 | 1 % 3 | 3 | +| 5 / 3 | 1 with a remainder of 2 | 5 % 3 | 2 | +| 16 / 3 | 5 with a remainder of 1 | 16 % 3 | 1 | A common approach to determine if a number is even or odd is to use the modulus operator: | Modulus | Result | Swift Code | Swift Code Result | Comment | | ------------- | ---------------:| ------------------------------- | -----------------:| --------------------------------------------- | -| 6 `%` 2 | 0 | `let isEven = number % 2 == 0` | `true` | If a number is divisible by 2 it is `even` | -| 5 `%` 2 | 1 | `let isOdd = number % 2 != 0` | `true` | If a number is not divisible by 2 it is `odd` | +| 6 % 2 | 0 | `let isEven = (number % 2 == 0)` | `true` | If a number is divisible by 2 it is *even* | +| 5 % 2 | 1 | `let isOdd = (number % 2 != 0)` | `true` | If a number is not divisible by 2 it is *odd* | -Now we can use the modulus operator `%` to solve fizz buzz. +## Solving fizz buzz + +Now we can use the modulus operator `%` to solve fizz buzz. Finding numbers divisible by three: | Modulus | Modulus Result | Swift Code | Swift Code Result | | ------- | --------------:| ------------- |------------------:| -| 1 `%` 3 | `1` | `1 % 3 == 0` | `false` | -| 2 `%` 3 | `2` | `2 % 3 == 0` | `false` | -| 3 `%` 3 | `0` | `3 % 3 == 0` | `true` | -| 4 `%` 3 | `1` | `4 % 3 == 0` | `false` | +| 1 % 3 | 1 | `1 % 3 == 0` | `false` | +| 2 % 3 | 2 | `2 % 3 == 0` | `false` | +| 3 % 3 | 0 | `3 % 3 == 0` | `true` | +| 4 % 3 | 1 | `4 % 3 == 0` | `false` | Finding numbers divisible by five: | Modulus | Modulus Result | Swift Code | Swift Code Result | | ------- | --------------:| ------------- |------------------:| -| 1 `%` 5 | `1` | `1 % 5 == 0` | `false` | -| 2 `%` 5 | `2` | `2 % 5 == 0` | `false` | -| 3 `%` 5 | `3` | `3 % 5 == 0` | `false` | -| 4 `%` 5 | `4` | `4 % 5 == 0` | `false` | -| 5 `%` 5 | `0` | `5 % 5 == 0` | `true` | -| 6 `%` 5 | `1` | `6 % 5 == 0` | `false` | +| 1 % 5 | 1 | `1 % 5 == 0` | `false` | +| 2 % 5 | 2 | `2 % 5 == 0` | `false` | +| 3 % 5 | 3 | `3 % 5 == 0` | `false` | +| 4 % 5 | 4 | `4 % 5 == 0` | `false` | +| 5 % 5 | 0 | `5 % 5 == 0` | `true` | +| 6 % 5 | 1 | `6 % 5 == 0` | `false` | ## The code @@ -80,8 +82,13 @@ func fizzBuzz(numberOfTurns: Int) { Put this code in a playground and test it like so: ```swift -fizzBuzz(15) // This will output 1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, Fizz Buzz +fizzBuzz(15) ``` + +This will output: + + 1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, Fizz Buzz + ## See also [Fizz buzz on Wikipedia](https://en.wikipedia.org/wiki/Fizz_buzz) diff --git a/GCD/README.markdown b/GCD/README.markdown index a7fe799d9..1d20fb9cb 100644 --- a/GCD/README.markdown +++ b/GCD/README.markdown @@ -30,16 +30,16 @@ func gcd(a: Int, _ b: Int) -> Int { Put it in a playground and try it out with these examples: ```swift -gcd(52, 39) // 13 -gcd(228, 36) // 12 -gcd(51357, 3819) // 57 +gcd(52, 39) // 13 +gcd(228, 36) // 12 +gcd(51357, 3819) // 57 ``` Let's step through the third example: gcd(51357, 3819) -According to Euclid's rule, this is equivalent to: +According to Euclid's rule, this is equivalent to, gcd(3819, 51357 % 3819) = gcd(3819, 1710) @@ -65,7 +65,7 @@ By the way, it's also possible that two numbers have a GCD of 1. They are said t gcd(841, 299) // 1 ``` -Here is a slightly different implementation of Euclid's algorithm. Unlike the first version this doesn't use recursion but only a basic `while` loop. The `max()` and `min()` at the top of the function make sure we always divide the larger number by the smaller one. +Here is a slightly different implementation of Euclid's algorithm. Unlike the first version this doesn't use recursion but only a basic `while` loop. ```swift func gcd(m: Int, _ n: Int) -> Int { @@ -82,6 +82,8 @@ func gcd(m: Int, _ n: Int) -> Int { } ``` +The `max()` and `min()` at the top of the function make sure we always divide the larger number by the smaller one. + ## Least Common Multiple An idea related to the GCD is the *least common multiple* or LCM. diff --git a/Hash Set/README.markdown b/Hash Set/README.markdown index 8dcc891d3..c6616fb42 100644 --- a/Hash Set/README.markdown +++ b/Hash Set/README.markdown @@ -11,7 +11,7 @@ If the following were arrays, they'd all be different. However, they all represe [1, 2, 2, 3, 1] ``` -(Because each element can appear only once, it doesn't matter how often you write it down -- only one of them counts.) +Because each element can appear only once, it doesn't matter how often you write the element down -- only one of them counts. > **Note:** I often prefer to use sets over arrays when I have a collection of objects but don't care what order they are in. Using a set communicates to the programmer that the order of the elements is unimportant. If you're using an array, then you can't assume the same thing. @@ -135,7 +135,7 @@ let union = setA.union(setB) union.allElements() // [5, 6, 2, 3, 1, 4] ``` -As you can see, the union of the two sets contains all of the elements now. The values `3` and `4` appear only once, even though they were in both sets. +As you can see, the union of the two sets contains all of the elements now. The values `3` and `4` still appear only once, even though they were in both sets. The *intersection* of two sets contains only the elements that they have in common. Here is the code: @@ -192,7 +192,7 @@ difference2.allElements() // [5, 6] If you look at the [documentation](http://swiftdoc.org/v2.1/type/Set/) for Swift's own `Set`, you'll notice it has tons more functionality. An obvious extension would be to make `HashSet` conform to `SequenceType` so that you can iterate it with a `for`...`in` loop. -Another thing you could do is replace the `Dictionary` with an actual [hash table](../Hash Table), but one that just stores the keys and doesn't associate them with anything. +Another thing you could do is replace the `Dictionary` with an actual [hash table](../Hash Table), but one that just stores the keys and doesn't associate them with anything. So you wouldn't need the `Bool` values anymore. If you often need to look up whether an element belongs to a set and perform unions, then the [union-find](../Union-Find/) data structure may be more suitable. It uses a tree structure instead of a dictionary to make the find and union operations very efficient. diff --git a/Hash Table/README.markdown b/Hash Table/README.markdown index 5afecabba..02a2c9578 100644 --- a/Hash Table/README.markdown +++ b/Hash Table/README.markdown @@ -4,11 +4,11 @@ A hash table allows you to store and retrieve objects by a "key". Also called dictionary, map, associative array. There are other ways to implement these, such as with a tree or even a plain array, but hash table is the most common. -This should explain why Swift's built-in `Dictionary` type requires that keys conform to the `Hashable` protocol: internally it uses a hash table. +This should explain why Swift's built-in `Dictionary` type requires that keys conform to the `Hashable` protocol: internally it uses a hash table, just like the one you'll learn about here. -## How a hash table works +## How it works -At its most basic, a hash table is nothing more than an array. Initially, this array is empty. When you put a value into the hash table, it uses the key to calculate an index in the array, like so: +At its most basic, a hash table is nothing more than an array. Initially, this array is empty. When you put a value into the hash table under a certain key, it uses that key to calculate an index in the array, like so: ```swift hashTable["firstName"] = "Steve" @@ -68,11 +68,11 @@ Using hashes in this manner is what makes the dictionary so efficient: to find a ## Avoiding collisions -There is one problem: because we take the modulo of the hash value with the size of the array, it can happen than two or more keys get assigned the same array index. This is called a collision. +There is one problem: because we take the modulo of the hash value with the size of the array, it can happen that two or more keys get assigned the same array index. This is called a collision. One way to avoid collisions is to have a very large array. That reduces the likelihood of two keys mapping to the same index. Another trick is to use a prime number for the array size. However, collisions are bound to occur so you need some way to handle them. -Because our table is so small it's easy to show a collision. For example, the array index for the key `"lastName"` is also 3. +Because our table is so small it's easy to show a collision. For example, the array index for the key `"lastName"` is also 3. That's a problem, as we don't want to overwrite the value that's already at this array index. There are a few ways to handle collisions. A common one is to use chaining. The array now looks as follows: @@ -91,7 +91,7 @@ There are a few ways to handle collisions. A common one is to use chaining. The +-----+ ``` -Keys are not stored directly in the array. Instead, each element in the array is really a list of key/value pairs. The array elements are usually called the *buckets* and the lists are called the *chains*. So here we have 5 buckets and two of these buckets have chains. The other three buckets are empty. +With chaining, keys and their values are not stored directly in the array. Instead, each array element is really a list of zero or more key/value pairs. The array elements are usually called the *buckets* and the lists are called the *chains*. So here we have 5 buckets and two of these buckets have chains. The other three buckets are empty. If we now write the following to retrieve an item from the hash table, @@ -99,13 +99,13 @@ If we now write the following to retrieve an item from the hash table, let x = hashTable["lastName"] ``` -then this first hashes the key `"lastName"` to calculate the array index, which is 3. Bucket 3 has a chain, so we step through that list to find the item with the key `"lastName"`. That is done by comparing the keys, so here that involves a string comparison. The hash table sees that this key belongs to the last item in the chain and returns the corresponding value, `"Jobs"`. +then this first hashes the key `"lastName"` to calculate the array index, which is 3. Bucket 3 has a chain, so we step through that list to find the value with the key `"lastName"`. That is done by comparing the keys, so here that involves a string comparison. The hash table sees that this key belongs to the last item in the chain and returns the corresponding value, `"Jobs"`. -Common ways to implement this chaining mechanism are to use a linked list or another array. Technically speaking the order of the items in the chain doesn't matter, so you also can think of it as a set instead of a list. (Now you can also imagine where the term "bucket" comes from.) +Common ways to implement this chaining mechanism are to use a linked list or another array. Technically speaking the order of the items in the chain doesn't matter, so you also can think of it as a set instead of a list. (Now you can also imagine where the term "bucket" comes from; we just dump all the objects together into the bucket.) -It's important that chains do not become too long or looking up items in the hash table becomes really slow. Ideally, we would have no chains at all but in practice it is impossible to avoid collisions. But you can improve the odds by giving the hash table enough buckets and by using high-quality hash functions. +It's important that chains do not become too long or looking up items in the hash table becomes really slow. Ideally, we would have no chains at all but in practice it is impossible to avoid collisions. You can improve the odds by giving the hash table enough buckets and by using high-quality hash functions. -> **Note:** An alternative to chaining is "open addressing". The idea is this: if an array index is already taken, we put the element in an unused bucket. Of course, this approach has its own upsides and downsides. +> **Note:** An alternative to chaining is "open addressing". The idea is this: if an array index is already taken, we put the element in the next unused bucket. Of course, this approach has its own upsides and downsides. ## The code @@ -129,9 +129,9 @@ public struct HashTable { } ``` -The `HashTable` is a generic container and the two generic types are named `Key` (which must be `Hashable`) and `Value`. We also define two other types: `Element` is a key/value pair for use in a chain and `Bucket` is an array of such `Element`s. +The `HashTable` is a generic container and the two generic types are named `Key` (which must be `Hashable`) and `Value`. We also define two other types: `Element` is a key/value pair for use in a chain and `Bucket` is an array of such `Elements`. -The main array is named `buckets`. It has a fixed size, the so-called capacity, provided bythe `init(capacity)` method. We're also keeping track of how many items have been added to the hash table using the `count` variable. +The main array is named `buckets`. It has a fixed size, the so-called capacity, provided by the `init(capacity)` method. We're also keeping track of how many items have been added to the hash table using the `count` variable. An example of how to create a new hash table object: @@ -197,9 +197,9 @@ This calls three helper functions to do the actual work. Let's take a look at `v } ``` -First it calls `indexForKey()` to convert the key into an array index. That gives us the bucket, but if there were collisions this bucket may be used by more than one key. So `valueForKey()` loops through the chain from that bucket and compares the keys one-by-one. If found, it returns the corresponding value, otherwise it returns `nil`. +First it calls `indexForKey()` to convert the key into an array index. That gives us the bucket number, but if there were collisions this bucket may be used by more than one key. So `valueForKey()` loops through the chain from that bucket and compares the keys one-by-one. If found, it returns the corresponding value, otherwise it returns `nil`. -The code to insert or update an existing element lives in `updateValue(forKey)`. It's a little bit more complicated: +The code to insert a new element or update an existing element lives in `updateValue(forKey)`. It's a little bit more complicated: ```swift public mutating func updateValue(value: Value, forKey key: Key) -> Value? { @@ -243,7 +243,7 @@ Removing is similar in that again it loops through the chain: } ``` -And these are the basic functions of the hash table. They all work the same way: convert the key into an array index using its hash value, then loop through the chain for that bucket and perform the desired operation. +These are the basic functions of the hash table. They all work the same way: convert the key into an array index using its hash value, find the bucket, then loop through that bucket's chain and perform the desired operation. Try this stuff out in a playground. It should work just like a standard Swift `Dictionary`. @@ -255,7 +255,7 @@ The *load factor* of a hash table is the percentage of the capacity that is curr If the hash table is too small and the chains are long, the load factor can become greater than 1. That's not a good idea. -If the load factor becomes too high, say > 75%, you can resize the hash table. Adding the code for this is left as an exercise for the reader. Keep in mind that making the buckets array larger will change the array indices that the keys map to! +If the load factor becomes too high, say > 75%, you can resize the hash table. Adding the code for this is left as an exercise for the reader. Keep in mind that making the buckets array larger will change the array indices that the keys map to! To account for this, you'll have to insert all the elements again after resizing the array. ## Where to go from here? diff --git a/Heap Sort/README.markdown b/Heap Sort/README.markdown index 84f978931..3d8b0f394 100644 --- a/Heap Sort/README.markdown +++ b/Heap Sort/README.markdown @@ -18,16 +18,16 @@ The heap's internal array is then: [ 25, 13, 20, 8, 7, 17, 2, 5, 4 ] -That's hardly what you'd call sorted! But now the sorting process starts: we swap the first element (index *0*) with the last one (index *n-1*) to get: +That's hardly what you'd call sorted! But now the sorting process starts: we swap the first element (index *0*) with the last one at index *n-1*, to get: [ 4, 13, 20, 8, 7, 17, 2, 5, 25 ] * * -Now the new root node, `4`, will be smaller than its children, so we fix up the max-heap up to element to *n-2* using the *shift down* or "heapify" procedure. After repairing the heap, the new root is now the second-largest item in the array: +Now the new root node, `4`, will be smaller than its children, so we fix up the max-heap up to element *n-2* using the *shift down* or "heapify" procedure. After repairing the heap, the new root is now the second-largest item in the array: [20, 13, 17, 8, 7, 4, 2, 5 | 25] -Important: When we fix the heap, we ignore the last item. That now contains the array's maximum value, so it is in its final sorted place already. The `|` bar indicates where the sorted portion of the array begins. We'll leave that part of the array alone from now on. +Important: When we fix the heap, we ignore the last item at index *n-1*. That now contains the array's maximum value, so it is in its final sorted place already. The `|` bar indicates where the sorted portion of the array begins. We'll leave that part of the array alone from now on. Again, we swap the first element with the last one (this time at index *n-2*): @@ -67,7 +67,7 @@ let a1 = h1.sort() Because we need a max-heap to sort from low-to-high, you need to give `Heap` the reverse of the sort function. To sort `<`, the `Heap` object must be created with `>` as the sort function. In other words, sorting from low-to-high creates a max-heap and turns it into a min-heap. -We can write a helper function for that: +We can write a handy helper function for that: ```swift public func heapsort(a: [T], _ sort: (T, T) -> Bool) -> [T] { diff --git a/Heap/README.markdown b/Heap/README.markdown index a51f97636..fb5092fe3 100644 --- a/Heap/README.markdown +++ b/Heap/README.markdown @@ -13,7 +13,7 @@ Common uses for heap: There are two kinds of heaps: a *max-heap* and a *min-heap*. They are identical, except that the order in which they store the tree nodes is opposite. -In a max-heap, parent nodes must always have a greater value than each one of their children. For a min-heap it's the other way around: every parent node has a smaller value than its child nodes. This is called the "heap property" and it is true for every single node in the tree. +In a max-heap, parent nodes must always have a greater value than each of their children. For a min-heap it's the other way around: every parent node has a smaller value than its child nodes. This is called the "heap property" and it is true for every single node in the tree. An example: @@ -21,19 +21,19 @@ An example: This is a max-heap because every parent node is greater than its children. `(10)` is greater than `(7)` and `(2)`. `(7)` is greater than `(5)` and `(1)`. -As a result of this heap property, a max-heap always stores its largest item at the root of the tree. For a min-heap, the root is always the smallest item in the tree. That is very useful because heaps are often used as a [priority queue](../Priority Queue/) where you want to quickly access the most important element. +As a result of this heap property, a max-heap always stores its largest item at the root of the tree. For a min-heap, the root is always the smallest item in the tree. That is very useful because heaps are often used as a [priority queue](../Priority Queue/) where you want to quickly access the "most important" element. -Note that you can't really say anything else about the sort order of the heap. For example, in a max-heap the maximum element is always at index 0 but the minimum element isn’t necessarily the last one -- the only guarantee you have is that it is one of the leaf nodes, but not which one. +> **Note:** You can't really say anything else about the sort order of the heap. For example, in a max-heap the maximum element is always at index 0 but the minimum element isn’t necessarily the last one -- the only guarantee you have is that it is one of the leaf nodes, but not which one. ## How does heap compare to regular trees? -A heap isn't really intended to be a replacement for a binary search tree. But there are many similarities between the two and also some differences. Here are some of the bigger differences: +A heap isn't intended to be a replacement for a binary search tree. But there are many similarities between the two and also some differences. Here are some of the bigger differences: **Order of the nodes.** In a [binary search tree (BST)](../Binary Search Tree/), the left child must always be smaller than its parent and the right child must be greater. This is not true for a heap. In max-heap both children must be smaller; in a min-heap they both must be greater. **Memory.** Traditional trees take up more memory than just the data they store. You need to allocate additional storage for the node objects and pointers to the left/right child nodes. A heap only uses a plain array for storage and uses no pointers. -**Balancing.** A binary search tree must be balanced so that most operations have **O(log n)** performance. You can either insert and delete your data in a random order or use something like an [AVL tree](../AVL Tree/) or [red-black tree](../Red-Black Tree/). But with heaps we don't actually need the entire tree to be sorted. We just want the heap property to be fulfilled, so balancing isn't an issue. Because of the way the heap is structured, heaps can guarantee **O(log n)** performance. +**Balancing.** A binary search tree must be "balanced" so that most operations have **O(log n)** performance. You can either insert and delete your data in a random order or use something like an [AVL tree](../AVL Tree/) or [red-black tree](../Red-Black Tree/). But with heaps we don't actually need the entire tree to be sorted. We just want the heap property to be fulfilled, and so balancing isn't an issue. Because of the way the heap is structured, heaps can guarantee **O(log n)** performance. **Searching.** Searching a binary tree is really fast -- that's its whole purpose. In a heap, searching is slow. The purpose of a heap is to always put the largest (or smallest) node at the front, and to allow relatively fast inserts and deletes. Searching isn't a top priority. @@ -77,17 +77,15 @@ Recall that in a max-heap, the parent's value is always greater than (or equal t array[parent(i)] >= array[i] ``` -Verify that this heap property holds for the array for the example heap. +Verify that this heap property holds for the array from the example heap. As you can see, these equations let us find the parent or child index for any node without the need for pointers. True, it's slightly more complicated than just dereferencing a pointer but that's the tradeoff: we save memory space but pay with extra computations. Fortunately, the computations are fast and only take **O(1)** time. -It's important to understand this relationship between array index and position in the tree. Here's a slightly larger heap: +It's important to understand this relationship between array index and position in the tree. Here's a slightly larger heap, this tree has 15 nodes divided over four levels: ![Large heap](Images/LargeHeap.png) -This tree has 15 nodes divided over four levels. Note that the numbers in this picture aren't the values of the nodes but the array indices that store the nodes! - -Those array indices correspond to the different levels of the tree like this: +The numbers in this picture aren't the values of the nodes but the array indices that store the nodes! Those array indices correspond to the different levels of the tree like this: ![The heap array](Images/Array.png) @@ -119,7 +117,7 @@ The heap property holds for each node because a parent is always smaller than it In case you are curious, here are a few more formulas that describe certain properties of a heap. You don't need to know these by heart but they come in handy sometimes. Feel free to skip this section! -The *height* of a tree is defined as the number of steps it takes to go from the root node to the lowest leaf node (or more formally: the maximum number of edges between the nodes). A heap of height *h* has *h + 1* levels. +The *height* of a tree is defined as the number of steps it takes to go from the root node to the lowest leaf node. Or more formally: the height is the maximum number of edges between the nodes. A heap of height *h* has *h + 1* levels. This heap has height 3 and therefore has 4 levels: @@ -143,9 +141,9 @@ There are two primitive operations necessary to make sure the heap is a valid ma - `shiftUp()`: If the element is greater (max-heap) or smaller (min-heap) than its parent, it needs to be swapped with the parent. This makes it move up the tree. -- `shiftDown()`. If the element is smaller (max-heap) or greater (min-heap) than its children, it needs to move down the tree. This is also called "heapify". +- `shiftDown()`. If the element is smaller (max-heap) or greater (min-heap) than its children, it needs to move down the tree. This operation is also called "heapify". -Shifting is a recursive procedure that takes **O(log n)** time. +Shifting up or down is a recursive procedure that takes **O(log n)** time. The other operations are built on these primitives. They are: @@ -155,7 +153,7 @@ The other operations are built on these primitives. They are: - `removeAtIndex(index)`: Just like `remove()` except it lets you remove any item from the heap, not just the root. This calls both `shiftDown()`, in case the new element is out-of-order with its children, and `shiftUp()`, in case the element is out-of-order with its parents. -- `replace(index, value)`: Assigns a smaller (min-heap) or larger (max-heap) value to a node. Because this invalidates the heap property, this uses `shiftUp()` to patch things up. (Also called "decrease key" and "increase key".) +- `replace(index, value)`: Assigns a smaller (min-heap) or larger (max-heap) value to a node. Because this invalidates the heap property, it uses `shiftUp()` to patch things up. (Also called "decrease key" and "increase key".) All of the above take time **O(log n)** because shifting up or down is the most expensive thing they do. There are also a few operations that take more time: @@ -205,7 +203,7 @@ The time required for shifting up is proportional to the height of the tree so i ## Removing the root -Let's remove 10 from this tree: +Let's remove `(10)` from this tree: ![The heap before removal](Images/Heap1.png) @@ -217,7 +215,7 @@ When inserting, we put the new value at the end of the array. Here, we'll do the ![The last node goes to the root](Images/Remove2.png) -Let's look at how to **shift-down** `(1)`. To maintain the heap property for this max-heap, we want to the highest number of top. We have 2 candidates for swapping places with: `(7)` and `(2)`. We choose the highest number between these three nodes to be on top. This is `(7)`, so swapping `(1)` and `(7)` gives us the following tree. +Let's look at how to **shift-down** `(1)`. To maintain the heap property for this max-heap, we want to the highest number of top. We have two candidates for swapping places with: `(7)` and `(2)`. We choose the highest number between these three nodes to be on top. That is `(7)`, so swapping `(1)` and `(7)` gives us the following tree. ![The last node goes to the root](Images/Remove3.png) @@ -249,7 +247,7 @@ As you know, removing an element could potentially invalidate the max-heap or mi The last element is the one that we'll return; we'll call `removeLast()` to remove it from the heap. The `(1)` is now out-of-order because it's smaller than its child, `(5)`, but sits higher in the tree. We call `shiftDown()` to repair this. -However, it may also happen that the new element must be shifted up. Consider what happens if you remove `(5)` from the following heap: +However, shifting down is not the only situation we need to handle -- it may also happen that the new element must be shifted up. Consider what happens if you remove `(5)` from the following heap: ![We need to shift up](Images/Remove5.png) @@ -271,7 +269,7 @@ In code it would look like this: We simply call `insert()` for each of the values in the array. Simple enough, but not very efficient. This takes **O(n log n)** time in total because there are **n** elements and each insertion takes **log n** time. -If you didn't skip the math section, you'd have seen that for any heap the elements at array indices *n/2* to *n-1* are the leaves of the tree. We can simply skip those leaves. We only have to process the other nodes, since they are parents with one or more children and therefore may be in the wrong order. +If you didn't gloss over the math section, you'd have seen that for any heap the elements at array indices *n/2* to *n-1* are the leaves of the tree. We can simply skip those leaves. We only have to process the other nodes, since they are parents with one or more children and therefore may be in the wrong order. In code: @@ -288,7 +286,7 @@ Here, `elements` is the heap's own array. We walk backwards through this array, ## Searching the heap -Heaps aren't made for fast searches, but if you want to remove an arbitrary item using `removeAtIndex()` or change the value of an item with `replace()`, then you need to obtain the index of that item somehow. Searching is one way to do this but it's kind of slow. +Heaps aren't made for fast searches, but if you want to remove an arbitrary element using `removeAtIndex()` or change the value of an element with `replace()`, then you need to obtain the index of that element somehow. Searching is one way to do this but it's kind of slow. In a [binary search tree](../Binary Search Tree/) you can depend on the order of the nodes to guarantee a fast search. A heap orders its nodes differently and so a binary search won't work. You'll potentially have to look at every node in the tree. @@ -298,15 +296,13 @@ Let's take our example heap again: If we want to search for the index of node `(1)`, we could just step through the array `[ 10, 7, 2, 5, 1 ]` with a linear search. -But even though the heap property wasn't conceived with searching in mind, we can still take advantage of it. Because we know that in a max-heap a parent node is always larger than its children. - -Let's say we want to see if the heap contains the value `8` (it doesn't). We start at the root `(10)`. This is obviously not what we're looking for, so we recursively look at its left and right child. +But even though the heap property wasn't conceived with searching in mind, we can still take advantage of it. We know that in a max-heap a parent node is always larger than its children, so we can ignore those children (and their children, and so on...) if the parent is already smaller than the value we're looking for. -The left child is `(7)`. That is also not what we want, but since this is a max-heap, we know there's no point in looking at the children of `(7)`. They will always be smaller than `7` and are therefore never equal to `8`. Likewise for the right child, `(2)`. +Let's say we want to see if the heap contains the value `8` (it doesn't). We start at the root `(10)`. This is obviously not what we're looking for, so we recursively look at its left and right child. The left child is `(7)`. That is also not what we want, but since this is a max-heap, we know there's no point in looking at the children of `(7)`. They will always be smaller than `7` and are therefore never equal to `8`. Likewise for the right child, `(2)`. Despite this small optimization, searching is still an **O(n)** operation. -> **Note:** There is away to turn this into **O(1)** by keeping an additional dictionary that maps node values to indices. This may be worth doing if you often need to call `replace()` to change the "priority" of objects in a [priority queue](../Priority Queue/) that's built on a heap. +> **Note:** There is away to turn lookups into a **O(1)** operation by keeping an additional dictionary that maps node values to indices. This may be worth doing if you often need to call `replace()` to change the "priority" of objects in a [priority queue](../Priority Queue/) that's built on a heap. ## The code diff --git a/Huffman Coding/Images/BuildTree.gif b/Huffman Coding/Images/BuildTree.gif index ddfce5c9b..6f5e51523 100644 Binary files a/Huffman Coding/Images/BuildTree.gif and b/Huffman Coding/Images/BuildTree.gif differ diff --git a/Huffman Coding/Images/BuildTree.psd b/Huffman Coding/Images/BuildTree.psd index f3145aa44..e13923fb3 100644 Binary files a/Huffman Coding/Images/BuildTree.psd and b/Huffman Coding/Images/BuildTree.psd differ diff --git a/Huffman Coding/Images/Compression.graffle b/Huffman Coding/Images/Compression.graffle index 81920ac70..e69912012 100644 Binary files a/Huffman Coding/Images/Compression.graffle and b/Huffman Coding/Images/Compression.graffle differ diff --git a/Huffman Coding/Images/Compression.png b/Huffman Coding/Images/Compression.png index 924e899aa..e241d15c9 100644 Binary files a/Huffman Coding/Images/Compression.png and b/Huffman Coding/Images/Compression.png differ diff --git a/Huffman Coding/Images/Decompression.graffle b/Huffman Coding/Images/Decompression.graffle index dcfd906bd..dd33b5faf 100644 Binary files a/Huffman Coding/Images/Decompression.graffle and b/Huffman Coding/Images/Decompression.graffle differ diff --git a/Huffman Coding/Images/Decompression.png b/Huffman Coding/Images/Decompression.png index 88f9edeb2..a2946a621 100644 Binary files a/Huffman Coding/Images/Decompression.png and b/Huffman Coding/Images/Decompression.png differ diff --git a/Huffman Coding/README.markdown b/Huffman Coding/README.markdown index 3f78aa203..528ba3a1d 100644 --- a/Huffman Coding/README.markdown +++ b/Huffman Coding/README.markdown @@ -41,7 +41,7 @@ Now if we replace the original bytes with these bit strings, the compressed outp The extra 0-bit at the end is there to make a full number of bytes. We were able to compress the original 34 bytes into merely 16 bytes, a space savings of over 50%! -But hold on... to be able to decode these bits we need to have the original frequency table. That table needs to be transmitted or saved along with the compressed data, otherwise the decoder doesn't know how to interpret the bits. Because of the overhead of this frequency table, it doesn't really pay to use Huffman encoding on very small inputs. +But hold on... to be able to decode these bits we need to have the original frequency table. That table needs to be transmitted or saved along with the compressed data, otherwise the decoder doesn't know how to interpret the bits. Because of the overhead of this frequency table (about 1 kilobyte), it doesn't really pay to use Huffman encoding on very small inputs. ## How it works @@ -63,7 +63,7 @@ Decompression works in exactly the opposite way. It reads the compressed bits on ## The code -Before we get to the actual Huffman coding scheme, it's useful to have some helper code that can write individual bits to an `NSData` object. The smallest piece of data that `NSData` understands is the byte. But we're dealing in bits, so we need to translate between the two. +Before we get to the actual Huffman coding scheme, it's useful to have some helper code that can write individual bits to an `NSData` object. The smallest piece of data that `NSData` understands is the byte, but we're dealing in bits, so we need to translate between the two. ```swift public class BitWriter { @@ -92,7 +92,7 @@ public class BitWriter { } ``` -To add a bit to the `NSData` you call `writeBit()`. This simply stuffs each new bit into the `outByte` variable. Once you've written 8 bits, `outByte` gets added to the `NSData` object for real. +To add a bit to the `NSData` you call `writeBit()`. This stuffs each new bit into the `outByte` variable. Once you've written 8 bits, `outByte` gets added to the `NSData` object for real. The `flush()` method is used for outputting the very last byte. There is no guarantee that the number of compressed bits is a nice round multiple of 8, in which case there may be some spare bits at the end. If so, `flush()` adds a few 0-bits to make sure that we write a full byte. @@ -193,7 +193,7 @@ Instead, we'll add a method to export the frequency table without all the pieces } ``` -The `frequencyTable()` method looks at those first 256 nodes from the tree but keeps only those that are actually used, without the `parent`, `left`, and `right` pointers. You would serialize the array it returns along with the compressed file so that it can be properly decompressed later. +The `frequencyTable()` method looks at those first 256 nodes from the tree but keeps only those that are actually used, without the `parent`, `left`, and `right` pointers. It returns an array of `Freq` objects. You have to serialize this array along with the compressed data so that it can be properly decompressed later. ## The tree @@ -206,7 +206,7 @@ The leaf nodes represent the actual bytes that are present in the input data. Th To build the tree, we do the following: 1. Find the two nodes with the smallest counts that do not have a parent node yet. -2. Add a new parent node that links these two nodes together. +2. Create a new parent node that links these two nodes together. 3. This repeats over and over until only one node with no parent remains. This becomes the root node of the tree. This is an ideal place to use a [priority queue](../Priority Queue/). A priority queue is a data structure that is optimized so that finding the minimum value is always very fast. Here, we repeatedly need to find the node with the smallest count. @@ -256,13 +256,13 @@ Here is how it works step-by-step: 6. Repeat steps 2-5 until there is only one node left in the queue. This becomes the root node of the tree, and we're done. -This is what the process looks like: +The animation shows what the process looks like: ![Building the tree](Images/BuildTree.gif) -> **Note:** Instead of using a priority queue, you can iterate through the `tree` array to find the next two smallest nodes, but that makes the compressor quite slow, **O(n^2)**. Using the priority queue, the running time is only **O(n log n)** where **n** is the number of nodes. +> **Note:** Instead of using a priority queue, you can repeatedly iterate through the `tree` array to find the next two smallest nodes, but that makes the compressor quite slow, **O(n^2)**. Using the priority queue, the running time is only **O(n log n)** where **n** is the number of nodes. -> **Note:** Due to the nature of binary trees, if we have *x* leaf nodes we can at most add *x - 1* additional nodes to the tree. Given that at most there will be 256 leaf nodes, the tree will never contain more than 511 nodes total. +> **Fun fact:** Due to the nature of binary trees, if we have *x* leaf nodes we can at most add *x - 1* additional nodes to the tree. Given that at most there will be 256 leaf nodes, the tree will never contain more than 511 nodes total. ## Compression @@ -319,7 +319,7 @@ In a picture: ![How compression works](Images/Compression.png) -Note that, even through the illustration of the tree shows a 0 or 1 for each edge between the nodes, the bit values 0 and 1 aren't actually stored in the tree! The rule is that we write a 1 bit if we take the left branch and a 0 bit if we take the right branch, so just knowing the direction we're going in is enough to determine what bit value to write. +Even though the illustration of the tree shows a 0 or 1 for each edge between the nodes, the bit values 0 and 1 aren't actually stored in the tree! The rule is that we write a 1 bit if we take the left branch and a 0 bit if we take the right branch, so just knowing the direction we're going in is enough to determine what bit value to write. You use the `compressData()` method as follows: @@ -412,6 +412,6 @@ You can see how this works in more detail in the Playground. [Huffman coding at Wikipedia](https://en.wikipedia.org/wiki/Huffman_coding) -The code is based on Al Stevens' C Programming column from Dr.Dobb's Magazine, February 1991 and October 1992. +The code is loosely based on Al Stevens' C Programming column from Dr.Dobb's Magazine, February 1991 and October 1992. *Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Insertion Sort/README.markdown b/Insertion Sort/README.markdown index ea39f5dad..b67541669 100644 --- a/Insertion Sort/README.markdown +++ b/Insertion Sort/README.markdown @@ -13,7 +13,7 @@ You are given an array of numbers and need to put them in the right order. The i That's why this is called an "insertion" sort, because you take a number from the pile and insert it in the array in its proper sorted position. -### An example +## An example Let's say the numbers to sort are `[ 8, 3, 5, 4, 6 ]`. This is our unsorted pile. @@ -25,7 +25,7 @@ Pick the next number from the pile, `5`, and insert it into the sorted array. It Repeat this process until the pile is empty. -### In-place sort +## In-place sort The above explanation makes it seem like you need two arrays: one for the unsorted pile and one that contains the numbers in sorted order. @@ -54,7 +54,7 @@ This is how the content of the array changes during the sort: In each step, the `|` bar moves up one position. As you can see, the beginning of the array up to the `|` is always sorted. The pile shrinks by one and the sorted portion grows by one, until the pile is empty and there are no more unsorted numbers left. -### How to insert +## How to insert At each step you pick the top-most number from the unsorted pile and insert it into the sorted portion of the array. You must put that number in the proper place so that the beginning of the array remains sorted. How does that work? @@ -85,9 +85,9 @@ Again, look at the previous element. Is `3` greater than `4`? No, it is not. Tha This was a description of the inner loop of the insertion sort algorithm, which you'll see in the next section. It inserts the number from the top of the pile into the sorted portion by swapping numbers. -### The code +## The code -Here is a simple implementation of insertion sort in Swift: +Here is an implementation of insertion sort in Swift: ```swift func insertionSort(array: [Int]) -> [Int] { @@ -118,9 +118,9 @@ Here is how the code works. 3. The inner loop looks at the element at position `x`. This is the number at the top of the pile, and it may be smaller than any of the previous elements. The inner loop steps backwards through the sorted array; every time it finds a previous number that is larger, it swaps them. When the inner loop completes, the beginning of the array is sorted again, and the sorted portion has grown by one element. -Note: The outer loop starts at index 1, not 0. Moving the very first element from the pile to the sorted portion doesn't actually change anything, so we might as well skip it. +> **Note:** The outer loop starts at index 1, not 0. Moving the very first element from the pile to the sorted portion doesn't actually change anything, so we might as well skip it. -### No more swaps +## No more swaps The above version of insertion sort works fine, but it can be made a tiny bit faster by removing the call to `swap()`. @@ -168,7 +168,7 @@ func insertionSort(array: [Int]) -> [Int] { The line at `//1` is what shifts up the previous elements by one position. At the end of the inner loop, `y` is the destination index for the new number in the sorted portion, and the line at `//2` copies this number into place. -### Making it generic +## Making it generic It would be nice to sort other things than just numbers. We can make the datatype of the array generic and use a user-supplied function (or closure) to perform the less-than comparison. This only requires two changes to the code. @@ -188,7 +188,7 @@ The only other change is in the inner loop, which now becomes: while y > 0 && isOrderedBefore(temp, a[y - 1]) { ``` -Instead of writing `temp < a[y - 1]`, we now call the `isOrderedBefore()` function. It does the exact same thing, except we can now compare any kind of object, not just numbers. +Instead of writing `temp < a[y - 1]`, we call the `isOrderedBefore()` function. It does the exact same thing, except we can now compare any kind of object, not just numbers. To test this in a playground, do: @@ -218,7 +218,7 @@ The closure tells `insertionSort()` to sort on the `priority` property of the ob Insertion sort is a *stable* sort. A sort is stable when elements that have identical sort keys remain in the same relative order after sorting. This is not important for simple values such as numbers or strings, but it is important when sorting more complex objects. In the example above, if two objects have the same `priority`, regardless of the values of their other properties, those two objects don't get swapped around. -### Performance +## Performance Insertion sort is really fast if the array is already sorted. That sounds obvious, but this is not true for all search algorithms. In practice, a lot of data will already be largely -- if not entirely -- sorted and insertion sort works quite well in that case. @@ -228,8 +228,8 @@ Insertion sort is actually very fast for sorting small arrays. Some standard lib I did a quick test comparing our `insertionSort()` with Swift's built-in `sort()`. On arrays of about 100 items or so, the difference in speed is tiny. However, as your input becomes larger, **O(n^2)** quickly starts to perform a lot worse than **O(n log n)** and insertion sort just can't keep up. -### See also +## See also -See also [Wikipedia](https://en.wikipedia.org/wiki/Insertion_sort). +[Insertion sort on Wikipedia](https://en.wikipedia.org/wiki/Insertion_sort) -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Kth Largest Element/README.markdown b/Kth Largest Element/README.markdown index 04d10ad99..d50d691a1 100644 --- a/Kth Largest Element/README.markdown +++ b/Kth Largest Element/README.markdown @@ -6,21 +6,21 @@ For example, the 1-st largest element is the maximum value that occurs in the ar ## The naive solution -The following solution is semi-naive. Its time complexity is **O(n log n)** since it first sorts the array, and uses an additional **O(n)** space. Better solutions using heaps exist that run in **O(n)** time. +The following solution is semi-naive. Its time complexity is **O(n log n)** since it first sorts the array, and therefore also uses additional **O(n)** space. ```swift func kthLargest(a: [Int], k: Int) -> Int? { let len = a.count - - if k <= 0 || k > len || len < 1 { return nil } - - let sorted = a.sort() - - return sorted[len - k] + if k > 0 && k <= len { + let sorted = a.sort() + return sorted[len - k] + } else { + return nil + } } ``` -The `kthLargest()` function takes two parameters: the array `a` consisting of integers, and `k`. It returns the k-th largest element. +The `kthLargest()` function takes two parameters: the array `a` consisting of integers, and `k`. It returns the *k*-th largest element. Let's take a look at an example and run through the algorithm to see how it works. Given `k = 4` and the array: @@ -48,9 +48,9 @@ There is a clever algorithm that combines the ideas of [binary search](../Binary Recall that binary search splits the array in half over and over again, to quickly narrow in on the value you're searching for. That's what we'll do here too. -Quicksort also splits up arrays. It uses partitioning to move all smaller values to the left of the pivot and all greater values to the right. After partitioning around a certain pivot, that pivot will already be in its final, sorted position. We can use that to our advantage here. +Quicksort also splits up arrays. It uses partitioning to move all smaller values to the left of the pivot and all greater values to the right. After partitioning around a certain pivot, that pivot value will already be in its final, sorted position. We can use that to our advantage here. -We choose a random pivot, partition the array around that pivot, and then act like a binary search and only continue in the left or right partition. This repeats until we've found a pivot that happens to end up in the *k*-th position. +Here's how it works: We choose a random pivot, partition the array around that pivot, then act like a binary search and only continue in the left or right partition. This repeats until we've found a pivot that happens to end up in the *k*-th position. Let's look at the original example again. We're looking for the 4-th largest element in this array: @@ -58,12 +58,12 @@ Let's look at the original example again. We're looking for the 4-th largest ele The algorithm is a bit easier to follow if we look for the k-th *smallest* item instead, so let's take `k = 4` and look for the 4-th smallest element. -Note that we don't have to sort the array first. We pick a random pivot, let's say `11` and partition the array around that. We might end up with something like this: +Note that we don't have to sort the array first. We pick one of the elements at random to be the pivot, let's say `11`, and partition the array around that. We might end up with something like this: [ 7, 9, -1, 0, 6, 11, 92, 23 ] <------ smaller larger --> -As you can see, all values smaller than `11` are on the left; all values larger are on the right. The pivot value is now in its final place. The index of the pivot is 5, so the 4-th smallest element must be in the left partition somewhere. We can ignore the rest of the array from now on: +As you can see, all values smaller than `11` are on the left; all values larger are on the right. The pivot value `11` is now in its final place. The index of the pivot is 5, so the 4-th smallest element must be in the left partition somewhere. We can ignore the rest of the array from now on: [ 7, 9, -1, 0, 6, x, x, x ] @@ -126,15 +126,15 @@ public func randomizedSelect(array: [T], order k: Int) -> T { } ``` -To keep things readable, it splits up the functionality into three inner functions: +To keep things readable, the functionality is split into three inner functions: - `randomPivot()` picks a random number and puts it at the end of the current partition (this is a requirement of the Lomuto partitioning scheme, see the discussion on [quicksort](../Quicksort/) for more details). - `randomizedPartition()` is Lomuto's partitioning scheme from quicksort. When this completes, the randomly chosen pivot is in its final sorted position in the array. It returns the array index of the pivot. -- `randomizedSelect()` does all the hard work. It first calls the partitioning function and then decides what to do next. If the index of the pivot is equal to the k-th number we're looking for, we're done. If `k` is less than the pivot index, it must be in the left partition and we'll recursively try again there. Likewise for when the k-th number must be in the right partition. +- `randomizedSelect()` does all the hard work. It first calls the partitioning function and then decides what to do next. If the index of the pivot is equal to the *k*-th number we're looking for, we're done. If `k` is less than the pivot index, it must be in the left partition and we'll recursively try again there. Likewise for when the *k*-th number must be in the right partition. -Pretty cool, huh? +Pretty cool, huh? Normally quicksort is an **O(n log n)** algorithm, but because we only partition smaller and smaller slices of the array, the running time of `randomizedSelect()` works out to **O(n)**. > **Note:** This function calculates the *k*-th smallest item in the array, where *k* starts at 0. If you want the *k*-th largest item, call it with `a.count - k`. diff --git a/Kth Largest Element/kthLargest.playground/Contents.swift b/Kth Largest Element/kthLargest.playground/Contents.swift index 9cac62f4e..7f97bb271 100644 --- a/Kth Largest Element/kthLargest.playground/Contents.swift +++ b/Kth Largest Element/kthLargest.playground/Contents.swift @@ -2,13 +2,14 @@ func kthLargest(a: [Int], k: Int) -> Int? { let len = a.count - if k <= 0 || k > len || len < 1 { return nil } - - let sorted = a.sort() - return sorted[len - k] + if k > 0 && k <= len { + let sorted = a.sort() + return sorted[len - k] + } else { + return nil + } } - let a = [5, 1, 3, 2, 7, 6, 4] kthLargest(a, k: 0) diff --git a/Kth Largest Element/kthLargest.swift b/Kth Largest Element/kthLargest.swift index 69c03b76f..5bf781932 100644 --- a/Kth Largest Element/kthLargest.swift +++ b/Kth Largest Element/kthLargest.swift @@ -1,17 +1,20 @@ import Foundation /* - * Returns the k-th largest value inside of an array a. - * This is an O(n log n) solution since we sort the array. - */ + Returns the k-th largest value inside of an array a. + This is an O(n log n) solution since we sort the array. +*/ func kthLargest(a: [Int], k: Int) -> Int? { let len = a.count - if k <= 0 || k > len || len < 1 { return nil } - - let sorted = a.sort() - return sorted[len - k] + if k > 0 && k <= len { + let sorted = a.sort() + return sorted[len - k] + } else { + return nil + } } + // MARK: - Randomized selection /* Returns a random integer in the range min...max, inclusive. */ diff --git a/Linear Search/README.markdown b/Linear Search/README.markdown index 178a1dd09..115dd7ee2 100644 --- a/Linear Search/README.markdown +++ b/Linear Search/README.markdown @@ -2,15 +2,17 @@ Goal: Find a particular value in an array. -We have an array of generic objects and we iterate over all the objects by comparing each one to the object we're looking for. If the two objects are equal, we stop and we return the index of the object in the array. If not, we continue to look for the next object as long as we have objects in the array. +We have an array of generic objects. With linear search, we iterate over all the objects in the array and compare each one to the object we're looking for. If the two objects are equal, we stop and return the current array index. If not, we continue to look for the next object as long as we have objects in the array. -### An example +## An example Let's say we have an array of numbers `[5, 2, 4, 7]` and we want to check if the array contains the number `2`. -We start by comparing the first number in the array, `5` with the number we're looking for, `2`. They are obviously not the same number and so we continue by taking the second element in the array. We compare the number `2` to our number `2` and we notice that they are the same. We stop our iteration and we return `1`, which is the index of the number `2` in the array. +We start by comparing the first number in the array, `5`, to the number we're looking for, `2`. They are obviously not the same, and so we continue to the next array element. -### The code +We compare the number `2` from the array to our number `2` and notice they are equal. Now we can stop our iteration and return 1, which is the index of the number `2` in the array. + +## The code Here is a simple implementation of linear search in Swift: @@ -30,14 +32,14 @@ let array = [5, 2, 4, 7] linearSearch(array, 2) // This will return 1 ``` -### Performance +## Performance -Linear search runs at **O(n)**. It compares the object we are looking for with each object in the array and so the time it takes is proportional to the array length. +Linear search runs at **O(n)**. It compares the object we are looking for with each object in the array and so the time it takes is proportional to the array length. In the worst case, we need to look at all the elements in the array. -The best-case performance is **O(1)** but this case is rare because the object we're looking for has to be positioned at the start of the array to be immediately found. +The best-case performance is **O(1)** but this case is rare because the object we're looking for has to be positioned at the start of the array to be immediately found. You might get lucky, but most of the time you won't. On average, linear search needs to look at half the objects in the array. -### See also +## See also -See also [Wikipedia](https://en.wikipedia.org/wiki/Linear_search). +[Linear search on Wikipedia](https://en.wikipedia.org/wiki/Linear_search) *Written by [Patrick Balestra](http://www.github.com/BalestraPatrick)* diff --git a/Linked List/README.markdown b/Linked List/README.markdown index 0ce6c57fa..d37749acd 100644 --- a/Linked List/README.markdown +++ b/Linked List/README.markdown @@ -1,6 +1,6 @@ # Linked List -A linked list is a sequence of data items, just like an array. But where an array allocates a big block of memory and then divides this into different parts to store the objects, the elements in a linked list are totally separate objects in memory and are connected through links: +A linked list is a sequence of data items, just like an array. But where an array allocates a big block of memory to store the objects, the elements in a linked list are totally separate objects in memory and are connected through links: +--------+ +--------+ +--------+ +--------+ | | | | | | | | @@ -38,15 +38,17 @@ This means that when you're dealing with a linked list, you should insert new it ## Singly vs doubly linked lists -A singly linked list uses less memory than a doubly linked list because it doesn't need to store all those `previous` pointers. +A singly linked list uses a little less memory than a doubly linked list because it doesn't need to store all those `previous` pointers. -But if you have a node and you need to find its previous node, you're screwed. You have to start back at the head of the list and iterate through the entire list until you get to the right node. +But if you have a node and you need to find its previous node, you're screwed. You have to start at the head of the list and iterate through the entire list until you get to the right node. For many tasks, a doubly linked list makes things easier. ## Why use a linked list? -An example of where to use a linked list is when you need a [queue](../Queue/). With an array, removing elements from the front of the queue is slow because it needs to shift down all the other elements in memory. But with a linked list it's just a matter of changing `head` to point to the second element. Much faster. +A typical example of where to use a linked list is when you need a [queue](../Queue/). With an array, removing elements from the front of the queue is slow because it needs to shift down all the other elements in memory. But with a linked list it's just a matter of changing `head` to point to the second element. Much faster. + +But to be honest, you hardly ever need to write your own linked list these days. Still, it's useful to understand how they work; the principle of linking objects together is also used with [trees](../Tree/) and [graphs](../Graph/). ## The code @@ -86,7 +88,7 @@ public class LinkedList { } ``` -Ideally, I'd like to put the `LinkedListNode` class inside `LinkedList` but Swift 2 doesn't allow generic types to have nested types. Instead we're using a typealias so inside `LinkedList` we can write the shorter `Node` instead of `LinkedListNode`. +Ideally, I'd like to put the `LinkedListNode` class inside `LinkedList` but Swift currently doesn't allow generic types to have nested types. Instead we're using a typealias so inside `LinkedList` we can write the shorter `Node` instead of `LinkedListNode`. This linked list only has a `head` pointer, not a tail. Adding a tail pointer is left as an exercise for the reader. (I'll point out which functions would be different if we also had a tail pointer.) @@ -329,13 +331,13 @@ Now that we have this helper function, we can write the method for inserting nod } ``` -Some notes about this method: +Some remarks about this method: 1. First, we need to find where to insert this node. After calling the helper method, `prev` points to the previous node and `next` is the node currently at the given index. We'll insert the new node in between these two. Note that `prev` can be nil (index is 0), `next` can be nil (index equals size of the list), or both can be nil if the list is empty. 2. Create the new node and connect the `previous` and `next` pointers. Because the local `prev` and `next` variables are optionals and may be nil, so we use optional chaining here. -3. If the new node is being inserted at the front of the list, we need to update the `head` pointer. Note: If the list had a tail pointer, you'd also need to update that pointer here if `next == nil`, because that means the last element has changed. +3. If the new node is being inserted at the front of the list, we need to update the `head` pointer. (Note: If the list had a tail pointer, you'd also need to update that pointer here if `next == nil`, because that means the last element has changed.) Try it out: @@ -419,7 +421,7 @@ list.removeAtIndex(0) // "Swift" list.count // 0 ``` -> **Note:** For a singly linked list, it's slightly more complicated. You can't just use `last` to find the end of the list because you also need a reference to the second-to-last node. You'd need to use the `nodesBeforeAndAfter()` helper method instead. If you're using a tail pointer, then `removeLast()` is really quick, but you do need to remember to make `tail` point to the previous node. +> **Note:** For a singly linked list, removing the last node is slightly more complicated. You can't just use `last` to find the end of the list because you also need a reference to the second-to-last node. Instead, use the `nodesBeforeAndAfter()` helper method. If the list has a tail pointer, then `removeLast()` is really quick, but you do need to remember to make `tail` point to the previous node. There's a few other fun things we can do with our `LinkedList` class. It's handy to have some sort of readable debug output: @@ -520,7 +522,7 @@ Exercise for the reader: These implementations of `map()` and `filter()` aren't ## An alternative approach -The version of `LinkedList` you've seen so far uses nodes that are classes and therefore use reference semantics. Nothing wrong with that, but that does make them a bit more heavyweight than Swift's other collections such as `Array` and `Dictionary`. +The version of `LinkedList` you've seen so far uses nodes that are classes and therefore have reference semantics. Nothing wrong with that, but that does make them a bit more heavyweight than Swift's other collections such as `Array` and `Dictionary`. It is possible to implement a linked list with value semantics using an enum. That would look somewhat like this: @@ -541,6 +543,6 @@ Linked lists are flexible but many operations are **O(n)**. When performing operations on a linked list, you always need to be careful to update the relevant `next` and `previous` pointers, and possibly also the `head` and `tail` pointers. If you mess this up, your list will no longer be correct and your program will likely crash at some point. Be careful! -When processing lists, you can often use recursion: process the first element and then recursively call the operation on the rest of the list. You’re done when there is no next element. This is why linked lists are the foundation of functional programming languages such as LISP. +When processing lists, you can often use recursion: process the first element and then recursively call the function again on the rest of the list. You’re done when there is no next element. This is why linked lists are the foundation of functional programming languages such as LISP. *Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Merge Sort/README.markdown b/Merge Sort/README.markdown index 190f76a34..023e18b32 100644 --- a/Merge Sort/README.markdown +++ b/Merge Sort/README.markdown @@ -4,28 +4,28 @@ Goal: Sort an array from low to high (or high to low) Invented in 1945 by John von Neumann, merge sort is a fairly efficient sorting algorithm with a best, worst, and average time complexity of **O(n log n)**. -The idea behind merge sort is to **divide and conquer**. To divide a big problem into smaller problems and solving many small problems instead of solving a big one. I think of merge sort as **split first** and **merge after**. +The idea behind merge sort is to **divide and conquer**: to divide a big problem into smaller problems and solving many small problems instead of solving a big one. I think of merge sort as **split first** and **merge after**. Assume you're given an array of *n* numbers and you need to put them in the right order. The merge sort algorithm works as follows: - Put the numbers in a pile. The pile is unsorted. - Split the pile into 2. Now you have **two unsorted piles** of numbers. -- Keep splitting the resulting piles until you can't anymore. In the end, you will have *n* piles with 1 number in each pile. -- Begin to **merge** the piles together by sequentially pairing a pile with another pile. During each merge, you want to sort the contents in order. +- Keep splitting the resulting piles until you can't split anymore. In the end, you will have *n* piles with 1 number in each pile. +- Begin to **merge** the piles together by sequentially pairing a pile with another pile. During each merge, you put the contents in sorted order. This is fairly easy because each individual pile is already sorted. ## An example ### Splitting -Let's say the numbers to sort are `[2, 1, 5, 4, 9]`. This is your unsorted pile. Our goal is to keep splitting the pile until you can't anymore. +Let's say the numbers to sort are `[2, 1, 5, 4, 9]`. This is your unsorted pile. The goal is to keep splitting the pile until you can't split anymore. -Split the array into two halves: `[2, 1,]` and `[5, 4, 9]`. Can you keep splitting them? Yes you can! +First, split the array into two halves: `[2, 1,]` and `[5, 4, 9]`. Can you keep splitting them? Yes you can! Focus on the left pile. `[2, 1]` will split into `[2]` and `[1]`. Can you keep splitting them? No. Time to check the other pile. -`[5, 4, 9]` splits to `[5]` and `[4, 9]`. Unsurprisingly, `[5]` can't split into anymore, but `[4, 9]` splits into `[4]` and `[9]`. +`[5, 4, 9]` splits to `[5]` and `[4, 9]`. Unsurprisingly, `[5]` can't be split anymore, but `[4, 9]` splits into `[4]` and `[9]`. -The splitting process ends with the following piles: `[2]` `[1]` `[5]` `[4]` `[9]` +The splitting process ends with the following piles: `[2]` `[1]` `[5]` `[4]` `[9]`. Notice that each pile consists of just one element. ### Merging @@ -35,7 +35,7 @@ Given the piles `[2]` `[1]` `[5]` `[4]` `[9]`, the first pass will result in `[1 The next pass will merge `[1, 2]` and `[4, 5]` together. This results in `[1, 2, 4, 5]`, with the `[9]` left out again since it's the odd one out. -Since you're left with only two piles, `[9]` finally gets its chance to merge, resulting in the sorted array `[1, 2, 4, 5, 9]`. +You're left with only two piles and `[9]` finally gets its chance to merge, resulting in the sorted array `[1, 2, 4, 5, 9]`. ## Top-down implementation @@ -61,9 +61,9 @@ A step-by-step explanation of how the code works: 2. Find the middle index. -3. Using the middle index from the previous step, recursively split the left side of the resulting arrays. +3. Using the middle index from the previous step, recursively split the left side of the array. -4. Using the middle index, recursively split the right side of the resulting arrays. +4. Also recursively split the right side of the array. 5. Finally, merge all the values together, making sure that it's always sorted. @@ -119,7 +119,7 @@ This method may look scary but it is quite straightforward: 4. If control exits from the previous while loop, it means that either `leftPile` or `rightPile` has its contents completely merged into the `orderedPile`. At this point, you no longer need to do comparisons. Just append the rest of the contents of the other array until there's no more to append. -As an example of how `merge()` works, suppose that we have the following piles: `leftPile = [1, 7, 8]` and `rightPile = [3, 6, 9]`. Note that each of these piles is individually sorted already. These are merged into one larger sorted pile in the following steps: +As an example of how `merge()` works, suppose that we have the following piles: `leftPile = [1, 7, 8]` and `rightPile = [3, 6, 9]`. Note that each of these piles is individually sorted already -- that is always true with merge sort. These are merged into one larger sorted pile in the following steps: leftPile rightPile orderedPile [ 1, 7, 8 ] [ 3, 6, 9 ] [ ] @@ -218,7 +218,7 @@ Notable points: 1. Merge sort needs a temporary working array because you can't merge the left and right piles and at the same time overwrite their contents. But allocating a new array for each merge is wasteful. Therefore, we're using two working arrays and we'll switch between them using the value of `d`, which is either 0 or 1. The array `z[d]` is used for reading, `z[1 - d]` is used for writing. This is called *double-buffering*. -2. Conceptually, the bottom-up version works the same way as the top-down version. First, it merges small piles of 1 element each, then it merges piles of 2 elements each, then piles of 4 elements each, and so on. The size of the pile is given by `width`. Initially, this is `1` but at the end of each loop iteration we multiply it by 2. So this outer loop determines the size of the piles being merged. And in each step, the subarrays to merge become larger. +2. Conceptually, the bottom-up version works the same way as the top-down version. First, it merges small piles of 1 element each, then it merges piles of 2 elements each, then piles of 4 elements each, and so on. The size of the pile is given by `width`. Initially, `width` is `1` but at the end of each loop iteration we multiply it by 2. So this outer loop determines the size of the piles being merged. And in each step, the subarrays to merge become larger. 3. The inner loop steps through the piles and merges each pair of piles into a larger one. The result is written in the array given by `z[1 - d]`. @@ -237,7 +237,7 @@ mergeSortBottomUp(array, <) // [1, 2, 4, 5, 9] ## Performance -The speed of merge sort is dependent on the size of the array it needs to sort. +The speed of merge sort is dependent on the size of the array it needs to sort. The larger the array, the more work it needs to do. Whether or not the initial array is sorted already doesn't affect the speed of merge sort since you'll be doing the same amount splits and comparisons regardless of the initial order of the elements. @@ -249,6 +249,6 @@ Most implementations of merge sort produce a **stable** sort. This means that ar ## See also -See also [Wikipedia](https://en.wikipedia.org/wiki/Merge_sort) +[Merge sort on Wikipedia](https://en.wikipedia.org/wiki/Merge_sort) *Written by Kelvin Lau. Additions by Matthijs Hollemans.* diff --git a/Monty Hall Problem/README.markdown b/Monty Hall Problem/README.markdown index 81004410e..f096a3a7a 100644 --- a/Monty Hall Problem/README.markdown +++ b/Monty Hall Problem/README.markdown @@ -8,7 +8,7 @@ Now Monty gives you the opportunity to change your mind. Should you stick with y You'd think that changing your answer wouldn't improve your chances... but it does! -This is a very nonintuitive result. Maybe you have trouble believing this is true. Don't worry, when this problem was first proposed many mathematicians didn't believe it either, so you're in good company. +This is a very nonintuitive result. Maybe you have trouble believing this is true. Don't worry, when this problem was first proposed many professional mathematicians didn't believe it either, so you're in good company. There's a simple way to verify this claim: we can write a program to test it out! We should be able to show who wins more often by playing the game a large number of times. @@ -65,7 +65,7 @@ Here's why: When you first make a choice, your chances of picking the prize are After Monty opens one of the doors, this gives you new information. However, it doesn't change the probability of your original choice being the winner. That chance remains 33% because you made that choice when you didn't know yet what was behind this open door. -Since probabilities always need to add up to 100%, the chance that the prize is behind the other door is now 100 - 33 = 67%. So, as strange as it may sound, you're better of switching doors! +Since probabilities always need to add up to 100%, the chance that the prize is behind the other door is now 100 - 33 = 67%. So, as strange as it may sound, you're better off switching doors! This is hard to wrap your head around, but easily shown using a simulation that runs a significant number of times. Probability is weird. diff --git a/Ordered Array/README.markdown b/Ordered Array/README.markdown index d258024d3..f64e55057 100644 --- a/Ordered Array/README.markdown +++ b/Ordered Array/README.markdown @@ -63,7 +63,7 @@ What remains is the `insert()` function. Here is an initial stab at it: } ``` -The helper function `findInsertionPoint()` simply loops through the entire array, looking for the right place to insert the new element. +The helper function `findInsertionPoint()` simply iterates through the entire array, looking for the right place to insert the new element. > **Note:** Quite conveniently, `array.insert(... atIndex: array.count)` adds the new object to the end of the array, so if no suitable insertion point was found we can simply return `array.count` as the index. @@ -83,7 +83,7 @@ a // [-2, -1, 1, 3, 4, 5, 7, 9, 10] The array's contents will always be sorted from low to high, now matter what. -Unfortunately, the current `findInsertionPoint()` function is a bit slow. In the worst case, it needs to scan through the entire array in order to insert the new element. We can speed this up by using a [binary search](../Binary Search) to find the insertion point. +Unfortunately, the current `findInsertionPoint()` function is a bit slow. In the worst case, it needs to scan through the entire array. We can speed this up by using a [binary search](../Binary Search) to find the insertion point. Here is the new version: @@ -106,6 +106,6 @@ Here is the new version: The big difference with a regular binary search is that this doesn't return `nil` when the value can't be found, but the array index where the element would have been. That's where we insert the new object. -Note that using binary search doesn't change the worst-case running time complexity of `insert()`. The binary search itself takes only **O(log n)** time, but inserting a new object in the middle of an array still involves shifting all remaining elements to the right in memory. So overall, the time complexity is still **O(n)**. But in practice this new version definitely is a lot faster, especially on large arrays. +Note that using binary search doesn't change the worst-case running time complexity of `insert()`. The binary search itself takes only **O(log n)** time, but inserting a new object in the middle of an array still involves shifting all remaining elements in memory. So overall, the time complexity is still **O(n)**. But in practice this new version definitely is a lot faster, especially on large arrays. *Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Queue/README.markdown b/Queue/README.markdown index 70a6a76eb..7cf4bd484 100644 --- a/Queue/README.markdown +++ b/Queue/README.markdown @@ -97,7 +97,9 @@ where `xxx` is memory that is reserved but not filled in yet. Adding a new eleme This is simply matter of copying memory from one place to another, a constant-time operation. -Of course, there are only a limited number of such unused spots at the end of the array. When the last `xxx` gets used and you want to add another item, the array needs to resize to make more room. Resizing includes allocating new memory and copying all the existing data over to the new array. This is an **O(n)** process, so relatively slow. But since it happens only every so often, the time for appending a new element to the end of the array is still **O(1)** on average, or **O(1)** "amortized". +Of course, there are only a limited number of such unused spots at the end of the array. When the last `xxx` gets used and you want to add another item, the array needs to resize to make more room. + +Resizing includes allocating new memory and copying all the existing data over to the new array. This is an **O(n)** process, so it's relatively slow. But since it happens only every so often, the time for appending a new element to the end of the array is still **O(1)** on average, or **O(1)** "amortized". The story for dequeueing is slightly different. To dequeue we remove the element from the *beginning* of the array, not the end. This is always an **O(n)** operation because it requires all remaining array elements to be shifted in memory. @@ -116,7 +118,7 @@ Moving all these elements in memory is always an **O(n)** operation. So with our To make dequeuing more efficient, we can use the same trick of reserving some extra free space, but this time do it at the front of the array. We're going to have to write this code ourselves as the built-in Swift array doesn't support this out of the box. -The idea is this: whenever we dequeue an item, we don't shift the contents of the array to the front (slow) but mark the item's position in the array as empty (fast). After dequeuing `"Ada"`, the array is: +The idea is as follows: whenever we dequeue an item, we don't shift the contents of the array to the front (slow) but mark the item's position in the array as empty (fast). After dequeuing `"Ada"`, the array is: [ xxx, "Steve", "Tim", "Grace", xxx, xxx ] @@ -229,7 +231,7 @@ q.array // [nil, nil, {Some "Tim"}, {Some "Grace"}] q.count // 2 ``` -To test the trimming behavior, replace the line +To test the trimming behavior, replace the line, ```swift if array.count > 50 && percentage > 0.25 { diff --git a/Quicksort/README.markdown b/Quicksort/README.markdown index eda50aff5..b8a908985 100644 --- a/Quicksort/README.markdown +++ b/Quicksort/README.markdown @@ -31,7 +31,7 @@ Here's how it works. When given an array, `quicksort()` splits it up into three All the elements less than the pivot go into a new array called `less`. All the elements equal to the pivot go into the `equal` array. And you guessed it, all elements greater than the pivot go into the third array, `greater`. This is why the generic type `T` must be `Comparable`, so we can compare the elements with `<`, `==`, and `>`. -Once we have these three arrays, `quicksort()` recursively sorts the `less` array and the `right` array, then glues those sorted subarrays back together to get the final result. +Once we have these three arrays, `quicksort()` recursively sorts the `less` array and the `right` array, then glues those sorted subarrays back together with the `equal` array to get the final result. ## An example @@ -160,16 +160,16 @@ Note that `list` needs to be a `var` because `partitionLomuto()` directly change The `low` and `high` parameters are necessary because when this is used inside quicksort, you don't always want to (re)partition the entire array, only a limited range that becomes smaller and smaller. -Previously we used the middle array element as the pivot but it's important to realize that the Lomuto algorithm always uses the *last* element, `a[high]`. Because we've been pivoting around `8` all this time, I swapped the positions of `8` and `26` in the example so that `8` is at the end of the array and is used as the pivot value here too. +Previously we used the middle array element as the pivot but it's important to realize that the Lomuto algorithm always uses the *last* element, `a[high]`, for the pivot. Because we've been pivoting around `8` all this time, I swapped the positions of `8` and `26` in the example so that `8` is at the end of the array and is used as the pivot value here too. After partitioning, the array looks like this: [ 0, 3, 2, 1, 5, 8, -1, 8, 9, 10, 14, 26, 27 ] * -The variable `p` contains the return value of the call to `partitionLomuto()` and is 7. This is the index of the pivot element in the new array (marked with a star). +The variable `p` contains the return value of the call to `partitionLomuto()` and is 7. This is the index of the pivot element in the new array (marked with a star). -The left partition goes from 0 to `p-1` and is `[ 0, 3, 2, 1, 5, 8, -1 ]`. The right partition goes from `p+1` to the end, and is `[ 9, 10, 14, 26, 27 ]` (the fact that this is already sorted is a coincidence). +The left partition goes from 0 to `p-1` and is `[ 0, 3, 2, 1, 5, 8, -1 ]`. The right partition goes from `p+1` to the end, and is `[ 9, 10, 14, 26, 27 ]` (the fact that the right partition is already sorted is a coincidence). You may notice something interesting... The value `8` occurs more than once in the array. One of those `8`s did not end up neatly in the middle but somewhere in the left partition. That's a small downside of the Lomuto algorithm as it makes quicksort slower if there are a lot of duplicate elements. @@ -372,7 +372,7 @@ You might think this means we should always choose the middle element rather tha Now the middle element is `1` and that gives the same lousy results as in the previous example. -Ideally, the pivot is the *median* element of the array that you're partitioning. Of course, you won't know what the median is until after you've sorted the array, so this is a bit of a chicken-and-egg problem. However, there are some tricks to choose good, if not ideal, pivots. +Ideally, the pivot is the *median* element of the array that you're partitioning, i.e. the element that sits in the middle of the sorted array. Of course, you won't know what the median is until after you've sorted the array, so this is a bit of a chicken-and-egg problem. However, there are some tricks to choose good, if not ideal, pivots. One trick is "median-of-three", where you find the median of the first, middle, and last element in this subarray. In theory that often gives a good approximation of the true median. @@ -478,6 +478,6 @@ Using Dutch flag partitioning makes for a more efficient quicksort if the array ## See also -See also [Wikipedia](https://en.wikipedia.org/wiki/Quicksort). +[Quicksort on Wikipedia](https://en.wikipedia.org/wiki/Quicksort) -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/README.markdown b/README.markdown index 03bd2601b..c389f219b 100644 --- a/README.markdown +++ b/README.markdown @@ -8,7 +8,9 @@ The goal of this project is to **explain how algorithms work**. The focus is on All code is compatible with **Xcode 7.2** and **Swift 2.1**. We'll keep this updated with the latest version of Swift. -This is a work in progress. More algorithms will be added soon. :-) +This is a work in progress. More algorithms will be added soon. :-) + +:heart_eyes: **Suggestions and contributions are welcome!** :heart_eyes: ## Important links @@ -20,7 +22,7 @@ This is a work in progress. More algorithms will be added soon. :-) [Algorithm design techniques](Algorithm Design.markdown). How do you create your own algorithms? -[How to contribute](How to Contribute.markdown). Report an issue to leave feedback, or submit a pull request. **Suggestions and contributions are welcome!** +[How to contribute](How to Contribute.markdown). Report an issue to leave feedback, or submit a pull request. ## Where to start? @@ -41,7 +43,7 @@ If you're new to algorithms and data structures, here are a few good ones to sta - [Binary Search](Binary Search/). Quickly find elements in a sorted array. - [Count Occurrences](Count Occurrences/). Count how often a value appears in an array. - [Select Minimum / Maximum](Select Minimum Maximum). Find the minimum/maximum value in an array. -- [k-th Largest Element](Kth Largest Element/). Find the *k*th largest element in an array, such as the median. +- [k-th Largest Element](Kth Largest Element/). Find the *k*-th largest element in an array, such as the median. - [Selection Sampling](Selection Sampling/). Randomly choose a bunch of items from a collection. - [Union-Find](Union-Find/). Keeps track of disjoint sets and lets you quickly merge them. @@ -108,7 +110,7 @@ The choice of data structure for a particular task depends on a few things. First, there is the shape of your data and the kinds of operations that you'll need to perform on it. If you want to look up objects by a key you need some kind of dictionary; if your data is hierarchical in nature you want a tree structure of some sort; if your data is sequential you want a stack or queue. -Second, it matters what particular operations you'll be performing most, as certain data structures are optimized for certain actions. For example, if you often need to find the most important object in a queue, then a heap or priority queue is more optimal than a plain array. +Second, it matters what particular operations you'll be performing most, as certain data structures are optimized for certain actions. For example, if you often need to find the most important object in a collection, then a heap or priority queue is more optimal than a plain array. Most of the time using just the built-in `Array`, `Dictionary`, and `Set` types is sufficient, but sometimes you may want something more fancy... @@ -136,7 +138,7 @@ Most of the time using just the built-in `Array`, `Dictionary`, and `Set` types - [Tree](Tree/). A general-purpose tree structure. - [Binary Tree](Binary Tree/). A tree where each node has at most two children. -- [Binary Search Tree (BST)](Binary Search Tree/). A binary tree with the special requirement that elements are inserted in a specific way, allowing for faster queries, like when using a [binary search](Binary Search/) algorithm. +- [Binary Search Tree (BST)](Binary Search Tree/). A binary tree that orders its nodes in a way that allows for fast queries. - [AVL Tree](AVL Tree/). A binary search tree that balances itself using rotations. - Red-Black Tree - Splay Tree diff --git a/Ring Buffer/README.markdown b/Ring Buffer/README.markdown index 72fb08ebd..70e0ad108 100644 --- a/Ring Buffer/README.markdown +++ b/Ring Buffer/README.markdown @@ -41,7 +41,7 @@ Now the app decides it's time to write again and enqueues two more data items, ` [ 123, 456, 789, 666, 333 ] r ---> w -Whoops, the write pointer has reached the end of the array, so there is no more room. What now? Well, this is why it's a circular buffer: we wrap the write pointer back to the beginning and write the remaining data: +Whoops, the write pointer has reached the end of the array, so there is no more room for object `555`. What now? Well, this is why it's a circular buffer: we wrap the write pointer back to the beginning and write the remaining data: [ 555, 456, 789, 666, 333 ] ---> w r @@ -113,7 +113,7 @@ public struct RingBuffer { The `RingBuffer` object has an array for the actual storage of the data, and `readIndex` and `writeIndex` variables for the "pointers" into the array. The `write()` function puts the new element into the array at the `writeIndex`, and the `read()` function returns the element at the `readIndex`. -But hold up, you say, how does this wrapping around work? There are several ways to accomplish this and I chose a slightly controversial one. In this implementation, the `writeIndex` and `readIndex` always increment and never actually wrap around. Instead, we do this to find the actual index into the array: +But hold up, you say, how does this wrapping around work? There are several ways to accomplish this and I chose a slightly controversial one. In this implementation, the `writeIndex` and `readIndex` always increment and never actually wrap around. Instead, we do the following to find the actual index into the array: ```swift array[writeIndex % array.count] @@ -125,7 +125,7 @@ and: array[readIndex % array.count] ``` -In other words, we take the modulo (or the remainder) of the read and write index divided by the size of the underlying array. +In other words, we take the modulo (or the remainder) of the read index and write index divided by the size of the underlying array. The reason this is a bit controversial is that `writeIndex` and `readIndex` always increment, so in theory these values could become too large to fit into an integer and the app will crash. However, a quick back-of-the-napkin calculation should take away those fears. @@ -158,8 +158,8 @@ You've seen that a ring buffer can make a more optimal queue but it also has a d A ring buffer is also very useful for when a producer of data writes into the array at a different rate than the consumer of the data reads it. This happens often with file or network I/O. Ring buffers are also the preferred way of communicating between high priority threads (such as an audio rendering callback) and other, slower, parts of the system. -The implementation given here is not thread safe. It only serves as an example of how a ring buffer works. That said, it should be fairly straightforward to make it thread-safe for a single reader and single writer by using `OSAtomicIncrement64()` to change the read and write pointers. +The implementation given here is not thread-safe. It only serves as an example of how a ring buffer works. That said, it should be fairly straightforward to make it thread-safe for a single reader and single writer by using `OSAtomicIncrement64()` to change the read and write pointers. A cool trick to make a really fast ring buffer is to use the operating system's virtual memory system to map the same buffer onto different memory pages. Crazy stuff but worth looking into if you need to use a ring buffer in a high performance environment. -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Run-Length Encoding/README.markdown b/Run-Length Encoding/README.markdown index 0f6041885..8371b1227 100644 --- a/Run-Length Encoding/README.markdown +++ b/Run-Length Encoding/README.markdown @@ -8,7 +8,7 @@ then RLE encodes it as follows: 5a3b1c1d7e1f... -Instead of repeating bytes, you first write how often that byte occurs and then the byte's actual value. If the data has a lot of "byte runs", that is lots of repeating bytes, then RLE can save quite a bit of space. It works quite well on images. +Instead of repeating bytes, you first write how often that byte occurs and then the byte's actual value. So `5a` means `aaaaa`. If the data has a lot of "byte runs", that is lots of repeating bytes, then RLE can save quite a bit of space. It works quite well on images. There are many different ways you can implement RLE. Here's an extension of `NSData` that does a version of RLE inspired by the old [PCX image file format](https://en.wikipedia.org/wiki/PCX). diff --git a/Segment Tree/README.markdown b/Segment Tree/README.markdown index fb9b6aaa1..0bb6d8da5 100644 --- a/Segment Tree/README.markdown +++ b/Segment Tree/README.markdown @@ -4,11 +4,23 @@ I'm pleased to present to you Segment Tree. It's actually one of my favorite dat Let's suppose that you have an array **a** of some type and some associative function **f**. For example, the function can be sum, multiplication, min, max, [gcd](../GCD/), and so on. -Your task is: +Your task is to: - answer a query for an interval given by **l** and **r**, i.e. perform `f(a[l], a[l+1], ..., a[r-1], a[r])` - support replacing an item at some index `a[index] = newItem` +For example, if we have an array of numbers: + +```swift +var a = [ 20, 3, -1, 101, 14, 29, 5, 61, 99 ] +``` + +We want to query this array on the interval from 3 to 7 for the function "sum". That means we do the following: + + 101 + 14 + 29 + 5 + 61 = 210 + +because `101` is at index 3 in the array and `61` is at index 7. So we pass all the numbers between `101` and `61` to the sum function, which adds them all up. If we had used the "min" function, the result would have been `5` because that's the smallest number in the interval from 3 to 7. + Here's naive approach if our array's type is `Int` and **f** is just the sum of two integers: ```swift @@ -21,7 +33,7 @@ func query(array: [Int], l: Int, r: Int) -> Int { } ``` -The running time of this algorithm is **O(n)** in the worst case, that is when **l = 0, r = n-1**. And if we have **m** queries to answer we get **O(m*n)** complexity. +The running time of this algorithm is **O(n)** in the worst case, that is when **l = 0, r = n-1** (where **n** is the number of elements in the array). And if we have **m** queries to answer we get **O(m*n)** complexity. If we have an array with 100,000 items (**n = 10^5**) and we have to do 100 queries (**m = 100**), then our algorithm will do **10^7** units of work. Ouch, that doesn't sound very good. Let's look at how we can improve it. @@ -48,7 +60,7 @@ Each node has the following data: - `leftBound` and `rightBound` describe an interval - `leftChild` and `rightChild` are pointers to child nodes -- `value` is actually the application of the function `f(a[leftBound], a[leftBound+1], ..., a[rightBound-1], a[rightBound])` +- `value` is the result of applying the function `f(a[leftBound], a[leftBound+1], ..., a[rightBound-1], a[rightBound])` If our array is `[1, 2, 3, 4]` and the function `f = a + b`, the segment tree looks like this: @@ -80,11 +92,11 @@ public init(array: [T], leftBound: Int, rightBound: Int, function: (T, T) -> T) } ``` -Notice that this is a recursive method! You give it an array, such as `[1, 2, 3, 4]` and it creates the root node of the tree and all the child nodes as well. +Notice that this is a recursive method. You give it an array such as `[1, 2, 3, 4]` and it builds up the entire tree, from the root node to all the child nodes. -1. The recursion terminates if `leftBound` and `rightBound` are equal. That means this `SegmentTree` instance will represent a leaf node. For the input array `[1, 2, 3, 4]`, it will create four such leaf nodes: `1`, `2`, `3`, and `4`. We just fill in the `value` property with the number from the array. +1. The recursion terminates if `leftBound` and `rightBound` are equal. Such a `SegmentTree` instance represents a leaf node. For the input array `[1, 2, 3, 4]`, this process will create four such leaf nodes: `1`, `2`, `3`, and `4`. We just fill in the `value` property with the number from the array. -2. However, if `rightBound` is still greater than `leftBound`, we create two child nodes. We divide the current segment into two equal (if length is even) segments. +2. However, if `rightBound` is still greater than `leftBound`, we create two child nodes. We divide the current segment into two equal segments (at least, if the length is even; if it's odd, one segment will be slightly larger). 3. Recursively build child nodes for those two segments. The left child node covers the interval **[leftBound, middle]** and the right child node covers **[middle+1, rightBound]**. @@ -143,7 +155,7 @@ Again, this is a recursive method. It checks four different possibilities. ![mixedSegment](Images/MixedSegment.png) -For example, this is how you could test it out in a playground: +This is how you can test it out in a playground: ```swift let array = [1, 2, 3, 4] diff --git a/Select Minimum Maximum/README.markdown b/Select Minimum Maximum/README.markdown index 5d7c11731..5e006f50e 100644 --- a/Select Minimum Maximum/README.markdown +++ b/Select Minimum Maximum/README.markdown @@ -2,23 +2,23 @@ Goal: Find the minimum/maximum object in an unsorted array. -### Maximum or minimum +## Maximum or minimum We have an array of generic objects and we iterate over all the objects keeping track of the minimum/maximum element so far. -#### An example +### An example Let's say the we want to find the maximum value in the unsorted list `[ 8, 3, 9, 4, 6 ]`. Pick the first number, `8`, and store it as the maximum element so far. -Pick the next number from the list, `3`, and compare it to the current maximum `8`. `3` is less than `8` so the maximum `8` does not change. +Pick the next number from the list, `3`, and compare it to the current maximum. `3` is less than `8` so the maximum `8` does not change. -Pick the next number from the list, `9`, and compare it to the current maximum `8`. `9` is greater than `8` so we store `9` as the maximum. +Pick the next number from the list, `9`, and compare it to the current maximum. `9` is greater than `8` so we store `9` as the maximum. Repeat this process until the all elements in the list have been processed. -#### The code +### The code Here is a simple implementation in Swift: @@ -52,35 +52,44 @@ Put this code in a playground and test it like so: ```swift let array = [ 8, 3, 9, 4, 6 ] -minimum(array) // This will return 3 -maximum(array) // This will return 9 +minimum(array) // This will return 3 +maximum(array) // This will return 9 ``` -### Maximum and minimum +### In the Swift standard library + +The Swift library already contains an extension to `SequenceType` that returns the minimum/maximum element in a sequence. + +```swift +let array = [ 8, 3, 9, 4, 6 ] +array.minElement() // This will return 3 +array.maxElement() // This will return 9 +``` + +## Maximum and minimum To find both the maximum and minimum values contained in array while minimizing the number of comparisons we can compare the items in pairs. -#### An example +### An example -Let's say the we want to find the minimum and maximum value in the unsorted list `[ 8, 3, 9, 4, 6 ]`. +Let's say the we want to find the minimum and maximum value in the unsorted list `[ 8, 3, 9, 6, 4 ]`. Pick the first number, `8`, and store it as the minimum and maximum element so far. -Because we have an odd number of items we remove `8` from the list which leaves the pairs `[ 3, 9 ]` and `[ 4, 6 ]`. +Because we have an odd number of items we remove `8` from the list which leaves the pairs `[ 3, 9 ]` and `[ 6, 4 ]`. -Pick the next pair of numbers from the list, `[ 3, 9 ]`, `3` is less than `9` so we compare `3` to the current minimum `8` and `9` to the current maximum `8`. `3` is less than `8` so the new minimum is `3`. `9` is greater than `8` so the new maximum is `9`. +Pick the next pair of numbers from the list, `[ 3, 9 ]`. Of these two numbers, `3` is the smaller one, so we compare `3` to the current minimum `8`, and we compare `9` to the current maximum `8`. `3` is less than `8` so the new minimum is `3`. `9` is greater than `8` so the new maximum is `9`. -Pick the next pair of numbers from the list, `[ 4, 6 ]`, `4` is less than `6` so we compare `4` to the current minimum `3` and `6` to the current maximum `9`. `4` is greater than `3` so the minimum does not change. `6` is less than `9` so the maximum does not change. +Pick the next pair of numbers from the list, `[ 6, 4 ]`. Here, `4` is the smaller one, so we compare `4` to the current minimum `3`, and we compare `6` to the current maximum `9`. `4` is greater than `3` so the minimum does not change. `6` is less than `9` so the maximum does not change. The result is a minimum of `3` and a maximum of `9`. -#### The code +### The code Here is a simple implementation in Swift: ```swift -func minimumMaximum(var array: [T]) -> (minimum: T, maximum: T)? -{ +func minimumMaximum(var array: [T]) -> (minimum: T, maximum: T)? { guard !array.isEmpty else { return nil } @@ -95,7 +104,6 @@ func minimumMaximum(var array: [T]) -> (minimum: T, maximum: T)? while !array.isEmpty { let pair = (array.removeFirst(), array.removeFirst()) - if pair.0 > pair.1 { if pair.0 > maximum { maximum = pair.0 @@ -120,26 +128,15 @@ func minimumMaximum(var array: [T]) -> (minimum: T, maximum: T)? Put this code in a playground and test it like so: ```swift - let result = minimumMaximum(array)! -result.minimum // This will return 3 -result.maximum // This will return 9 +result.minimum // This will return 3 +result.maximum // This will return 9 ``` By picking elements in pairs and comparing their maximum and minimum with the running minimum and maximum we reduce the number of comparisons to 3 for every 2 elements. -### Performance +## Performance These algorithms run at **O(n)**. Each object in the array is compared with the running minimum/maximum so the time it takes is proportional to the array length. -### Swift library - -The Swift library already contains an extension to `SequenceType` that returns the minimum/maximum element in a sequence. - -```swift -let array = [ 8, 3, 9, 4, 6 ] -array.minElement() // This will return 3 -array.maxElement() // This will return 9 -``` - *Written by [Chris Pilcher](https://github.com/chris-pilcher)* diff --git a/Selection Sampling/README.markdown b/Selection Sampling/README.markdown index 174b3a3da..a02c17b79 100644 --- a/Selection Sampling/README.markdown +++ b/Selection Sampling/README.markdown @@ -19,7 +19,7 @@ func select(from a: [T], count k: Int) -> [T] { } ``` -As often happens with these [kinds of algorithms](../Shuffle/), it divides the array into two regions. The first region is the selected items; the second region is all the remaining items. +As often happens with these [kinds of algorithms](../Shuffle/), it divides the array into two regions. The first region contains the selected items; the second region is all the remaining items. Here's an example. Let's say the array is: @@ -115,7 +115,7 @@ Only one item left to select but there are still 4 candidates to look at. Suppos 4 * 0.718 = 2.872 -For this element to be selected the number has to be less than 1, since there is only 1 element left to be picked. It isn't, so we skip `"d"`. Only three possibilities left -- will we make it? +For this element to be selected the number has to be less than 1, as there is only 1 element left to be picked. It isn't, so we skip `"d"`. Only three possibilities left -- will we make it before we run out of elements? The random number is 0.346. The formula gives: @@ -135,7 +135,7 @@ Let's say our final random number is 0.999 (remember, it can never be 1.0 or hig And so the last element will always be chosen if we didn't have a big enough selection yet. The final selection is `[ "b", "c", "g" ]`. Notice that the elements are still in their original order, because we examined the array from left to right. -Maybe you're not convinced yet... What if we always got 0.999 as the random value, would that still select 3 items? Well, let's do the math: +Maybe you're not convinced yet... What if we always got 0.999 as the random value (the maximum possible), would that still select 3 items? Well, let's do the math: 7 * 0.999 = 6.993 is this less than 3? no 6 * 0.999 = 5.994 is this less than 3? no diff --git a/Selection Sort/README.markdown b/Selection Sort/README.markdown index 57b1d09f5..6eb019ab1 100644 --- a/Selection Sort/README.markdown +++ b/Selection Sort/README.markdown @@ -2,7 +2,7 @@ Goal: Sort an array from low to high (or high to low). -You are given an array of numbers and need to put them in the right order. The selection sort algorithm divides the array into two parts: the beginning of the array is sorted, while the rest of the array consists of the numbers that still need to be sorted. +You are given an array of numbers and need to put them in the right order. The selection sort algorithm divides the array into two parts: the beginning of the array is sorted, while the rest of the array consists of the numbers that still remain to be sorted. [ ...sorted numbers... | ...unsorted numbers... ] @@ -17,11 +17,11 @@ It works as follows: - Swap it with the number at index 1. Now the sorted portion contains two numbers and extends from index 0 to index 1. - Go to index 2. - Find the lowest number in the rest of the array, starting from index 2, and swap it with the one at index 2. Now the array is sorted from index 0 to 2; this range contains the three lowest numbers in the array. -- And so on, until no numbers remain to be sorted. +- And so on... until no numbers remain to be sorted. It's called a "selection" sort, because at every step you search through the rest of the array to select the next lowest number. -### An example +## An example Let's say the numbers to sort are `[ 5, 8, 3, 4, 6 ]`. We also keep track of where the sorted portion of the array ends, denoted by the `|` symbol. @@ -48,11 +48,13 @@ With every step, the `|` bar moves one position to the right. We again look thro [ 3, 4, 5 | 8, 6 ] * -This process repeats until the array is sorted. Note that everything to the left of the `|` bar is always in sorted order and always contains the lowest numbers in the array. +This process repeats until the array is sorted. Note that everything to the left of the `|` bar is always in sorted order and always contains the lowest numbers in the array. Finally, we end up with: -As you can see, selection sort is an *in-place* sort because everything happens in the same array; no additional memory is necessary. You can also implement this as a *stable* sort so that identical elements do not get swapped around relative to each other (note that the version below isn't stable). + [ 3, 4, 5, 6, 8 |] -### The code +As you can see, selection sort is an *in-place* sort because everything happens in the same array; no additional memory is necessary. You can also implement this as a *stable* sort so that identical elements do not get swapped around relative to each other (note that the version given below isn't stable). + +## The code Here is an implementation of selection sort in Swift: @@ -90,7 +92,7 @@ A step-by-step explanation of how the code works: 1. If the array is empty or only contains a single element, then there's not much point to sorting it. -2. Make a copy of the array. This is necessary because we cannot modify the contents of the `array` parameter directly. Like Swift's own `sort()`, the `selectionSort()` function will return a sorted *copy* of the original array. +2. Make a copy of the array. This is necessary because we cannot modify the contents of the `array` parameter directly in Swift. Like Swift's own `sort()`, the `selectionSort()` function will return a sorted *copy* of the original array. 3. There are two loops inside this function. The outer loop looks at each of the elements in the array in turn; this is what moves the `|` bar forward. @@ -98,18 +100,20 @@ A step-by-step explanation of how the code works: 5. Swap the lowest number with the current array index. The `if` check is necessary because you can't `swap()` an element with itself in Swift. -In summary: For each element of the array, it swaps positions with the lowest value from the rest of the array. As a result, the array gets sorted from the left to the right. (You can also do it right-to-left, in which case you always look for the largest number in the array. Give that a try!) +In summary: For each element of the array, selection sort swaps positions with the lowest value from the rest of the array. As a result, the array gets sorted from the left to the right. (You can also do it right-to-left, in which case you always look for the largest number in the array. Give that a try!) -Note: The outer loop ends at index `a.count - 2`. The very last element will automatically always be in the correct position because at that point there are no other smaller elements left. +> **Note:** The outer loop ends at index `a.count - 2`. The very last element will automatically always be in the correct position because at that point there are no other smaller elements left. The source file [SelectionSort.swift](SelectionSort.swift) has a version of this function that uses generics, so you can also use it to sort strings and other types. -### Performance +## Performance + +Selection sort is easy to understand but it performs quite badly, **O(n^2)**. It's worse than [insertion sort](../Insertion Sort/) but better than [bubble sort](../Bubble Sort/). The killer is finding the lowest element in the rest of the array. This takes up a lot of time, especially since the inner loop will be performed over and over. -Selection sort is easy to understand but it performs quite badly, **O(n^2)**. It's worse than insertion sort but better than bubble sort. The killer is finding the lowest element in the rest of the array. This takes up a lot of time, especially since the inner loop will be performed over and over. +[Heap sort](../Heap Sort/) uses the same principle as selection sort but has a really fast method for finding the minimum value in the rest of the array. Its performance is **O(n log n)**. -### See also +## See also -See also [Wikipedia](https://en.wikipedia.org/wiki/Selection_sort). +[Selection sort on Wikipedia](https://en.wikipedia.org/wiki/Selection_sort) -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Shell Sort/README.markdown b/Shell Sort/README.markdown index 8870ffea6..6cb682035 100644 --- a/Shell Sort/README.markdown +++ b/Shell Sort/README.markdown @@ -1,6 +1,6 @@ # Shell Sort -Shell sort is based on [insertion sort](../Insertion%20Sort/) as a general way to improve its performance, by breaking the original list into smaller sublists which can then be individually sorted using insertion sort. +Shell sort is based on [insertion sort](../Insertion%20Sort/) as a general way to improve its performance, by breaking the original list into smaller sublists which are then individually sorted using insertion sort. [There is a nice video created at Sapientia University](https://www.youtube.com/watch?v=CmPA7zE8mx0) which shows the process as a Hungarian folk dance. @@ -12,7 +12,7 @@ The distance between elements is known as the *gap*. If the elements being compa The idea is that by moving the elements over large gaps, the array becomes partially sorted quite quickly. This makes later passes faster because they don't have to swap so many items anymore. -Once a pass has been completed, the gap is made smaller and a new pass starts. This repeats until the gap is 1, at which point the algorithm functions just like insertion sort. But since the data is already fairly well sorted by then, the final pass can be very quick. +Once a pass has been completed, the gap is made smaller and a new pass starts. This repeats until the gap has size 1, at which point the algorithm functions just like insertion sort. But since the data is already fairly well sorted by then, the final pass can be very quick. ## An example @@ -26,36 +26,36 @@ This is the gap size. We create `n` sublists. In each sublist, the items are spaced apart by a gap of size `n`. In our example, we need to make four of these sublists. The sublists are sorted by the `insertionSort()` function. -That probably didn't make a whole lot of sense, so let's look at what happens. +That may not have made a whole lot of sense, so let's take a closer look at what happens. The first pass is as follows. We have `n = 4`, so we make four sublists: - list 0: [ 64, xx, xx, xx, 72, xx, xx, xx, 4 ] - list 1: [ xx, 20, xx, xx, xx, 10, xx, xx, xx ] - list 2: [ xx, xx, 50, xx, xx, xx, 23, xx, xx ] - list 3: [ xx, xx, xx, 33, xx, xx, xx, -1, xx ] + sublist 0: [ 64, xx, xx, xx, 72, xx, xx, xx, 4 ] + sublist 1: [ xx, 20, xx, xx, xx, 10, xx, xx, xx ] + sublist 2: [ xx, xx, 50, xx, xx, xx, 23, xx, xx ] + sublist 3: [ xx, xx, xx, 33, xx, xx, xx, -1, xx ] -As you can see, each list contains only every 4th item from the original array. The items that are not in a sublist are marked with `xx`. So the first list is `[ 64, 72, 4]` and the second is `[ 20, 10 ]`, and so on. The reason we use this "gap" is so that we don't have to actually make new arrays. Instead, we interleave them in the original array. +As you can see, each sublist contains only every 4th item from the original array. The items that are not in a sublist are marked with `xx`. So the first sublist is `[ 64, 72, 4 ]` and the second is `[ 20, 10 ]`, and so on. The reason we use this "gap" is so that we don't have to actually make new arrays. Instead, we interleave them in the original array. We now call `insertionSort()` once on each sublist. -This particular version of [insertion sort](../Insertion Sort/) sorts from the back to the front. Each item in the sublist is compared against the others. If they're in the wrong order, the value is swapped and travels all the way down until we reach the start of the list. +This particular version of [insertion sort](../Insertion Sort/) sorts from the back to the front. Each item in the sublist is compared against the others. If they're in the wrong order, the value is swapped and travels all the way down until we reach the start of the sublist. -So for list 0, we swap `4` with `72`, then swap `4` with `64`. After sorting, this sublist looks like: +So for sublist 0, we swap `4` with `72`, then swap `4` with `64`. After sorting, this sublist looks like: - list 0: [ 4, xx, xx, xx, 64, xx, xx, xx, 72 ] + sublist 0: [ 4, xx, xx, xx, 64, xx, xx, xx, 72 ] The other three sublists after sorting: - list 1: [ xx, 10, xx, xx, xx, 20, xx, xx, xx ] - list 2: [ xx, xx, 23, xx, xx, xx, 50, xx, xx ] - list 3: [ xx, xx, xx, -1, xx, xx, xx, 33, xx ] + sublist 1: [ xx, 10, xx, xx, xx, 20, xx, xx, xx ] + sublist 2: [ xx, xx, 23, xx, xx, xx, 50, xx, xx ] + sublist 3: [ xx, xx, xx, -1, xx, xx, xx, 33, xx ] The total array looks like this now: [ 4, 10, 23, -1, 64, 20, 50, 33, 72 ] -It's not sorted yet but it's more sorted than before. This completes the first pass. +It's not entirely sorted yet but it's more sorted than before. This completes the first pass. In the second pass, we divide the gap size by two: @@ -63,13 +63,13 @@ In the second pass, we divide the gap size by two: That means we now create only two sublists: - list 0: [ 4, xx, 23, xx, 64, xx, 50, xx, 72 ] - list 1: [ xx, 10, xx, -1, xx, 20, xx, 33, xx ] + sublist 0: [ 4, xx, 23, xx, 64, xx, 50, xx, 72 ] + sublist 1: [ xx, 10, xx, -1, xx, 20, xx, 33, xx ] Each sublist contains every 2nd item. Again, we call `insertionSort()` to sort these sublists. The result is: - list 0: [ 4, xx, 23, xx, 50, xx, 64, xx, 72 ] - list 1: [ xx, -1, xx, 10, xx, 20, xx, 33, xx ] + sublist 0: [ 4, xx, 23, xx, 50, xx, 64, xx, 72 ] + sublist 1: [ xx, -1, xx, 10, xx, 20, xx, 33, xx ] Note that in each list only two elements were out of place. So the insertion sort is really fast. That's because we already sorted the array a little in the first pass. @@ -85,7 +85,7 @@ A gap size of 1 means we only have a single sublist, the array itself, and once [ -1, 4, 10, 20, 23, 33, 50, 64, 72 ] -The performance of shell sort is **O(n^2)** or **O(n log n)** if you get lucky. This algorithm produces an unstable sort; it may change the relative order of elements with equal values. +The performance of shell sort is **O(n^2)** in most cases or **O(n log n)** if you get lucky. This algorithm produces an unstable sort; it may change the relative order of elements with equal values. ## The gap sequence diff --git a/Shuffle/README.markdown b/Shuffle/README.markdown index 6cbb23016..e0be6c1e4 100644 --- a/Shuffle/README.markdown +++ b/Shuffle/README.markdown @@ -29,7 +29,7 @@ list.shuffle() list.shuffle() ``` -You should see three different arrangements -- or permutations to use math-speak -- of the objects in the array. +You should see three different arrangements -- or [permutations](../Combinatorics/) to use math-speak -- of the objects in the array. This shuffle works *in place*, it modifies the contents of the original array. The algorithm works by creating a new array, `temp`, that is initially empty. Then we randomly choose an element from the original array and append it to `temp`, until the original array is empty. Finally, the temporary array is copied back into the original one. diff --git a/Shunting Yard/README.markdown b/Shunting Yard/README.markdown index d4d7c3e22..d2e568d39 100644 --- a/Shunting Yard/README.markdown +++ b/Shunting Yard/README.markdown @@ -1,38 +1,40 @@ # Shunting Yard Algorithm -Any mathematical expression that we write is expressed in a notation known as *infix notation*. - -For example: +Any mathematics we write is expressed in a notation known as *infix notation*. For example: **A + B * C** -In the above expression the operator is placed between operands, hence the expression is said to be in *infix* form. If you think about it, any expression that you write on a piece of paper will always be in infix form. This is what we humans understand. +The operator is placed in between the operands, hence the expression is said to be in *infix* form. If you think about it, any expression that you write on a piece of paper will always be in infix form. This is what we humans understand. + +Multiplication has higher precedence than addition, so when the above expression is evaluated you would first multiply **B** and **C**, and then add the result to **A**. We humans can easily understand the precedence of operators, but a machine needs to be given instructions about each operator. + +To change the order of evaluation, we can use parentheses: -When the above expression is evaluated, you would first multiply **B** and **C**, and then add the result to **A**. This is because multiplication has higher precedence than addition. We humans can easily understand the precedence of operators, but a machine needs to be given instructions about each operator. +**(A + B) * C** -If you were to write an algorithm that parsed and evaluated the infix notation you will realize that it's a tedious process. You'd have to parse the expression multiple times to know what operation to perform first. As the number of operators increase so does the complexity. +Now **A** is first added to **B** and then the sum is multiplied by **C**. -## Postfix notations / Reverse Polish Notation +If you were to write an algorithm that parsed and evaluated expressions in infix notation you will realize that it's a tedious process. You'd have to parse the expression multiple times to know what operation to perform first. As the number of operators increase so does the complexity. + +## Postfix notation In postfix notation, also known as Reverse Polish Notation or RPN, the operators come after the corresponding operands. Here is the postfix representation of the example from the previous section: **A B C * +** -An expression when represented in postfix form will not have any brackets and neither will you have to worry about scanning for operator precedence. This makes it easy for the computer to evaluate expressions, since the order in which the operators need to be applied is fixed. - -### Evaluating a postfix expression +An expression in postfix form will not have any parentheses and neither will you have to worry about scanning for operator precedence. This makes it easy for the computer to evaluate expressions, since the order in which the operators need to be applied is fixed. -A stack is used to evaluate a postfix expression. Here is the pseudocode: +Evaluating a postfix expression is easily done using a stack. Here is the pseudocode: -1. Read postfix expression token by token -2. If the token is an operand, push it into the stack +1. Read the postfix expression token by token +2. If the token is an operand, push it onto the stack 3. If the token is a binary operator, - 1. Pop the two top most operands from the stack + 1. Pop the two topmost operands from the stack 2. Apply the binary operator to the two operands - 3. Push the result into the stack + 3. Push the result back onto the stack 4. Finally, the value of the whole postfix expression remains in the stack -Using the above pseudocode, the evaluation on the stack would be as follows: +Using the above pseudocode, the evaluation of **A B C * +** would be as follows: | Expression | Stack | | ------------- | --------| @@ -47,7 +49,7 @@ Where **D = B * C** and **E = A + D.** As seen above, a postfix operation is relatively easy to evaluate as the order in which the operators need to be applied is pre-decided. -## Dijkstra's shunting yard algorithm +## Converting infix to postfix The shunting yard algorithm was invented by Edsger Dijkstra to convert an infix expression to postfix. Many calculators use this algorithm to convert the expression being entered to postfix form. @@ -65,11 +67,9 @@ Here is the psedocode of the algorithm: 1. Until the top token (from the stack) is left parenthesis, pop from the stack to the output buffer 2. Also pop the left parenthesis but don’t include it in the output buffer 7. Else add token to output buffer -2. While there are still operator tokens in the stack, pop them to output - -### How does it work +2. Pop any remaining operator tokens from the stack to the output -Let's take a small example and see how the pseudocode works. +Let's take a small example and see how the pseudocode works. Here is the infix expression to convert: **4 + 4 * 2 / ( 1 - 5 )** diff --git a/Stack/README.markdown b/Stack/README.markdown index fbc2512e4..6e832b69b 100644 --- a/Stack/README.markdown +++ b/Stack/README.markdown @@ -72,6 +72,6 @@ public struct Stack { Notice that a push puts the new element at the end of the array, not the beginning. Inserting at the beginning of an array is expensive, an **O(n)** operation, because it requires all existing array elements to be shifted in memory. Adding at the end is **O(1)**; it always takes the same amount of time, regardless of the size of the array. -Fun fact about stacks: each time you call a function or a method, the return address is placed on a stack by the CPU. When the function ends, the CPU uses that return address to jump back to the caller. That's why if you call too many functions -- for example in a recursive function that never ends -- you get a so-called "stack overflow" as the CPU stack is out of space. +Fun fact about stacks: Each time you call a function or a method, the CPU places the return address on a stack. When the function ends, the CPU uses that return address to jump back to the caller. That's why if you call too many functions -- for example in a recursive function that never ends -- you get a so-called "stack overflow" as the CPU stack has run out of space. -*Written by Matthijs Hollemans* +*Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Tree/README.markdown b/Tree/README.markdown index 98d1bec12..52a46c597 100644 --- a/Tree/README.markdown +++ b/Tree/README.markdown @@ -1,10 +1,10 @@ -# Tree +# Trees A tree represents hierarchical relationships between objects. This is a tree: ![A tree](Images/Tree.png) -A tree consists of nodes. +A tree consists of nodes, and these nodes are linked to one another. Nodes have links to their children and usually to their parent as well. The children are the nodes below a given node; the parent is the node above. A node always has just one parent but can have multiple children. @@ -16,7 +16,7 @@ The pointers in a tree do not form cycles. This is not a tree: ![Not a tree](Images/Cycles.png) -Such a structure is called a [graph](../Graph/). A tree is really a very simple form of a graph. (In a similar vein, a [linked list](../Linked List) is a simple version of a tree. Think about it!) +Such a structure is called a [graph](../Graph/). A tree is really a very simple form of a graph. (In a similar vein, a [linked list](../Linked List/) is a simple version of a tree. Think about it!) This article talks about a general-purpose tree. That's a tree without any kind of restrictions on how many children each node may have, or on the order of the nodes in the tree. @@ -33,7 +33,7 @@ public class TreeNode { self.value = value } - func addChild(node: TreeNode) { + public func addChild(node: TreeNode) { children.append(node) node.parent = self } @@ -165,4 +165,6 @@ Then we can describe the tree with the following array: Each entry in the array is a pointer to its parent node. The item at index 3, `tea`, has the value 1 because its parent is `hot`. The root node points to `-1` because it has no parent. You can only traverse such trees from a node back to the root but not the other way around. +By the way, sometimes you see algorithms using the term *forest*. Unsurprisingly, that is the name given to a collection of separate tree objects. For an example of this, see [union-find](../Union-Find/). + *Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Tree/Tree.playground/Contents.swift b/Tree/Tree.playground/Contents.swift index c4e8959f5..966870bdf 100644 --- a/Tree/Tree.playground/Contents.swift +++ b/Tree/Tree.playground/Contents.swift @@ -10,7 +10,7 @@ public class TreeNode { self.value = value } - func addChild(node: TreeNode) { + public func addChild(node: TreeNode) { children.append(node) node.parent = self } @@ -26,6 +26,9 @@ extension TreeNode: CustomStringConvertible { } } + + + let tree = TreeNode(value: "beverages") let hotNode = TreeNode(value: "hot") diff --git a/Tree/Tree.swift b/Tree/Tree.swift index e77045339..5c027141c 100644 --- a/Tree/Tree.swift +++ b/Tree/Tree.swift @@ -8,7 +8,7 @@ public class TreeNode { self.value = value } - func addChild(node: TreeNode) { + public func addChild(node: TreeNode) { children.append(node) node.parent = self } diff --git a/Two-Sum Problem/README.markdown b/Two-Sum Problem/README.markdown index 0c010954a..e2e15d4c0 100644 --- a/Two-Sum Problem/README.markdown +++ b/Two-Sum Problem/README.markdown @@ -8,7 +8,7 @@ There are a variety of solutions to this problem (some better than others). The This solution uses a dictionary to store differences between each element in the array and the sum `k` that we're looking for. The dictionary also stores the indices of each element. -With this approach, each key in the dictionary corresponds to a new target value. If one of the following numbers from the array is equal to one of the dictionary's keys, then we know there exist two numbers that sum to `k`. +With this approach, each key in the dictionary corresponds to a new target value. If one of the successive numbers from the array is equal to one of the dictionary's keys, then we know there exist two numbers that sum to `k`. ```swift func twoSumProblem(a: [Int], k: Int) -> ((Int, Int))? { @@ -38,7 +38,7 @@ Let's find out if there exist two entries whose sum is equal to 10. Initially, our dictionary is empty. We begin looping through each element: -- **i = 0**: Is `0` in the dictionary? No. We add the difference between the target `k` and the current number to the dictionary. The difference is `10 - 7 = 3`, so the dictionary key is `3`. The value for that key is the current index, `0`. The dictionary now looks like this: +- **i = 0**: Is `7` in the dictionary? No. We add the difference between the target `k` and the current number to the dictionary. The difference is `10 - 7 = 3`, so the dictionary key is `3`. The value for that key is the current index, `0`. The dictionary now looks like this: ```swift [ 3: 0 ] @@ -148,4 +148,4 @@ It's possible, of course, that there are no values for `a[i] + a[j]` that sum to I'm quite enamored by this little algorithm. It shows that with some basic preprocessing on the input data -- sorting it from low to high -- you can turn a tricky problem into a very simple and beautiful algorithm. -*Written by Matthijs Hollemans and Daniel Speiser* +*Written for Swift Algorithm Club by Matthijs Hollemans and Daniel Speiser* diff --git a/Union-Find/README.markdown b/Union-Find/README.markdown index 66937cf69..1d674e965 100644 --- a/Union-Find/README.markdown +++ b/Union-Find/README.markdown @@ -54,10 +54,10 @@ There are two trees in this forest, each of which corresponds to one set of elem We give each subset a unique number to identify it. That number is the index of the root node of that subset's tree. In the example, node `1` is the root of the first tree and `6` is the root of the second tree. -Note that the `parent[]` of a root node points to itself. So `parent[1] = 1` and `parent[6] = 6`. That's how we can tell something is a root node. - So in this example we have two subsets, the first with the label `1` and the second with the label `6`. The **Find** operation actually returns the set's label, not its contents. +Note that the `parent[]` of a root node points to itself. So `parent[1] = 1` and `parent[6] = 6`. That's how we can tell something is a root node. + ## Add set Let's look at the implementation of these basic operations, starting with adding a new set. @@ -181,7 +181,7 @@ Now we call `unionSetsContaining(4, and: 3)`. The smaller tree is attached to th ![AfterUnion](Images/AfterUnion.png) -Note that, because we call `setOf()` in the beginning of the method, the larger tree was also optimized in the process -- node `3` now hangs directly off the root. +Note that, because we call `setOf()` at the start of the method, the larger tree was also optimized in the process -- node `3` now hangs directly off the root. Union with optimizations also takes almost **O(1)** time.