{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/interactive-coding-challenges)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Solution Notebook" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem: Implement breadth-first search on a graph.\n", "\n", "* [Constraints](#Constraints)\n", "* [Test Cases](#Test-Cases)\n", "* [Algorithm](#Algorithm)\n", "* [Code](#Code)\n", "* [Unit Test](#Unit-Test)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Constraints\n", "\n", "* Is the graph directed?\n", " * Yes\n", "* Can we assume we already have Graph and Node classes?\n", " * Yes\n", "* Can we assume this is a connected graph?\n", " * Yes\n", "* Can we assume the inputs are valid?\n", " * Yes\n", "* Can we assume this fits memory?\n", " * Yes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test Cases\n", "\n", "Input:\n", "* `add_edge(source, destination, weight)`\n", "\n", "```\n", "graph.add_edge(0, 1, 5)\n", "graph.add_edge(0, 4, 3)\n", "graph.add_edge(0, 5, 2)\n", "graph.add_edge(1, 3, 5)\n", "graph.add_edge(1, 4, 4)\n", "graph.add_edge(2, 1, 6)\n", "graph.add_edge(3, 2, 7)\n", "graph.add_edge(3, 4, 8)\n", "```\n", "\n", "Result:\n", "* Order of nodes visited: [0, 1, 4, 5, 3, 2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Algorithm\n", "\n", "We generally use breadth-first search to determine the shortest path.\n", "\n", "* Add the current node to the queue and mark it as visited\n", "* While the queue is not empty\n", " * Dequeue a node and visit it\n", " * Iterate through each adjacent node\n", " * If the node has not been visited, add it to the queue and mark it as visited\n", "\n", "Complexity:\n", "* Time: O(V + E), where V = number of vertices and E = number of edges\n", "* Space: O(V)\n", "\n", "Note on space complexity from [Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search):\n", "* When the number of vertices in the graph is known ahead of time, and additional data structures are used to determine which vertices have already been added to the queue, the space complexity can be expressed as O(V) \n", "* If the graph is represented by an adjacency list it occupies O(V + E) space in memory\n", "* If the graph is represented by an adjacency matrix representation, it occupies O(V^2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Code" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "%run ../graph/graph.py" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from collections import deque\n", "\n", "\n", "class GraphBfs(Graph):\n", "\n", " def bfs(self, root, visit_func):\n", " if root is None:\n", " return\n", " queue = deque()\n", " queue.append(root)\n", " root.visit_state = State.visited\n", " while queue:\n", " node = queue.popleft()\n", " visit_func(node)\n", " for adjacent_node in node.adj_nodes.values():\n", " if adjacent_node.visit_state == State.unvisited:\n", " queue.append(adjacent_node)\n", " adjacent_node.visit_state = State.visited" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unit Test" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "%run ../utils/results.py" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting test_bfs.py\n" ] } ], "source": [ "%%writefile test_bfs.py\n", "import unittest\n", "\n", "\n", "class TestBfs(unittest.TestCase):\n", "\n", " def __init__(self, *args, **kwargs):\n", " super(TestBfs, self).__init__()\n", " self.results = Results()\n", "\n", " def test_bfs(self):\n", " nodes = []\n", " graph = GraphBfs()\n", " for id in range(0, 6):\n", " nodes.append(graph.add_node(id))\n", " graph.add_edge(0, 1, 5)\n", " graph.add_edge(0, 4, 3)\n", " graph.add_edge(0, 5, 2)\n", " graph.add_edge(1, 3, 5)\n", " graph.add_edge(1, 4, 4)\n", " graph.add_edge(2, 1, 6)\n", " graph.add_edge(3, 2, 7)\n", " graph.add_edge(3, 4, 8)\n", " graph.bfs(nodes[0], self.results.add_result)\n", " self.assertEqual(str(self.results), \"[0, 1, 4, 5, 3, 2]\")\n", "\n", " print('Success: test_bfs')\n", "\n", "\n", "def main():\n", " test = TestBfs()\n", " test.test_bfs()\n", "\n", "\n", "if __name__ == '__main__':\n", " main()" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Success: test_bfs\n" ] } ], "source": [ "%run -i test_bfs.py" ] } ], "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.7.2" } }, "nbformat": 4, "nbformat_minor": 1 }