|
1 | 1 | {
|
2 |
| - "cells": [], |
3 |
| - "metadata": {}, |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "metadata": {}, |
| 6 | + "source": [ |
| 7 | + "# Implementation of Depth-First Search\n", |
| 8 | + "This algorithm we will be discussing is Depth-First search which as the name hints at, explores possible vertices (from a supplied root) down each branch before backtracking. This property allows the algorithm to be implemented succinctly in both iterative and recursive forms. Below is a listing of the actions performed upon each visit to a node.\n", |
| 9 | + "\n", |
| 10 | + "* Mark the current vertex as being visited.\n", |
| 11 | + "* Explore each adjacent vertex that is not included in the visited set.\n", |
| 12 | + "\n", |
| 13 | + "We will assume a simplified version of a graph in the following form:" |
| 14 | + ] |
| 15 | + }, |
| 16 | + { |
| 17 | + "cell_type": "code", |
| 18 | + "execution_count": 7, |
| 19 | + "metadata": { |
| 20 | + "collapsed": true |
| 21 | + }, |
| 22 | + "outputs": [], |
| 23 | + "source": [ |
| 24 | + "graph = {'A': set(['B', 'C']),\n", |
| 25 | + " 'B': set(['A', 'D', 'E']),\n", |
| 26 | + " 'C': set(['A', 'F']),\n", |
| 27 | + " 'D': set(['B']),\n", |
| 28 | + " 'E': set(['B', 'F']),\n", |
| 29 | + " 'F': set(['C', 'E'])}" |
| 30 | + ] |
| 31 | + }, |
| 32 | + { |
| 33 | + "cell_type": "markdown", |
| 34 | + "metadata": {}, |
| 35 | + "source": [ |
| 36 | + "## Connected Component\n", |
| 37 | + "\n", |
| 38 | + "The implementation below uses the stack data-structure to build-up and return a set of vertices that are accessible within the subjects connected component. Using Python’s overloading of the subtraction operator to remove items from a set, we are able to add only the unvisited adjacent vertices." |
| 39 | + ] |
| 40 | + }, |
| 41 | + { |
| 42 | + "cell_type": "code", |
| 43 | + "execution_count": 15, |
| 44 | + "metadata": { |
| 45 | + "collapsed": false |
| 46 | + }, |
| 47 | + "outputs": [ |
| 48 | + { |
| 49 | + "data": { |
| 50 | + "text/plain": [ |
| 51 | + "{'A', 'B', 'C', 'D', 'E', 'F'}" |
| 52 | + ] |
| 53 | + }, |
| 54 | + "execution_count": 15, |
| 55 | + "metadata": {}, |
| 56 | + "output_type": "execute_result" |
| 57 | + } |
| 58 | + ], |
| 59 | + "source": [ |
| 60 | + "def dfs(graph, start):\n", |
| 61 | + " visited, stack = set(), [start]\n", |
| 62 | + " while stack:\n", |
| 63 | + " vertex = stack.pop()\n", |
| 64 | + " if vertex not in visited:\n", |
| 65 | + " visited.add(vertex)\n", |
| 66 | + " stack.extend(graph[vertex] - visited)\n", |
| 67 | + " return visited\n", |
| 68 | + "\n", |
| 69 | + "dfs(graph, 'A') " |
| 70 | + ] |
| 71 | + }, |
| 72 | + { |
| 73 | + "cell_type": "markdown", |
| 74 | + "metadata": {}, |
| 75 | + "source": [ |
| 76 | + "The second implementation provides the same functionality as the first, however, this time we are using the more succinct recursive form. Due to a common Python gotcha with default parameter values being created only once, we are required to create a new visited set on each user invocation. Another Python language detail is that function variables are passed by reference, resulting in the visited mutable set not having to reassigned upon each recursive call." |
| 77 | + ] |
| 78 | + }, |
| 79 | + { |
| 80 | + "cell_type": "code", |
| 81 | + "execution_count": 12, |
| 82 | + "metadata": { |
| 83 | + "collapsed": false |
| 84 | + }, |
| 85 | + "outputs": [ |
| 86 | + { |
| 87 | + "data": { |
| 88 | + "text/plain": [ |
| 89 | + "{'A', 'B', 'C', 'D', 'E', 'F'}" |
| 90 | + ] |
| 91 | + }, |
| 92 | + "execution_count": 12, |
| 93 | + "metadata": {}, |
| 94 | + "output_type": "execute_result" |
| 95 | + } |
| 96 | + ], |
| 97 | + "source": [ |
| 98 | + "def dfs(graph, start, visited=None):\n", |
| 99 | + " if visited is None:\n", |
| 100 | + " visited = set()\n", |
| 101 | + " visited.add(start)\n", |
| 102 | + " for nxt in graph[start] - visited:\n", |
| 103 | + " dfs(graph, nxt, visited)\n", |
| 104 | + " return visited\n", |
| 105 | + "\n", |
| 106 | + "dfs(graph, 'A') " |
| 107 | + ] |
| 108 | + }, |
| 109 | + { |
| 110 | + "cell_type": "markdown", |
| 111 | + "metadata": {}, |
| 112 | + "source": [ |
| 113 | + "## Paths\n", |
| 114 | + "We are able to tweak both of the previous implementations to return all possible paths between a start and goal vertex. The implementation below uses the stack data-structure again to iteratively solve the problem, yielding each possible path when we locate the goal. Using a generator allows the user to only compute the desired amount of alternative paths." |
| 115 | + ] |
| 116 | + }, |
| 117 | + { |
| 118 | + "cell_type": "code", |
| 119 | + "execution_count": 11, |
| 120 | + "metadata": { |
| 121 | + "collapsed": false |
| 122 | + }, |
| 123 | + "outputs": [ |
| 124 | + { |
| 125 | + "data": { |
| 126 | + "text/plain": [ |
| 127 | + "[['A', 'B', 'E', 'F'], ['A', 'C', 'F']]" |
| 128 | + ] |
| 129 | + }, |
| 130 | + "execution_count": 11, |
| 131 | + "metadata": {}, |
| 132 | + "output_type": "execute_result" |
| 133 | + } |
| 134 | + ], |
| 135 | + "source": [ |
| 136 | + "def dfs_paths(graph, start, goal):\n", |
| 137 | + " stack = [(start, [start])]\n", |
| 138 | + " while stack:\n", |
| 139 | + " (vertex, path) = stack.pop()\n", |
| 140 | + " for nxt in graph[vertex] - set(path):\n", |
| 141 | + " if nxt == goal:\n", |
| 142 | + " yield path + [nxt]\n", |
| 143 | + " else:\n", |
| 144 | + " stack.append((nxt, path + [nxt]))\n", |
| 145 | + "\n", |
| 146 | + "list(dfs_paths(graph, 'A', 'F'))" |
| 147 | + ] |
| 148 | + }, |
| 149 | + { |
| 150 | + "cell_type": "markdown", |
| 151 | + "metadata": {}, |
| 152 | + "source": [ |
| 153 | + "### Resources\n", |
| 154 | + "* [Depth-and Breadth-First Search](http://jeremykun.com/2013/01/22/depth-and-breadth-first-search/)\n", |
| 155 | + "* [Connected component](https://en.wikipedia.org/wiki/Connected_component_(graph_theory))\n", |
| 156 | + "* [Adjacency matrix](https://en.wikipedia.org/wiki/Adjacency_matrix)\n", |
| 157 | + "* [Adjacency list](https://en.wikipedia.org/wiki/Adjacency_list)\n", |
| 158 | + "* [Python Gotcha: Default arguments and mutable data structures](https://developmentality.wordpress.com/2010/08/23/python-gotcha-default-arguments/)\n", |
| 159 | + "* [Generators](https://wiki.python.org/moin/Generators)" |
| 160 | + ] |
| 161 | + } |
| 162 | + ], |
| 163 | + "metadata": { |
| 164 | + "kernelspec": { |
| 165 | + "display_name": "Python 2", |
| 166 | + "language": "python", |
| 167 | + "name": "python2" |
| 168 | + }, |
| 169 | + "language_info": { |
| 170 | + "codemirror_mode": { |
| 171 | + "name": "ipython", |
| 172 | + "version": 2 |
| 173 | + }, |
| 174 | + "file_extension": ".py", |
| 175 | + "mimetype": "text/x-python", |
| 176 | + "name": "python", |
| 177 | + "nbconvert_exporter": "python", |
| 178 | + "pygments_lexer": "ipython2", |
| 179 | + "version": "2.7.11" |
| 180 | + } |
| 181 | + }, |
4 | 182 | "nbformat": 4,
|
5 | 183 | "nbformat_minor": 0
|
6 | 184 | }
|
0 commit comments