diff --git a/README.md b/README.md index 996e165..fbe7301 100644 --- a/README.md +++ b/README.md @@ -19,35 +19,51 @@ If you have any suggestions or want to make additions, I would be very happy if # Categories -### Sorting Algorithms +### Algorithm Essentials -- Selection Sort [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/sorting/selection_sort.ipynb)] -- Bubble Sort [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/sorting/bubble_sort.ipynb)] -- QuickSort [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/sorting/quicksort.ipynb)] -### Data Structures +#### Greedy Algorithms -- Bloom Filters [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/bloom-filter.ipynb)] +- Introduction to Greedy Algorithms [[ GitHub ](ipython_nbs/essentials/greedy-algorithm-intro.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/greedy-algorithm-intro.ipynb)] +- More Greedy Algorithm Examples [[ GitHub ](ipython_nbs/essentials/greedy-algorithm-examples.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/greedy-algorithm-examples.ipynb)] +- Breadth-First Search [cross-linked] [[ GitHub ](ipython_nbs/search/breadth-first-search.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/search/breadth-first-search)] +- Dijkstra's Algorithm [cross-linked] [[ GitHub ](ipython_nbs/search/dijkstra-algorithm.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/search/dijkstra-algorithm.ipynb)] -- Singly Linked List [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/singly-linked-list.ipynb)] +#### Divide-and-Conquer Algorithms -- Stacks [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/stacks.ipynb)] +- Introduction to Divide-and-Conquer Algorithms[[ GitHub ](ipython_nbs/essentials/divide-and-conquer-algorithm-intro.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/divide-and-conquer-algorithm-intro.ipynb)] +- Binary Search [cross-linked] [[ GitHub ](ipython_nbs/search/binary_search.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/search/binary_search.ipynb)] +- Recursion Examples [[ GitHub ](ipython_nbs/essentials/recursion-examples.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/recursion-examples.ipynb)] -- Queues and Deques [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/queues-and-deques.ipynb)] +#### Other -### Search Algorithms +- FizzBuzz [[ GitHub ](ipython_nbs/essentials/fizzbuzz.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/fizzbuzz.ipynb)] -- Binary Search [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/search/binary_search.ipynb)] +### Sorting Algorithms -- Breadth-First Search [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/search/breadth-first-search)] +- Selection Sort [[ GitHub ](ipython_nbs/sorting/selection_sort.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/sorting/selection_sort.ipynb)] +- Bubble Sort [[ GitHub ](ipython_nbs/sorting/bubble_sort.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/sorting/bubble_sort.ipynb)] +- QuickSort [[ GitHub ](ipython_nbs/sorting/quicksort.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/sorting/quicksort.ipynb)] -### Statistical Analysis +### Data Structures -- Linear regression via the least squares fit method [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/statistics/linregr_least_squares_fit.ipynb)] +- Implementing a Simple Hash Table [[ GitHub ](ipython_nbs/data-structures/hashtable-1.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/hashtable-1.ipynb)] +- Bloom Filters [[ GitHub ](ipython_nbs/data-structures/bloom-filter.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/bloom-filter.ipynb)] +- Singly Linked List [[ GitHub ](ipython_nbs/data-structures/singly-linked-list.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/singly-linked-list.ipynb)] +- Stacks [[ GitHub ](ipython_nbs/data-structures/stacks.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/stacks.ipynb)] +- Queues and Deques [[ GitHub ](ipython_nbs/data-structures/queues-and-deques.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/queues-and-deques.ipynb)] -- Dixon's Q test to identify outliers for small sample sizes [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/statistics/dixon_q_test.ipynb)] +### Search Algorithms + +- Binary Search [[ GitHub ](ipython_nbs/search/binary_search.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/search/binary_search.ipynb)] +- Breadth-First Search [[ GitHub ](ipython_nbs/search/breadth-first-search.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/search/breadth-first-search)] +- Dijkstra's Algorithm [[ GitHub ](ipython_nbs/search/dijkstra-algorithm.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/search/dijkstra-algorithm.ipynb)] + +### Statistical Analysis -- Rejection sampling [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/statistics/rejection_sampling.ipynb)] +- Linear regression via the least squares fit method [[ GitHub ](ipython_nbs/statistics/linregr_least_squares_fit.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/statistics/linregr_least_squares_fit.ipynb)] +- Dixon's Q test to identify outliers for small sample sizes [[ GitHub ](ipython_nbs/statistics/dixon_q_test.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/statistics/dixon_q_test.ipynb)] +- Rejection sampling [[ GitHub ](ipython_nbs/data-structures/rejection_sampling.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/statistics/rejection_sampling.ipynb)] ### Machine Learning @@ -56,20 +72,13 @@ If you have any suggestions or want to make additions, I would be very happy if ### Geometry -- Counting points inside a hypercube [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/geometry/points_in_hybercube.ipynb)] - -- Vectorizing a classic `for`-loop in NumPy for calculating Euclidean distances [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/geometry/eucldist_numpy_vectorization.ipynb)] - +- Counting points inside a hypercube [[ GitHub ](ipython_nbs/geometry/points_in_hybercube.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/geometry/points_in_hybercube.ipynb)] +- Vectorizing a classic `for`-loop in NumPy for calculating Euclidean distances [[ GitHub ](ipython_nbs/geometry/eucldist_numpy_vectorization.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/geometry/eucldist_numpy_vectorization.ipynb)] -### Essentials -- FizzBuzz [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/fizzbuzz.ipynb)] -- Introduction to Greedy Algorithms [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/greedy-algorithm-intro.ipynb)] -- Introduction to Divide-and-Conquer Algorithms [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/divide-and-conquer-algorithm-intro.ipynb)] -- Recursion Examples [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/essentials/recursion-examples.ipynb)] ### Efficiency -- Finding the Maximum Pairwise Product [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/efficiency/maximum-pairwise-product.ipynb)] -- Fibonacci Numbers [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/efficiency/fibonacci-tree.ipynb)] -- Greatest Common Divisor [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/efficiency/greatest-common-divisor.ipynb)] +- Finding the Maximum Pairwise Product [[ GitHub ](ipython_nbs/efficiency/maximum-pairwise-product.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/efficiency/maximum-pairwise-product.ipynb)] +- Fibonacci Numbers [[ GitHub ](ipython_nbs/efficiency/fibonacci-tree.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/efficiency/fibonacci-tree.ipynb)] +- Greatest Common Divisor [[ GitHub ](ipython_nbs/efficiency/greatest-common-divisor.ipynb)] [[ Jupyter nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/efficiency/greatest-common-divisor.ipynb)] diff --git a/ipython_nbs/data-structures/hashtable-1.ipynb b/ipython_nbs/data-structures/hashtable-1.ipynb new file mode 100644 index 0000000..17ab247 --- /dev/null +++ b/ipython_nbs/data-structures/hashtable-1.ipynb @@ -0,0 +1,519 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sebastian Raschka \n", + "last updated: 2017-08-14 \n", + "\n", + "CPython 3.6.1\n", + "IPython 6.1.0\n" + ] + } + ], + "source": [ + "%load_ext watermark\n", + "%watermark -a 'Sebastian Raschka' -u -d -v" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Implementing a Simple Hash Table" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebooks walks through a simple hash table implementation that behaves relatively similar to a Python dictionary but has a fixed size of elements that it can store for simplicity. The methods we are going to implement are listed below. Note that `__setitem__` and `__getitem__` are used to overload the `[]` index access for the `insert` and `get` methods, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class HashTable():\n", + "\n", + " def __init__(self, size=17):\n", + " pass\n", + " \n", + " def insert(self, key, value):\n", + " \"\"\"Insert a new key-value pair or overwrites an existing one.\"\"\"\n", + " pass\n", + " \n", + " def __setitem__(self, key, value):\n", + " return self.insert(key, value)\n", + " \n", + " def get(self, key):\n", + " \"\"\"Retrieve the value corresponding to the key.\"\"\"\n", + " pass\n", + " \n", + " def __getitem__(self, key):\n", + " return self.get(key)\n", + " \n", + " def keys(self):\n", + " \"\"\"Retrieves all keys from the hash table\"\"\"\n", + " pass\n", + " \n", + " def _hash(self, key):\n", + " \"\"\"Computes the hash position\"\"\"\n", + " pass\n", + " \n", + " def _rehash(self, previous_hash):\n", + " \"\"\"Find the next hash for linear probing\"\"\"\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Hash Function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this simple example, we will be using a very naive hash function, the *remainder method*, which simply computes the remainder of the key when dividing it by the size of the hash table. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Position in the hash table: 13\n" + ] + } + ], + "source": [ + "hash_table_size = 17\n", + "key = 123467 \n", + "position = key % hash_table_size\n", + "print('Position in the hash table:', position)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that this function only works with integer keys. Thus, if the key is a string, we have to make a little modification:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Position in the hash table: 2\n" + ] + } + ], + "source": [ + "string_key = 'abcdef'\n", + "key = sum(ord(c) for c in string_key)\n", + "position = key % hash_table_size\n", + "print('Position in the hash table:', position)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Collision resolution with linear probing\n", + "\n", + "For collision resolution we will use linear probing. That is, if a new key hashes to the same position as an existing one, we will increase the old hash value by a skip constant and check if the resulting slot is empty. If not, we will repeat this procedure until we find the next empty slot and insert the new key in this empty slot. We call this \"***rehashing***.\"\n", + "\n", + "For look-ups, we then have to use the following procedure:\n", + "\n", + "- Check the key in the hash table that maps to the hash value of a key we want to look up.\n", + " - If the keys are identical, return the value.\n", + " - Else, go through the hash table using \"reshashing\" look-up key is found or return None if an empty slot was encountered.\n", + " \n", + "For simplicity, we will use implement the reshashing function using a skip constant of 1. So let's now implement the `__init__`, `_hash`, `_rehash`, `keys` and `insert` methods to get a simple hashtable set up in which we can already store some keys:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class HashTable():\n", + " def __init__(self, size=17):\n", + " self.size = size\n", + " self.stored_keys = [None] * self.size\n", + " self.stored_values = [None] * self.size\n", + " \n", + " def insert(self, key, value):\n", + " \"\"\"Insert a new key-value pair or overwrites an existing one.\"\"\"\n", + " hash_val = self._hash(key)\n", + " \n", + " # insert new key if it doesn't exist yet\n", + " if self.stored_keys[hash_val] is None:\n", + " self.stored_keys[hash_val] = key\n", + " self.stored_values[hash_val] = value\n", + " \n", + " # overwrite key if it already exists\n", + " else:\n", + " if self.stored_keys[hash_val] == key:\n", + " self.stored_values[hash_val] = value\n", + " \n", + " # collision resolution\n", + " elif len(self.keys()) == self.size:\n", + " raise ValueError('Hash table is full')\n", + " \n", + " else:\n", + " next_hash = self._rehash(hash_val)\n", + " \n", + " while (self.stored_keys[next_hash] is not None\n", + " and self.stored_keys[next_hash] != key):\n", + " next_hash = self._rehash(next_hash)\n", + " \n", + " # insert new key if it doesn't exist yet\n", + " if self.stored_keys[next_hash] is None:\n", + " self.stored_keys[next_hash] = key\n", + " self.stored_values[next_hash] = value\n", + " \n", + " # overwrite key if it already exists\n", + " else:\n", + " self.stored_values[next_hash] = value\n", + " \n", + " def __setitem__(self, key, value):\n", + " return self.insert(key, value)\n", + " \n", + " def get(self, key):\n", + " \"\"\"Retrieve the value corresponding to the key.\"\"\"\n", + " pass\n", + " \n", + " def __getitem__(self, key):\n", + " return self.get(key)\n", + " \n", + " def keys(self):\n", + " \"\"\"Retrieves all keys from the hash table\"\"\"\n", + " return [k for k in self.stored_keys if k is not None]\n", + " \n", + " def _hash(self, key):\n", + " \"\"\"Computes the hash position.\"\"\"\n", + " if isinstance(key, str):\n", + " key = sum(ord(c) for c in key)\n", + " position = key % hash_table_size\n", + " return position\n", + " \n", + " def _rehash(self, previous_hash):\n", + " \"\"\"Find the next hash for linear probing\"\"\"\n", + " return (previous_hash + 1) % self.size" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's start by inserting 2 different keys and check they have been stored by listing all keys:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['abc', 'xyz']" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hashtable = HashTable()\n", + "hashtable['abc'] = 1\n", + "hashtable['xyz'] = 2\n", + "hashtable.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, let's use a key that would result in a hash collision with one of the already stored keys:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5\n", + "0\n", + "5\n" + ] + } + ], + "source": [ + "print(hashtable._hash('abc'))\n", + "print(hashtable._hash('efg'))\n", + "print(hashtable._hash('abcdefgh'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use this key, `'abcdefgh'`, now if hash collisions are resolved correctly:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['abc', 'xyz', 'abcdefgh']" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hashtable['abcdefgh'] = 3\n", + "hashtable.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, let's add the get method to retrieve keys:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "class HashTable():\n", + "\n", + " def __init__(self, size=17):\n", + " self.size = size\n", + " self.stored_keys = [None] * self.size\n", + " self.stored_values = [None] * self.size\n", + " \n", + " def insert(self, key, value):\n", + " \"\"\"Insert a new key-value pair or overwrites an existing one.\"\"\"\n", + " hash_val = self._hash(key)\n", + " \n", + " # insert new key if it doesn't exist yet\n", + " if self.stored_keys[hash_val] is None:\n", + " self.stored_keys[hash_val] = key\n", + " self.stored_values[hash_val] = value\n", + " \n", + " # overwrite key if it already exists\n", + " else:\n", + " if self.stored_keys[hash_val] == key:\n", + " self.stored_values[hash_val] = value\n", + " \n", + " # collision resolution\n", + " elif len(self.keys()) == self.size:\n", + " raise ValueError('Hash table is full')\n", + " \n", + " else:\n", + " next_hash = self._rehash(hash_val)\n", + " \n", + " while (self.stored_keys[next_hash] is not None\n", + " and self.stored_keys[next_hash] != key):\n", + " next_hash = self._rehash(next_hash)\n", + " \n", + " # insert new key if it doesn't exist yet\n", + " if self.stored_keys[next_hash] is None:\n", + " self.stored_keys[next_hash] = key\n", + " self.stored_values[next_hash] = value\n", + " \n", + " # overwrite key if it already exists\n", + " else:\n", + " self.stored_values[next_hash] = value\n", + " \n", + " def __setitem__(self, key, value):\n", + " return self.insert(key, value)\n", + " \n", + " def get(self, key):\n", + " \"\"\"Retrieve the value corresponding to the key.\"\"\"\n", + " \n", + " hash_val = self._hash(key)\n", + " if self.stored_keys[hash_val] == key:\n", + " return self.stored_values[hash_val]\n", + " elif self.stored_keys[hash_val] is None:\n", + " return KeyError(key)\n", + " else:\n", + " next_hash = self._rehash(hash_val)\n", + " while self.stored_keys[next_hash] != key:\n", + " next_hash = self._rehash(next_hash)\n", + " \n", + " if next_hash == hash_val:\n", + " return KeyError(key)\n", + " elif self.stored_keys[next_hash] is None:\n", + " return KeyError(key)\n", + " return self.stored_values[next_hash]\n", + " \n", + "\n", + " \n", + " def __getitem__(self, key):\n", + " return self.get(key)\n", + " \n", + " def keys(self):\n", + " \"\"\"Retrieves all keys from the hash table\"\"\"\n", + " return [k for k in self.stored_keys if k is not None]\n", + " \n", + " def _hash(self, key):\n", + " \"\"\"Computes the hash position.\"\"\"\n", + " if isinstance(key, str):\n", + " key = sum(ord(c) for c in key)\n", + " position = key % hash_table_size\n", + " return position\n", + " \n", + " def _rehash(self, previous_hash):\n", + " \"\"\"Find the next hash for linear probing\"\"\"\n", + " return (previous_hash + 1) % self.size" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "hashtable = HashTable()\n", + "hashtable['abc'] = 1\n", + "hashtable['xyz'] = 2\n", + "hashtable['abcdefgh'] = 3" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hashtable['abc']" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hashtable['xyz']" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "hashtable['abcdefgh']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the hash collision resolution works correctly for both `insert` and the `get`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/ipython_nbs/essentials/greedy-algorithm-examples.ipynb b/ipython_nbs/essentials/greedy-algorithm-examples.ipynb new file mode 100644 index 0000000..e4cff62 --- /dev/null +++ b/ipython_nbs/essentials/greedy-algorithm-examples.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext watermark" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sebastian Raschka \n", + "last updated: 2016-06-08 \n", + "\n", + "CPython 3.5.1\n", + "IPython 4.2.0\n" + ] + } + ], + "source": [ + "%watermark -a 'Sebastian Raschka' -u -d -v" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# More Greedy Algorithm Examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For an introduction to greedy algorithm, see the related notebook, [Introduction to Greedy Algorithms](greedy-algorithm-intro.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set Cover Problems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set cover problems are problems where we want to find the minimum number of subsets such that their set union contains all items in a target set. This target set is typically called the \"universe.\" To borrow an example from the excellent [Wikipedia page](https://en.wikipedia.org/wiki/Set_cover_problem) on set cover problems, let's assume we have the universe \n", + "\n", + "- $U=\\{1, 2, 3, 4, 5\\}$\n", + "\n", + "and are given the collection of sets \n", + "\n", + "- $C=\\{\\{1, 2, 3\\}, \\{2, 4\\}, \\{3, 4\\}, \\{4, 5\\}\\}$\n", + "\n", + "The task is to find the minimum number of sets in $C$ so that their union equals $U$.\n", + "\n", + "Note that set cover problems are NP-complete, thus no computationally efficient solution exists. However, we can use greedy algorithms to approximate the solution; this solution may or may not be globally optimal.\n", + "\n", + "The greedy strategy we are going to employ is very simple and works as follows:\n", + "\n", + "- While not all elements in U are covered:\n", + " - For all uncovered sets in C:\n", + " - Pick the set that covers most of the elements in U" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['set_1', 'set_2', 'set_4']\n" + ] + } + ], + "source": [ + "collection = {'set_1': {1, 2, 3},\n", + " 'set_2': {2, 4}, \n", + " 'set_3': {3, 4}, \n", + " 'set_4': {4, 5}}\n", + "sets_used = []\n", + "elements_not_covered = {1, 2, 3, 4, 5}\n", + "\n", + "\n", + "while elements_not_covered:\n", + " elements_covered = set()\n", + " for set_ in collection.keys():\n", + " \n", + " if set_ in sets_used:\n", + " continue\n", + " \n", + " current_set = collection[set_]\n", + " would_cover = elements_covered.union(current_set)\n", + " if len(would_cover) > len(elements_covered):\n", + " elements_covered = would_cover\n", + " sets_used.append(set_)\n", + " elements_not_covered -= elements_covered\n", + " \n", + " if not elements_not_covered:\n", + " break\n", + " \n", + "print(sets_used)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a result, we can see that 3 sets are required to cover the universe U. In this case, the greedy algorithm has not found the globally optimal solution, which would be `'set_1'` and `'set_4'`. Note that this is just a trivial example, and greedy algorithms can be very useful approximators for solutions that are computationally infeasible.\n", + "\n", + "For instance, an exhaustive solution to this problem that would guaranteed to find the global optimum (remember that set cover problems are NP-complete) would involve iterating over a power set, which has $2^n$ elements, where $n$ is the number of sets in the collection. For example, a collection of 30 sets would already require comparing the solutions of $2^{30}=1,073,741,824$ million possible combinations!\n", + "\n", + "(Note that the greedy approach may have found the globally optimal solution in this simple example if it had iterated over the dictionary in a different order -- for example, if we had swapped the positions of {2, 4} and {4, 5})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/ipython_nbs/search/binary_search.ipynb b/ipython_nbs/search/binary_search.ipynb index 73307b0..0177833 100644 --- a/ipython_nbs/search/binary_search.ipynb +++ b/ipython_nbs/search/binary_search.ipynb @@ -10,10 +10,10 @@ "output_type": "stream", "text": [ "Sebastian Raschka \n", - "last updated: 2017-07-25 \n", + "last updated: 2017-08-14 \n", "\n", "CPython 3.6.1\n", - "IPython 6.0.0\n" + "IPython 6.1.0\n" ] } ], @@ -47,6 +47,13 @@ "![](images/binary_search/ex-1-2.png)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Binary Search Implementation" + ] + }, { "cell_type": "code", "execution_count": 2, @@ -61,7 +68,7 @@ " max_idx = len(array)\n", " \n", " while min_idx < max_idx:\n", - " middle_idx = min_idx + (max_idx - min_idx) // 2\n", + " middle_idx = (min_idx + max_idx) // 2\n", "\n", " if array[middle_idx] == value:\n", " return middle_idx\n", @@ -76,6 +83,18 @@ { "cell_type": "code", "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "binary_search(array=[],\n", + " value=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -84,19 +103,19 @@ "0" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "binary_search(array=[1, 2, 4, 7, 8, 10, 11],\n", - " value=1)" + " value=1)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -105,19 +124,19 @@ "1" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "binary_search(array=[1, 2, 4, 7, 8, 10, 11],\n", - " value=2)" + " value=2)" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -126,7 +145,7 @@ "2" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -138,7 +157,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -147,7 +166,7 @@ "6" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -159,15 +178,145 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, + "execution_count": 8, + "metadata": {}, "outputs": [], "source": [ "binary_search(array=[1, 2, 4, 7, 8, 10, 11],\n", " value=99)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Binary Search using Recursion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that this implementation of recursive binary search deliberately avoid slicing the `array` (e.g., `array[:middle_idx]`), because slicing Python lists is expensive due to the random memory access. E.g., slicing a Python list with as `a_list[:k]` is an O(k) operation." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "def recursive_binary_search(array, value, start_idx=None, end_idx=None):\n", + " \n", + " len_ary = len(array)\n", + " \n", + " if start_idx is None:\n", + " start_idx = 0\n", + " if end_idx is None:\n", + " end_idx = len(array) - 1\n", + " \n", + " if not len_ary or start_idx >= end_idx:\n", + " return None\n", + " \n", + " middle_idx = (start_idx + end_idx) // 2\n", + " if array[middle_idx] == value:\n", + " return middle_idx\n", + "\n", + " elif array[middle_idx] > value:\n", + " return recursive_binary_search(array, \n", + " value, \n", + " start_idx=start_idx,\n", + " end_idx=middle_idx)\n", + " else:\n", + " return recursive_binary_search(array,\n", + " value,\n", + " start_idx=middle_idx + 1,\n", + " end_idx=len_ary)\n", + " return None" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "recursive_binary_search(array=[],\n", + " value=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "recursive_binary_search(array=[1, 2, 4, 7, 8, 10, 11],\n", + " value=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "recursive_binary_search(array=[1, 2, 4, 7, 8, 10, 11],\n", + " value=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "recursive_binary_search(array=[1, 2, 4, 7, 8, 10, 11],\n", + " value=11)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "recursive_binary_search(array=[1, 2, 4, 7, 8, 10, 11],\n", + " value=99)" + ] } ], "metadata": { diff --git a/ipython_nbs/search/dijkstra-algorithm.ipynb b/ipython_nbs/search/dijkstra-algorithm.ipynb new file mode 100644 index 0000000..2dc5732 --- /dev/null +++ b/ipython_nbs/search/dijkstra-algorithm.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sebastian Raschka \n", + "last updated: 2017-08-09 \n", + "\n", + "CPython 3.6.1\n", + "IPython 6.1.0\n" + ] + } + ], + "source": [ + "%load_ext watermark\n", + "%watermark -a 'Sebastian Raschka' -u -d -v" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dijkstra's Algorithm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dijkstra's algorithm is an algorithm that finds the shortest path in a directed or undirected graph. In contrast to [Breadth-First Search](breadth-first-search.ipynb), Dijkstra's algorithm works with **weighted graphs** -- that is, graphs that have different costs or length assigned to its edges. However, note that Dijkstra's algorithm does *not* work if the graph contains negative weights (in this case, different algorithms need to be used, for example, the Bellman-Ford algorithm). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A great and concise explanation of Dijkstra's algorithm was written on Wikipedia:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "> \n", + "Let the node at which we are starting be called the initial node. Let the distance of node Y be the distance from the initial node to Y. Dijkstra's algorithm will assign some initial distance values and will try to improve them step by step.\n", + "1. Assign to every node a tentative distance value: set it to zero for our initial node and to infinity for all other nodes.\n", + "2. Set the initial node as current. Mark all other nodes unvisited. Create a set of all the unvisited nodes called the unvisited set.\n", + "3. For the current node, consider all of its neighbors and calculate their tentative distances. Compare the newly calculated tentative distance to the current assigned value and assign the smaller one. For example, if the current node A is marked with a distance of 6, and the edge connecting it with a neighbor B has length 2, then the distance to B (through A) will be 6 + 2 = 8. If B was previously marked with a distance greater than 8 then change it to 8. Otherwise, keep the current value.\n", + "4. When we are done considering all of the neighbors of the current node, mark the current node as visited and remove it from the unvisited set. A visited node will never be checked again.\n", + "5. If the destination node has been marked visited (when planning a route between two specific nodes) or if the smallest tentative distance among the nodes in the unvisited set is infinity (when planning a complete traversal; occurs when there is no connection between the initial node and remaining unvisited nodes), then stop. The algorithm has finished.\n", + "6. Otherwise, select the unvisited node that is marked with the smallest tentative distance, set it as the new \"current node\", and go back to step 3. \n", + ">\n", + "[Source: Wikipedia, https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition, a great way to study Dijkstra's algorithm is to work through an example by hand, before we implement it in Python:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-1.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-2.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-3.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-3.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-4.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-5.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-6.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-7.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-8.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](images/dijkstra-algorithm/dijkstra-9.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before we implement Dijkstra's algorithm, let's convert the graph from the example above into a data structure that we could use. When we are woking with graphs, hash tables are naturally a good choice (aka Python dictionaries). Since we do not only need to sort information about which nodes are connected to each other but also have to keep track of the costs of the connections, let's use a nested dictionary that we call `graph`:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "graph = {'A': {'B': 14, 'C': 9, 'D': 7},\n", + " 'B': {'A': 14, 'C': 2, 'F': 9},\n", + " 'C': {'A': 9, 'B': 2, 'D': 7, 'E': 11},\n", + " 'D': {'A': 7, 'C':10, 'E':15},\n", + " 'E': {'C': 11, 'D':15, 'F': 6},\n", + " 'F': {'B': 9, 'E': 6}\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For example, to get the cost of the edge connecting C and B, we can use the dictionary as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph['C']['B']" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# equivalently:\n", + "graph['B']['C']" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "float('inf') > 99" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Costs: {'A': 0, 'B': 11, 'C': 9, 'D': 7, 'E': 20, 'F': 20}\n", + "Parent Nodes: {'B': 'C', 'C': 'A', 'D': 'A', 'E': 'C', 'F': 'B'}\n" + ] + } + ], + "source": [ + "def dijkstra(graph, start, destination):\n", + "\n", + " # initialize costs of starting node and its neighbors\n", + " costs = {node: float('inf') for node in graph.keys()}\n", + " costs[start] = 0\n", + " # and use parent_nodes to keep track of the chain of\n", + " # nodes that make up the shortest path\n", + " parent_nodes = {}\n", + " for neighbor in graph[start].keys():\n", + " costs[neighbor] = graph[start][neighbor]\n", + " parent_nodes[neighbor] = start\n", + " \n", + " nodes_checked = set()\n", + " while not len(nodes_checked) == len(graph.keys()):\n", + " \n", + " # get lowest cost node\n", + " min_cost, min_cost_node = float('inf'), None\n", + " for node in costs:\n", + " curr_cost = costs[node]\n", + " if curr_cost < min_cost and node not in nodes_checked:\n", + " min_cost, min_cost_node = curr_cost, node\n", + " \n", + " # check if we can reach any of the lowest cost node's\n", + " # neigbors by going through the lowest cose node\n", + " for neighbor in graph[min_cost_node].keys():\n", + " new_cost = min_cost + graph[min_cost_node][neighbor]\n", + " if new_cost < costs[neighbor]:\n", + " costs[neighbor] = new_cost\n", + " parent_nodes[neighbor] = min_cost_node\n", + " \n", + " # early stopping if we visited the destination\n", + " if neighbor == destination:\n", + " break\n", + " if neighbor == destination:\n", + " break\n", + " \n", + " # add the node to the checked nodes\n", + " nodes_checked.add(min_cost_node)\n", + " \n", + " return costs, parent_nodes\n", + "\n", + "\n", + "costs, parent_nodes = dijkstra(graph, start='A', destination='F')\n", + "\n", + "print('Costs:', costs)\n", + "print('Parent Nodes:', parent_nodes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, after running the algorithm, the `costs` dictionary provides us with the minimum cost to reach particular nodes in the graph starting at node A. For instance, the shortest path between A and F is 20.\n", + "\n", + "The `parent_nodes` dictionary maps each node along the path to its parent. For instance, to see the shortest path from A to F, we can simply backtrack starting from F:\n", + "\n", + "`'F': 'B' -> 'B': 'C' -> 'C': 'A'`\n", + "\n", + "Thus, the shortest path (with cost 20) is \n", + "\n", + "`A -> C -> B -> F`. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-1.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-1.png new file mode 100644 index 0000000..cfd9e00 Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-1.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-2.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-2.png new file mode 100644 index 0000000..49723d6 Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-2.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-3.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-3.png new file mode 100644 index 0000000..565f2b5 Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-3.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-4.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-4.png new file mode 100644 index 0000000..697f1d4 Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-4.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-5.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-5.png new file mode 100644 index 0000000..c54144e Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-5.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-6.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-6.png new file mode 100644 index 0000000..c10b523 Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-6.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-7.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-7.png new file mode 100644 index 0000000..7b3b08b Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-7.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-8.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-8.png new file mode 100644 index 0000000..9eb520d Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-8.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-9.png b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-9.png new file mode 100644 index 0000000..c170671 Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra-9.png differ diff --git a/ipython_nbs/search/images/dijkstra-algorithm/dijkstra.pptx b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra.pptx new file mode 100644 index 0000000..c6ab657 Binary files /dev/null and b/ipython_nbs/search/images/dijkstra-algorithm/dijkstra.pptx differ