diff --git a/README.md b/README.md index d4d493fbce464f0b2ff4269445b7e4e9c340ba66..eb5dde9020f334dd53705715679bc3a394f20241 100644 --- a/README.md +++ b/README.md @@ -105,10 +105,11 @@ Video on [Binary Tree](https://www.youtube.com/watch?v=pkYVOmU3MgA&t=11510s) 📝 Assignment 1 - Analyzing a dataset -### Exam (december) - -- Final exam +### Exam (december 5th) +- Final written exam +- 2h +- No document ## Books and ressources diff --git a/figures/Logo_ECL.png b/figures/Logo_ECL.png deleted file mode 100644 index 00b57d6ca30841a9eb9360ded009fad011f38f6e..0000000000000000000000000000000000000000 Binary files a/figures/Logo_ECL.png and /dev/null differ diff --git a/figures/a-series-paper-sizes-1.jpg b/figures/a-series-paper-sizes-1.jpg deleted file mode 100755 index 76178c439fc92ea68d321743f7536bc527cf2310..0000000000000000000000000000000000000000 Binary files a/figures/a-series-paper-sizes-1.jpg and /dev/null differ diff --git a/figures/big-o-chart.png b/figures/big-o-chart.png deleted file mode 100644 index b9a195e17ecdc9cadadf1c1d6a12ffda9eff4c2c..0000000000000000000000000000000000000000 Binary files a/figures/big-o-chart.png and /dev/null differ diff --git a/figures/flowchart.png b/figures/flowchart.png deleted file mode 100644 index c85c4e20f8f7ed2c2f9fe26fb91fbdd845b47437..0000000000000000000000000000000000000000 Binary files a/figures/flowchart.png and /dev/null differ diff --git a/figures/logo-ecl.png b/figures/logo-ecl.png deleted file mode 100644 index 64dbe6a8dbcc291560a7d01f352614c0eafb5cba..0000000000000000000000000000000000000000 Binary files a/figures/logo-ecl.png and /dev/null differ diff --git a/figures/logo-emlyon.png b/figures/logo-emlyon.png deleted file mode 100644 index d55b2f66777a0e61adeeecf372e08e414f9353e0..0000000000000000000000000000000000000000 Binary files a/figures/logo-emlyon.png and /dev/null differ diff --git a/figures/xkcd_fixing_problems.png b/figures/xkcd_fixing_problems.png deleted file mode 100644 index 67d1fce7b1617bc9ba438cdbb7184647a9a9a7ba..0000000000000000000000000000000000000000 Binary files a/figures/xkcd_fixing_problems.png and /dev/null differ diff --git a/lectures/08-binary-trees slides.pdf b/lectures/08-binary-trees slides.pdf index 5db8adba5cb9b9ca412ea71a3bbe3eb22c60100f..e3205b78a3cceb5ad61261c40580677e5349d71b 100644 Binary files a/lectures/08-binary-trees slides.pdf and b/lectures/08-binary-trees slides.pdf differ diff --git a/lectures/09-binary-trees-traversals slides.pdf b/lectures/09-binary-trees-traversals slides.pdf index 1134dbd6ac9cfcd63963302af29abfe23aa2cee5..0290dbe2c9c6086029662c8fe663d53cf4ee2632 100644 Binary files a/lectures/09-binary-trees-traversals slides.pdf and b/lectures/09-binary-trees-traversals slides.pdf differ diff --git a/lectures/10-trees slides.pdf b/lectures/10-trees slides.pdf index 045ef4c97789f48f5e0007aa83e0682d3c59b1ee..ffe565a7777995c02e999beec0a90289698ebcd9 100644 Binary files a/lectures/10-trees slides.pdf and b/lectures/10-trees slides.pdf differ diff --git a/lectures/11-graphs slides.pdf b/lectures/11-graphs slides.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dd255643c69bc7f0bbb7f471dbfef32328d730e0 Binary files /dev/null and b/lectures/11-graphs slides.pdf differ diff --git a/notebooks/08-binary-trees.ipynb b/notebooks/08-binary-trees.ipynb index 0c2e36db398e732613a3a72c9fbdee871f51762f..d4d90d26415f3e39bc98129501b221917e50f636 100644 --- a/notebooks/08-binary-trees.ipynb +++ b/notebooks/08-binary-trees.ipynb @@ -24,6 +24,24 @@ "---" ] }, + { + "cell_type": "code", + "execution_count": 3, + "id": "93dd9eb2", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "\n", + "#sys.path.append(\"../../\")\n", + "from utils import draw_directed_graph" + ] + }, { "cell_type": "markdown", "id": "f3ebe7d2", @@ -51,18 +69,12 @@ "source": [ "## Definitions\n", "\n", - "> Tree is a hierarchical data structure with nodes connected by edges\n", + "> A **Tree** is a hierarchical data structure with nodes (vertex) connected by links (edge)\n", "\n", "- A non-linear data structures (multiple ways to traverse it)\n", "- Nodes are connected by only one path (a series of edges) so trees have no cycle\n", "- Edges are also called links, they can be traversed in both ways (no orientation)\n", - "\n", - "We focus on _binary trees._\n", - "\n", - "> Trees that have at most two children\n", - "\n", - "- Children can be ordered left child and the right child\n", - "\n" + "- Trees are most commonly represented as a node-lin diagram, with the root at the top and the leaves (nodes without children) at the bottom)." ] }, { @@ -74,14 +86,20 @@ } }, "source": [ - "## Binary trees representation\n", + "## Binary trees\n", + "\n", + "We focus on _binary trees._\n", "\n", - "Trees are most commonly represented as a node-lin diagram, with the root at the top and the leaves (nodes without children) at the bottom)." + "> Trees that have at most two children\n", + "\n", + "- Children are ordered (left and right)\n", + "\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 278, + "execution_count": 4, "id": "51f0cf57", "metadata": { "slideshow": { @@ -98,69 +116,45 @@ "<!-- Generated by graphviz version 7.1.0 (20230121.1956)\n", " -->\n", "<!-- Pages: 1 -->\n", - "<svg width=\"212pt\" height=\"188pt\"\n", - " viewBox=\"0.00 0.00 212.19 188.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n", - "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n", - "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-184 208.19,-184 208.19,4 -4,4\"/>\n", - "<!-- ROOT -->\n", + "<svg width=\"134pt\" height=\"116pt\"\n", + " viewBox=\"0.00 0.00 134.00 116.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n", + "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 112)\">\n", + "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-112 130,-112 130,4 -4,4\"/>\n", + "<!-- A -->\n", "<g id=\"node1\" class=\"node\">\n", - "<title>ROOT</title>\n", - "<ellipse fill=\"none\" stroke=\"black\" cx=\"121.8\" cy=\"-162\" rx=\"34.39\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"121.8\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">ROOT</text>\n", + "<title>A</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"63\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"63\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">A</text>\n", "</g>\n", - "<!-- Node 1 -->\n", + "<!-- B -->\n", "<g id=\"node2\" class=\"node\">\n", - "<title>Node 1</title>\n", - "<ellipse fill=\"none\" stroke=\"black\" cx=\"76.8\" cy=\"-90\" rx=\"36.29\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"76.8\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">Node 1</text>\n", + "<title>B</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">B</text>\n", "</g>\n", - "<!-- ROOT->Node 1 -->\n", + "<!-- A->B -->\n", "<g id=\"edge1\" class=\"edge\">\n", - "<title>ROOT->Node 1</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M111.13,-144.41C105.86,-136.22 99.39,-126.14 93.48,-116.95\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"96.56,-115.26 88.2,-108.74 90.67,-119.05 96.56,-115.26\"/>\n", + "<title>A->B</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M54.65,-72.76C50.42,-64.55 45.19,-54.37 40.42,-45.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"43.68,-43.79 36,-36.49 37.46,-46.99 43.68,-43.79\"/>\n", "</g>\n", - "<!-- Node 2 -->\n", - "<g id=\"node5\" class=\"node\">\n", - "<title>Node 2</title>\n", - "<ellipse fill=\"none\" stroke=\"black\" cx=\"167.8\" cy=\"-90\" rx=\"36.29\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"167.8\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">Node 2</text>\n", - "</g>\n", - "<!-- ROOT->Node 2 -->\n", - "<g id=\"edge4\" class=\"edge\">\n", - "<title>ROOT->Node 2</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M132.47,-144.76C137.85,-136.58 144.5,-126.45 150.58,-117.2\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"153.47,-119.18 156.04,-108.9 147.62,-115.33 153.47,-119.18\"/>\n", - "</g>\n", - "<!-- Leaf 1 -->\n", + "<!-- C -->\n", "<g id=\"node3\" class=\"node\">\n", - "<title>Leaf 1</title>\n", - "<ellipse fill=\"none\" stroke=\"black\" cx=\"33.8\" cy=\"-18\" rx=\"33.6\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"33.8\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Leaf 1</text>\n", + "<title>C</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n", "</g>\n", - "<!-- Node 1->Leaf 1 -->\n", + "<!-- A->C -->\n", "<g id=\"edge2\" class=\"edge\">\n", - "<title>Node 1->Leaf 1</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M66.61,-72.41C61.63,-64.3 55.51,-54.35 49.92,-45.25\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"52.92,-43.45 44.71,-36.76 46.96,-47.11 52.92,-43.45\"/>\n", - "</g>\n", - "<!-- Leaf 2 -->\n", - "<g id=\"node4\" class=\"node\">\n", - "<title>Leaf 2</title>\n", - "<ellipse fill=\"none\" stroke=\"black\" cx=\"119.8\" cy=\"-18\" rx=\"33.6\" ry=\"18\"/>\n", - "<text text-anchor=\"middle\" x=\"119.8\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Leaf 2</text>\n", - "</g>\n", - "<!-- Node 1->Leaf 2 -->\n", - "<g id=\"edge3\" class=\"edge\">\n", - "<title>Node 1->Leaf 2</title>\n", - "<path fill=\"none\" stroke=\"black\" d=\"M86.99,-72.41C91.97,-64.3 98.08,-54.35 103.67,-45.25\"/>\n", - "<polygon fill=\"black\" stroke=\"black\" points=\"106.64,-47.11 108.89,-36.76 100.67,-43.45 106.64,-47.11\"/>\n", + "<title>A->C</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M71.35,-72.76C75.58,-64.55 80.81,-54.37 85.58,-45.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"88.54,-46.99 90,-36.49 82.32,-43.79 88.54,-46.99\"/>\n", "</g>\n", "</g>\n", "</svg>\n" ], "text/plain": [ - "<graphviz.graphs.Digraph at 0x104c33bb0>" + "<graphviz.graphs.Digraph at 0x1040b0f40>" ] }, "metadata": {}, @@ -168,7 +162,11 @@ } ], "source": [ - "draw_binary_tree(binary_tree)" + "T = {\n", + " 'A': ['B', 'C'],\n", + "}\n", + "\n", + "draw_directed_graph(T)" ] }, { @@ -208,14 +206,14 @@ } }, "source": [ - "## Binary trees data structures (dictionnaries and lists)\n", + "## Binary trees data structures (dict + lists)\n", "\n", "_Binary trees using dictionnaries where nodes are keys and edges are Lists._" ] }, { "cell_type": "code", - "execution_count": 199, + "execution_count": 5, "id": "d495c8a5", "metadata": {}, "outputs": [], @@ -243,7 +241,7 @@ }, { "cell_type": "code", - "execution_count": 200, + "execution_count": 6, "id": "5df1c518", "metadata": {}, "outputs": [], @@ -263,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 201, + "execution_count": 7, "id": "abc855b1", "metadata": {}, "outputs": [], @@ -329,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 254, + "execution_count": 8, "id": "1fbb1c2f", "metadata": { "slideshow": { @@ -347,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 255, + "execution_count": 9, "id": "6b4492dc", "metadata": {}, "outputs": [ @@ -357,7 +355,7 @@ "'A'" ] }, - "execution_count": 255, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -368,7 +366,7 @@ }, { "cell_type": "code", - "execution_count": 256, + "execution_count": 10, "id": "ea01e802", "metadata": {}, "outputs": [], @@ -387,14 +385,14 @@ } }, "source": [ - "### Get the list of nodes\n", + "### Get all nodes from a Tree\n", "\n", "_Return all the nodes in the tree (as a list of nodes names)._" ] }, { "cell_type": "code", - "execution_count": 205, + "execution_count": 11, "id": "3af082b7", "metadata": { "slideshow": { @@ -409,7 +407,7 @@ }, { "cell_type": "code", - "execution_count": 206, + "execution_count": 12, "id": "ede5b5f4", "metadata": {}, "outputs": [ @@ -419,7 +417,7 @@ "['A', 'B', 'C', 'D', 'E']" ] }, - "execution_count": 206, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -430,7 +428,7 @@ }, { "cell_type": "code", - "execution_count": 208, + "execution_count": 13, "id": "2d3305d5", "metadata": {}, "outputs": [], @@ -448,14 +446,14 @@ } }, "source": [ - "### Get the list of edges\n", + "### Get all links from a Tree\n", "\n", - "_Return all the edges as a list of pairs as `Tuple`._" + "_Return all the links as a list of pairs as `Tuple`._" ] }, { "cell_type": "code", - "execution_count": 209, + "execution_count": 14, "id": "b50fe9c2", "metadata": { "slideshow": { @@ -464,17 +462,17 @@ }, "outputs": [], "source": [ - "def get_edges(graph):\n", - " edges = []\n", - " for node, neighbors in graph.items():\n", + "def get_links(tree):\n", + " links = []\n", + " for node, neighbors in tree.items():\n", " for neighbor in neighbors:\n", - " edges.append((node, neighbor))\n", - " return edges" + " links.append((node, neighbor))\n", + " return links" ] }, { "cell_type": "code", - "execution_count": 210, + "execution_count": 15, "id": "8958bd83", "metadata": {}, "outputs": [ @@ -484,24 +482,24 @@ "[('A', 'B'), ('A', 'C'), ('B', 'D'), ('B', 'E')]" ] }, - "execution_count": 210, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "get_edges(T)" + "get_links(T)" ] }, { "cell_type": "code", - "execution_count": 211, + "execution_count": 16, "id": "30dd31d3", "metadata": {}, "outputs": [], "source": [ - "assert get_edges(T) == [('A', 'B'), ('A', 'C'), ('B', 'D'), ('B', 'E')]\n", - "assert get_edges({}) == []" + "assert get_links(T) == [('A', 'B'), ('A', 'C'), ('B', 'D'), ('B', 'E')]\n", + "assert get_links({}) == []" ] }, { @@ -520,7 +518,7 @@ }, { "cell_type": "code", - "execution_count": 217, + "execution_count": 17, "id": "37fbe31b", "metadata": { "slideshow": { @@ -529,8 +527,8 @@ }, "outputs": [], "source": [ - "def get_parent(graph, node_to_find):\n", - " for parent, neighbors in graph.items():\n", + "def get_parent(tree, node_to_find):\n", + " for parent, neighbors in tree.items():\n", " if node_to_find in neighbors:\n", " return parent\n", " return None" @@ -538,7 +536,7 @@ }, { "cell_type": "code", - "execution_count": 218, + "execution_count": 18, "id": "78e88d23", "metadata": {}, "outputs": [], @@ -553,18 +551,18 @@ "id": "3fb6f347", "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "subslide" } }, "source": [ "### Check if the node is the root\n", "\n", - "_Return True if the root not, else `None`." + "_Return True if the root not, else `None`._" ] }, { "cell_type": "code", - "execution_count": 226, + "execution_count": 19, "id": "164e4ef7", "metadata": { "slideshow": { @@ -574,12 +572,12 @@ "outputs": [], "source": [ "def is_root(T, node):\n", - " return find_parent(T, node) is None" + " return get_parent(T, node) is None" ] }, { "cell_type": "code", - "execution_count": 227, + "execution_count": 20, "id": "5c053617", "metadata": {}, "outputs": [], @@ -603,7 +601,7 @@ }, { "cell_type": "code", - "execution_count": 228, + "execution_count": 21, "id": "ac145c20", "metadata": { "slideshow": { @@ -619,7 +617,7 @@ }, { "cell_type": "code", - "execution_count": 229, + "execution_count": 22, "id": "9444a66f", "metadata": {}, "outputs": [], @@ -645,7 +643,7 @@ }, { "cell_type": "code", - "execution_count": 233, + "execution_count": 23, "id": "77f5f17c", "metadata": { "slideshow": { @@ -660,7 +658,7 @@ }, { "cell_type": "code", - "execution_count": 234, + "execution_count": 24, "id": "5f5078d6", "metadata": {}, "outputs": [], @@ -689,7 +687,7 @@ }, { "cell_type": "code", - "execution_count": 267, + "execution_count": 25, "id": "c9312a43", "metadata": { "slideshow": { @@ -714,10 +712,21 @@ }, { "cell_type": "code", - "execution_count": 268, + "execution_count": 26, "id": "38181a0f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'A': ['F']}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "U = {\"A\": []}\n", "add_node(U, \"A\", 'F')\n", @@ -743,38 +752,7 @@ }, { "cell_type": "code", - "execution_count": 295, - "id": "ee2973b7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'A': ['B', 'C'], 'B': ['D', 'E'], 'C': [], 'D': [], 'E': []}" - ] - }, - "execution_count": 295, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "T" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b7ef1fab", - "metadata": {}, - "outputs": [], - "source": [ - "v" - ] - }, - { - "cell_type": "code", - "execution_count": 291, + "execution_count": 27, "id": "6ef9af29", "metadata": { "slideshow": { @@ -797,7 +775,7 @@ }, { "cell_type": "code", - "execution_count": 292, + "execution_count": 28, "id": "44a54ec8", "metadata": {}, "outputs": [], @@ -853,7 +831,9 @@ "\n", "In a complete or balanced binary tree: \n", "- if the index of a node is equal to $i$, then the position indicating its left child is at $2i$, \n", - "- and the position indicating its right child is at $2i + 1$." + "- and the position indicating its right child is at $2i + 1$.\n", + "\n", + "Also works for ternary trees, etc." ] }, { @@ -870,7 +850,7 @@ }, { "cell_type": "code", - "execution_count": 275, + "execution_count": 29, "id": "610ad3bb", "metadata": {}, "outputs": [ @@ -957,10 +937,10 @@ "</svg>\n" ], "text/plain": [ - "<graphviz.graphs.Digraph at 0x104c32b30>" + "<graphviz.graphs.Digraph at 0x1040b1000>" ] }, - "execution_count": 275, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -986,55 +966,8 @@ "dot.edge('0', '2', color='red')\n", "dot.edge('2', '3', color='red')\n", "\n", - "\n", - "dot # Render the graph" - ] - }, - { - "cell_type": "markdown", - "id": "01880f1d", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Visualize a tree" - ] - }, - { - "cell_type": "code", - "execution_count": 296, - "id": "3a064bfb", - "metadata": {}, - "outputs": [], - "source": [ - "from graphviz import Digraph\n", - "from IPython.display import display\n", - "\n", - "def draw_binary_tree(tree_dict):\n", - " dot = Digraph(format='png')\n", - " \n", - " def add_nodes_and_edges(node, parent_name=None):\n", - " if isinstance(node, dict):\n", - " for key, value in node.items():\n", - " dot.node(key, key)\n", - " if parent_name:\n", - " dot.edge(parent_name, key)\n", - " add_nodes_and_edges(value, key)\n", - "\n", - " add_nodes_and_edges(tree_dict)\n", - " \n", - " display(dot)" + "dot # render" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb7726ee", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/09-binary-trees-traversals.ipynb b/notebooks/09-binary-trees-traversals.ipynb index 7ba83bef2fe0234932750be0fdf0d8c07c16c86c..7a5bf682d4771ec18edcd9a45057b74ad5ecd700 100644 --- a/notebooks/09-binary-trees-traversals.ipynb +++ b/notebooks/09-binary-trees-traversals.ipynb @@ -28,6 +28,20 @@ "---" ] }, + { + "cell_type": "code", + "execution_count": 24, + "id": "c30e8f1b", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "from graphviz import Digraph\n", + "from IPython.display import display\n", + "from utils import draw_binary_tree" + ] + }, { "cell_type": "markdown", "id": "6efad77c", @@ -53,11 +67,13 @@ } }, "source": [ - "## Traversal methods\n", + "## Binary trees traversal methods\n", "\n", - "> Methodes to explore and process all the nodes in a binary tree\n", + "> Methods to explore and process nodes in a tree (or a graph).\n", "\n", - "- Because Trees are non-linear, there are multiple possible paths" + "- Because Trees are non-linear, there are multiple possible paths\n", + "- Can be applied to the whole tree or until a certain condition is met\n", + "- Traversals methods will provide very different results" ] }, { @@ -73,13 +89,14 @@ "\n", "<img src=\"figures/arbre-largeur-hauteur.png\" style=\"width: 400px\">\n", "\n", - "1. **Depth-First Traversal (DFS):**\n", + "1. **Depth-First search (DFS):**\n", " - visiting a node (sarting with the root)\n", " - then recursively traversing as deep as possible \n", " - then explore another branch.\n", "\n", - "2. **Breadth-First Traversal (BFS):**\n", - " - visiting a node (sarting with the root)\n", + "\n", + "2. **Breadth-First search (BFS):**\n", + " - visiting a node ( with the root)\n", " - explore all its neighbors (children) \n", " - then mode move to the children." ] @@ -93,52 +110,75 @@ } }, "source": [ - "## Depth-first\n", + "## Depth-first search (or traversal)\n", "\n", - "**Pseudo-code for Depth-First Traversal:**\n", + "> **Depth-first search (DFS)** is a traversal method that visits all the leaves first in a tree (or a graph).\n", "\n", - "1. Place the source node in the **stack**.\n", + "1. Place the source node in a **stack**.\n", "2. Remove the node from the top of the stack for processing.\n", "3. Add all unexplored neighbors to the stack (at the top).\n", "4. If the stack is not empty, go back to step 2.\n" ] }, + { + "cell_type": "markdown", + "id": "6e0cf5f3", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Depth-first search (or traversal)" + ] + }, { "cell_type": "code", - "execution_count": 60, - "id": "41790c1d", + "execution_count": 25, + "id": "e6d32fe3", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "A\n", - "B\n", - "D\n", - "E\n", - "F\n", - "C\n" - ] - } - ], + "outputs": [], "source": [ - "def dfs(graph, start):\n", + "def dfs(tree, start):\n", " stack = [start]\n", " while stack:\n", " vertex = stack.pop()\n", - " print(vertex) # traitement\n", - " stack.extend(graph[vertex])\n", - "\n", - "graph = {'A': set(['B', 'C']),\n", + " print(vertex, end = ' ') # traitement\n", + " stack.extend(tree[vertex])" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "c49f305d", + "metadata": {}, + "outputs": [], + "source": [ + "tree = {'A': set(['B', 'C']),\n", " 'B': set(['D', 'E', 'F']),\n", " 'C': set([]),\n", " 'D': set([]),\n", " 'E': set([]),\n", " 'F': set([])\n", - " }\n", - "\n", - "dfs(graph, 'A') # A B D F E C" + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "b825ca4a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A B F E D C " + ] + } + ], + "source": [ + "dfs(tree, 'A') # A B D F E C" ] }, { @@ -150,15 +190,15 @@ } }, "source": [ - "## Depth-first traversal: pre-order, in-order, and post-order.\n", + "## Depth-first search: pre-order, in-order, and post-order.\n", "\n", - "For **depth-first traversal**, there are different types of processing: *pre-order*, *in-order*, and *post-order*.\n", + "For **depth-first search**, there are different types of processing: *pre-order*, *in-order*, and *post-order*, based on when the processing is done (before/after exploring the root or the children). Notation :\n", "\n", "- R = Root\n", "- D = Right subtree\n", "- G = Left subtree\n", "\n", - "There are three (main) types of traversal, observing the position of R:\n", + "There are three (main) types of traversal:\n", "\n", "- **Pre-order**: R G D\n", "- **In-order**: G R D\n", @@ -176,26 +216,26 @@ "source": [ "## Depth-first traversal: pre-order, in-order, and post-order.\n", "\n", - "For **depth-first traversal**, there are different types of processing: *pre-order*, *post-order*, and *in-order*.\n", + "Implementation of the strategies:\n", "\n", "```python\n", - "def Preorder(R):\n", + "def preorder(R):\n", " if not empty(R):\n", - " process(R) # Root\n", - " Preorder(left(R)) # Left\n", - " Preorder(right(R)) # Right\n", + " process(R) # Root\n", + " preorder(left(R)) # Left\n", + " preorder(right(R)) # Right\n", "\n", - "def Inorder(R):\n", + "def inorder(R):\n", " if not empty(R):\n", - " Inorder(left(R)) # Left\n", - " process(R) # Root\n", - " Inorder(right(R)) # Right\n", + " inorder(left(R)) # Left\n", + " process(R) # Root\n", + " inorder(right(R)) # Right\n", "\n", - "def Postorder(R):\n", + "def postorder(R):\n", " if not empty(R):\n", - " Postorder(left(R)) # Left\n", - " Postorder(right(R)) # Right\n", - " process(R) # Rooot\n" + " postorder(left(R)) # Left\n", + " postorder(right(R)) # Right\n", + " postorder(R) # Rooot\n" ] }, { @@ -207,138 +247,64 @@ } }, "source": [ - "## Depth-first traversal: pre-order\n", - "_Iterative implementation._" - ] - }, - { - "cell_type": "code", - "execution_count": 62, - "id": "b0d812d9", - "metadata": {}, - "outputs": [], - "source": [ - "def iterative_inorder_traversal(root):\n", - " stack = []\n", - " current = root\n", - " while current is not None or stack:\n", - " while current is not None:\n", - " stack.append(current)\n", - " current = current.left\n", - " current = stack.pop()\n", - " print(current.value)\n", - " current = current.right" - ] - }, - { - "cell_type": "markdown", - "id": "f368960e", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Depth-first traversal: pre-order\n", - "_Recursive implementation._" + "## Example\n", + "\n", + "We will use this data structure" ] }, { "cell_type": "code", - "execution_count": 114, - "id": "552dc46b", + "execution_count": 28, + "id": "c0785d33", "metadata": {}, "outputs": [], "source": [ - "TT = {\"dog\": [\"little\", \"very\"],\n", - " \"little\": [\"the\"],\n", - " \"the\": [],\n", - " \"very\": [\"is\", \"cute\"],\n", - " \"is\": [],\n", - " \"cute\": []\n", - " }" + "class Node:\n", + " def __init__(self, value):\n", + " self.value = value\n", + " self.left = None\n", + " self.right = None\n", + " \n", + " def get_value(self):\n", + " return self.value\n", + " \n", + " def set_value(self, v = None):\n", + " self.value = v" ] }, { "cell_type": "code", - "execution_count": 117, - "id": "2cafcf1c", + "execution_count": 29, + "id": "ffcda583", "metadata": {}, "outputs": [], "source": [ - "def preorder(T, node):\n", - " if node is not None:\n", - " if len(T[node]) > 0:\n", - " preorder(T, T[node][0])\n", - " print(node)\n", - " if len(T[node]) > 1:\n", - " preorder(T, T[node][1])" - ] - }, - { - "cell_type": "code", - "execution_count": 118, - "id": "8b8f5c52", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "the\n", - "little\n", - "dog\n", - "is\n", - "very\n", - "cute\n" - ] - } - ], - "source": [ - "preorder(TT, \"dog\")" + "root = Node(\"dog\")\n", + "root.left = Node(\"little\")\n", + "root.left.left = Node(\"the\")\n", + "root.right = Node(\"very\")\n", + "root.right.left = Node(\"is\")\n", + "root.right.right = Node(\"cute\")" ] }, { - "cell_type": "code", - "execution_count": 68, - "id": "fbbb9408", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "the\n", - "little\n", - "dog\n", - "is\n", - "very\n", - "cute\n" - ] + "cell_type": "markdown", + "id": "5389c181", + "metadata": { + "slideshow": { + "slide_type": "subslide" } - ], + }, "source": [ - "def preorder_traversal(node):\n", - " if node is not None:\n", - " if node.left:\n", - " preorder_traversal(node.left)\n", - " print(node.value)\n", - " if node.right:\n", - " preorder_traversal(node.right)\n", + "## Example\n", "\n", - "root = Node(\"dog\")\n", - "root.left = Node(\"little\")\n", - "root.left.left = Node(\"the\")\n", - "root.right = Node(\"very\")\n", - "root.right.left = Node(\"is\")\n", - "root.right.right = Node(\"cute\")\n", - "preorder_traversal(root)" + "_How to get the sentence in the correct order?_" ] }, { "cell_type": "code", - "execution_count": 69, - "id": "72f665f2", + "execution_count": 30, + "id": "8cf38ed1", "metadata": {}, "outputs": [ { @@ -424,147 +390,337 @@ "</svg>\n" ], "text/plain": [ - "<graphviz.graphs.Digraph at 0x1045bead0>" + "<graphviz.graphs.Digraph at 0x106e7ec50>" ] }, - "execution_count": 69, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "visualize_oop(root)" + "draw_binary_tree(root)" ] }, { "cell_type": "markdown", - "id": "c88e5f58", + "id": "93045f0c", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ - "## Breadth-first traversal\n", - "\n", - "_Visit all the nodes in a tree or graph level by level._\n", - "\n", - "\n", - "```\n", - " 1\n", - " / \\\n", - " 2 3\n", - " / \\\n", - " 4 5\n", - "```" + "## Depth-first traversal pre-order (OOP + iterative)" ] }, { "cell_type": "code", - "execution_count": 81, - "id": "73ceb6d8", - "metadata": {}, + "execution_count": 31, + "id": "b0d812d9", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, "outputs": [], "source": [ - "def bfs_print(node):\n", - " if node is None:\n", - " return\n", - "\n", - " queue = [node]\n", - "\n", - " while queue:\n", - " current_node = queue.pop(0)\n", - " print(current_node.value, end=' ')\n", - "\n", - " if current_node.left:\n", - " queue.append(current_node.left)\n", - "\n", + "def iterative_inorder_traversal(node):\n", + " stack = [node]\n", + " while stack:\n", + " current_node = stack.pop()\n", + " print(current_node.value)\n", " if current_node.right:\n", - " queue.append(current_node.right)" + " stack.append(current_node.right)\n", + " if current_node.left:\n", + " stack.append(current_node.left)" ] }, { "cell_type": "code", - "execution_count": 82, - "id": "1e1a1f21", + "execution_count": 32, + "id": "b2dc9113", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "1 2 3 4 5 " + "dog\n", + "little\n", + "the\n", + "very\n", + "is\n", + "cute\n" ] } ], "source": [ - "root = Node(1)\n", - "root.left = Node(2)\n", - "root.right = Node(3)\n", - "root.left.left = Node(4)\n", - "root.left.right = Node(5)\n", - "bfs_print(root)" + "iterative_inorder_traversal(root)" ] }, { "cell_type": "markdown", - "id": "7ba7af33", + "id": "f368960e", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ - "## Utils" + "## Depth-first traversal pre-order (dict + recursive)\n", + "_Recursive implementation using a dictionnary data structure._" ] }, { "cell_type": "code", - "execution_count": 83, - "id": "2dc5a89d", + "execution_count": 33, + "id": "552dc46b", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "TT = {\"dog\": [\"little\", \"very\"],\n", + " \"little\": [\"the\"],\n", + " \"the\": [],\n", + " \"very\": [\"is\", \"cute\"],\n", + " \"is\": [],\n", + " \"cute\": []\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "2cafcf1c", "metadata": {}, "outputs": [], "source": [ - "import graphviz\n", - "from graphviz import Digraph\n", - "from IPython.display import display" + "def preorder(T, node):\n", + " if node is not None:\n", + " print(node)\n", + " if len(T[node]) > 0:\n", + " preorder(T, T[node][0])\n", + " if len(T[node]) > 1:\n", + " preorder(T, T[node][1])" ] }, { "cell_type": "code", - "execution_count": 84, - "id": "7058a231", + "execution_count": 35, + "id": "8b8f5c52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dog\n", + "little\n", + "the\n", + "very\n", + "is\n", + "cute\n" + ] + } + ], + "source": [ + "preorder(TT, \"dog\")" + ] + }, + { + "cell_type": "markdown", + "id": "75c93b72", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "_Iterative version._" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "fbbb9408", "metadata": {}, "outputs": [], "source": [ - "def visualize_oop(root):\n", - " def build(node, dot=None):\n", - " if dot is None:\n", - " dot = graphviz.Digraph(format='png')\n", - "\n", - " if node is not None:\n", - " dot.node(str(node.value))\n", + "def preorder_traversal(T, node):\n", + " stack = [node]\n", + " \n", + " while stack:\n", + " current_node = stack.pop()\n", + " print(current_node)\n", + " \n", + " if len(T[current_node]) > 1:\n", + " stack.append(T[current_node][1])\n", + " \n", + " if len(T[current_node]) > 0:\n", + " stack.append(T[current_node][0])" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "f040425d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dog\n", + "little\n", + "the\n", + "very\n", + "is\n", + "cute\n" + ] + } + ], + "source": [ + "preorder_traversal(TT, \"dog\")" + ] + }, + { + "cell_type": "markdown", + "id": "1aeacaad", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Solution: inorder traversal" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "8a950f0f", + "metadata": {}, + "outputs": [], + "source": [ + "def inorder(T, node):\n", + " if node is not None:\n", + " if len(T[node]) > 0:\n", + " inorder(T, T[node][0])\n", + " print(node)\n", + " if len(T[node]) > 1:\n", + " inorder(T, T[node][1])" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "49625d32", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "the\n", + "little\n", + "dog\n", + "is\n", + "very\n", + "cute\n" + ] + } + ], + "source": [ + "inorder(TT, \"dog\")" + ] + }, + { + "cell_type": "markdown", + "id": "c88e5f58", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Breadth-first search (or traversal)\n", "\n", - " if node.left is not None:\n", - " dot.edge(str(node.value), str(node.left.value))\n", - " build(node.left, dot)\n", + "> **Breadth-first search (BFS)** is a traversal method that visits all the nodes in a tree (or a graph) level by level.\n", "\n", - " if node.right is not None:\n", - " dot.edge(str(node.value), str(node.right.value))\n", - " build(node.right, dot)\n", "\n", - " return dot\n", + "```\n", + " 1\n", + " / \\\n", + " 2 3\n", + " / \\\n", + " 4 5\n", + "```\n", "\n", - " return build(root)\n" + "The main difference will be that we use a Queue instead of a Stack" ] }, { "cell_type": "code", - "execution_count": null, - "id": "05b69326", + "execution_count": 40, + "id": "73ceb6d8", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "def bfs_print(node):\n", + " if node is None:\n", + " return\n", + "\n", + " queue = [node]\n", + "\n", + " while queue:\n", + " current_node = queue.pop(0)\n", + " print(current_node.value, end=' ')\n", + "\n", + " if current_node.left:\n", + " queue.append(current_node.left)\n", + "\n", + " if current_node.right:\n", + " queue.append(current_node.right)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "1e1a1f21", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "root = Node(1)\n", + "root.left = Node(2)\n", + "root.right = Node(3)\n", + "root.left.left = Node(4)\n", + "root.left.right = Node(5)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "74a2317d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 2 3 4 5 " + ] + } + ], + "source": [ + "bfs_print(root)" + ] } ], "metadata": { diff --git a/notebooks/10-trees.ipynb b/notebooks/10-trees.ipynb index 5f29e68ab317339af079d125a68e01aa6770eeeb..b7ce4b33120817b4487e0fc8fbc1ca18c9ad06df 100644 --- a/notebooks/10-trees.ipynb +++ b/notebooks/10-trees.ipynb @@ -24,6 +24,20 @@ "---" ] }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a9f48d96", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "from graphviz import Digraph\n", + "from IPython.display import display\n", + "from utils import draw_tree_dict" + ] + }, { "cell_type": "markdown", "id": "f3ebe7d2", @@ -96,7 +110,7 @@ }, { "cell_type": "markdown", - "id": "ce722126", + "id": "67f767cf", "metadata": { "slideshow": { "slide_type": "subslide" @@ -118,7 +132,7 @@ }, { "cell_type": "markdown", - "id": "0612be20", + "id": "bb8e8697", "metadata": { "slideshow": { "slide_type": "subslide" @@ -132,12 +146,12 @@ }, { "cell_type": "code", - "execution_count": 147, - "id": "b66a9451", + "execution_count": 26, + "id": "53891826", "metadata": {}, "outputs": [], "source": [ - "tree = {\n", + "tree_dict = {\n", " \"a\": [\"b\", \"c\"],\n", " \"b\": [\"d\", \"e\"],\n", " \"c\": [\"f\"],\n", @@ -149,8 +163,8 @@ }, { "cell_type": "code", - "execution_count": 148, - "id": "f2bce0eb", + "execution_count": 27, + "id": "cf1c0607", "metadata": {}, "outputs": [ { @@ -236,7 +250,7 @@ "</svg>\n" ], "text/plain": [ - "<graphviz.graphs.Digraph at 0x1106595a0>" + "<graphviz.graphs.Digraph at 0x103f43d00>" ] }, "metadata": {}, @@ -244,12 +258,12 @@ } ], "source": [ - "draw_tree(tree)" + "draw_tree_dict(tree_dict)" ] }, { "cell_type": "markdown", - "id": "0aa22e17", + "id": "4f833d5f", "metadata": { "slideshow": { "slide_type": "subslide" @@ -263,12 +277,12 @@ }, { "cell_type": "code", - "execution_count": 168, - "id": "16070d37", + "execution_count": 28, + "id": "15a30278", "metadata": {}, "outputs": [], "source": [ - "tree = {\n", + "tree_dict_name = {\n", " \"a\": {\"neighbors\": [\"b\", \"c\"]},\n", " \"b\": {\"neighbors\": [\"d\", \"e\"]},\n", " \"c\": {\"neighbors\": [\"f\"]},\n", @@ -280,8 +294,8 @@ }, { "cell_type": "code", - "execution_count": 169, - "id": "4c182c85", + "execution_count": 29, + "id": "bf6182a0", "metadata": {}, "outputs": [ { @@ -290,13 +304,13 @@ "['b', 'c']" ] }, - "execution_count": 169, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "tree[\"a\"][\"neighbors\"]" + "tree_dict_name[\"a\"][\"neighbors\"]" ] }, { @@ -310,18 +324,17 @@ "source": [ "## Data structures (sets)\n", "\n", - "- The children are not ordered\n", - "- Children names are unique" + "- The children are unique and not ordered" ] }, { "cell_type": "code", - "execution_count": 115, + "execution_count": 30, "id": "d996b53e", "metadata": {}, "outputs": [], "source": [ - "tree = {\n", + "tree_set = {\n", " \"a\": set([\"b\", \"c\"]),\n", " \"b\": set([\"d\", \"e\"]),\n", " \"c\": set([\"f\"]),\n", @@ -343,13 +356,13 @@ "## Data structures (lists of lists)\n", "\n", "- Each node is an entry in the list\n", - "- Childre are sub-lists" + "- Children are sub-lists" ] }, { "cell_type": "code", - "execution_count": 122, - "id": "1cfece72", + "execution_count": 31, + "id": "ed1ae4b2", "metadata": {}, "outputs": [], "source": [ @@ -376,17 +389,18 @@ "## Data structures (tuples)\n", "\n", "- Each node is the first tuple\n", - "- Children are additionnal tuply entries" + "- Children are additionnal tuply entries\n", + "- Warning: tuples are immutable (cannot be changed)" ] }, { "cell_type": "code", - "execution_count": 119, - "id": "0ed87f90", + "execution_count": 32, + "id": "36be872b", "metadata": {}, "outputs": [], "source": [ - "tree = (\"a\", [\n", + "tree_tuple = (\"a\", [\n", " (\"b\", []),\n", " (\"c\", [\n", " (\"d\", [\n", @@ -396,6 +410,27 @@ "])" ] }, + { + "cell_type": "code", + "execution_count": 33, + "id": "160d1a92", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'a'" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tree_tuple[0] # cannot be changed" + ] + }, { "cell_type": "markdown", "id": "f30c5bc6", @@ -405,16 +440,27 @@ } }, "source": [ - "## Class object\n", + "## Data structure (class object)\n", + "\n", + "How to create the tree? How to retrieve all nodes? Both iterative and recursive ways.\n", "\n", - "- The object contains a value and an unrestricted list of children" + "```python\n", + "class Node:\n", + " def __init__(self, value, children = []):\n", + " self.value = value\n", + " self.children = children\n", + "```" ] }, { "cell_type": "code", - "execution_count": 176, + "execution_count": 34, "id": "7d12baee", - "metadata": {}, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, "outputs": [], "source": [ "class Node:\n", @@ -440,8 +486,8 @@ }, { "cell_type": "code", - "execution_count": 177, - "id": "a1b3ac88", + "execution_count": 35, + "id": "4e21434d", "metadata": { "slideshow": { "slide_type": "subslide" @@ -459,13 +505,13 @@ " ]),\n", "])\n", "\n", - "# or using root.children" + "# or using \"root.children\"" ] }, { "cell_type": "code", - "execution_count": 178, - "id": "48cb2472", + "execution_count": 36, + "id": "3ac188f3", "metadata": {}, "outputs": [ { @@ -474,7 +520,7 @@ "['a', 'b', 'd', 'e', 'c', 'f']" ] }, - "execution_count": 178, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -485,8 +531,8 @@ }, { "cell_type": "code", - "execution_count": 175, - "id": "95477007", + "execution_count": 37, + "id": "bf8772d3", "metadata": {}, "outputs": [ { @@ -495,7 +541,7 @@ "['a', 'c', 'f', 'b', 'e', 'd']" ] }, - "execution_count": 175, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -506,7 +552,7 @@ }, { "cell_type": "markdown", - "id": "f5e99024", + "id": "f8aefc1e", "metadata": { "slideshow": { "slide_type": "subslide" @@ -515,49 +561,49 @@ "source": [ "# Weighted trees\n", "\n", - "> Trees with a quantity associated to the edges\n", + "> Trees with a quantity associated to the links or the nodes\n", "\n", - "- Since we have a tree a way to store weights is using nodes values\n", - "- Root note weight is $0$" + "- Useful to quantifie both nodes and links\n", + "- Storing those values require additionnal data structures" ] }, { "cell_type": "markdown", - "id": "c4544dce", + "id": "b66a3424", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ - "## Data structures (dicts for edges)\n", + "## Data structures for weighted trees (dicts for edges)\n", "\n", - "- To encode values in edges we need to add an extra value" + "- We need to add an extra value to encode values in edges " ] }, { "cell_type": "code", - "execution_count": 134, - "id": "cc40dc55", + "execution_count": 38, + "id": "f26343c9", "metadata": {}, "outputs": [], "source": [ - "tree = {'a': [{'b': 0}, {'c': 0}],\n", + "tree_w_dict = {'a': [{'b': 0}, {'c': 0}],\n", " 'b': [{'d': 0}, {'e': 0}],\n", " 'c': [{'f': 0}],\n", " 'd': [],\n", " 'e': []\n", - " }\n" + " }" ] }, { "cell_type": "code", - "execution_count": 135, - "id": "69301406", + "execution_count": 39, + "id": "a50608c9", "metadata": {}, "outputs": [], "source": [ - "tree = {\n", + "tree_w_tuple = {\n", " 'a': [('b', 0), ('c', 0)],\n", " 'b': [('d', 0), ('e', 0)],\n", " 'c': [('f', 0)],\n", @@ -568,7 +614,7 @@ }, { "cell_type": "markdown", - "id": "44b1c278", + "id": "e5f7dfb7", "metadata": { "slideshow": { "slide_type": "subslide" @@ -580,8 +626,8 @@ }, { "cell_type": "code", - "execution_count": 136, - "id": "cad00333", + "execution_count": 40, + "id": "a81e7954", "metadata": {}, "outputs": [], "source": [ @@ -600,7 +646,7 @@ }, { "cell_type": "markdown", - "id": "d6e804ec", + "id": "50918649", "metadata": { "slideshow": { "slide_type": "subslide" @@ -609,13 +655,13 @@ "source": [ "## Exercise: Calculate the total weight of a tree\n", "\n", - "Go through all the nodes.." + "_Tip: go through all the nodes and get the edges, then sum their weights._" ] }, { "cell_type": "code", - "execution_count": 137, - "id": "a3d02030", + "execution_count": 41, + "id": "35793e38", "metadata": { "slideshow": { "slide_type": "fragment" @@ -639,8 +685,8 @@ }, { "cell_type": "code", - "execution_count": 138, - "id": "ea86d253", + "execution_count": 42, + "id": "8d143474", "metadata": {}, "outputs": [ { @@ -649,23 +695,23 @@ "[(1, 2, 5), (1, 3, 7)]" ] }, - "execution_count": 138, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "tree = Node_weight(1)\n", + "tree_w_oo = Node_weight(1)\n", "child1 = Node_weight(2, weight=5)\n", "child2 = Node_weight(3, weight=7)\n", - "tree.children = [child1, child2]\n", - "get_tree_edges(tree)" + "tree_w_oo.children = [child1, child2]\n", + "get_tree_edges(tree_w_oo)" ] }, { "cell_type": "code", - "execution_count": 139, - "id": "461303a6", + "execution_count": 43, + "id": "864d9a68", "metadata": {}, "outputs": [ { @@ -674,18 +720,18 @@ "12" ] }, - "execution_count": 139, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "sum(tpl[2] for tpl in get_tree_edges(tree))" + "sum(tpl[2] for tpl in get_tree_edges(tree_w_oo))" ] }, { "cell_type": "markdown", - "id": "b5554daf", + "id": "cad8ffb6", "metadata": { "slideshow": { "slide_type": "subslide" @@ -699,8 +745,8 @@ }, { "cell_type": "code", - "execution_count": 142, - "id": "6118a8e3", + "execution_count": 44, + "id": "36b7cce1", "metadata": {}, "outputs": [], "source": [ @@ -713,8 +759,8 @@ }, { "cell_type": "code", - "execution_count": 143, - "id": "e9742977", + "execution_count": 45, + "id": "a3371c5a", "metadata": {}, "outputs": [ { @@ -723,33 +769,34 @@ "12" ] }, - "execution_count": 143, + "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "calculate_total_weight(tree)" + "calculate_total_weight(tree_w_oo)" ] }, { "cell_type": "markdown", - "id": "2949e143", + "id": "df3b5ca2", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ - "# An Edge class for edges\n", + "## An Edge class for edges\n", "\n", - "- To consider edges as objects" + "- To consider edges as objects\n", + "- Can be used as a complement of the nodes (or without the nodes)" ] }, { "cell_type": "code", - "execution_count": 145, - "id": "6958cf7d", + "execution_count": 46, + "id": "f8094971", "metadata": {}, "outputs": [], "source": [ @@ -771,40 +818,531 @@ }, { "cell_type": "markdown", - "id": "7dc8b845", + "id": "659ca77e", + "metadata": {}, + "source": [ + "## Main trees properties\n", + "\n", + "- Hierarchical structure\n", + "- No cycle\n", + "- All nodes connected\n", + "\n", + "We will mostly use one of the two traversal methods (BFS and DFS) to achieve this.\n", + "\n", + "Also we will using the dictionnary-based data structure:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d45a5bb6", + "metadata": {}, + "outputs": [], + "source": [ + "tree = {\n", + " \"A\": [\"B\", \"C\"],\n", + " \"B\": [\"D\", \"E\"],\n", + " \"C\": [\"F\", \"G\"],\n", + " \"D\": [\"H\", \"I\"],\n", + " \"E\": [\"J\"],\n", + " \"F\": [],\n", + " \"G\": [],\n", + " \"H\": [],\n", + " \"I\": [],\n", + " \"J\": []\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2737d1a8", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n", + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n", + " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n", + "<!-- Generated by graphviz version 7.1.0 (20230121.1956)\n", + " -->\n", + "<!-- Pages: 1 -->\n", + "<svg width=\"350pt\" height=\"260pt\"\n", + " viewBox=\"0.00 0.00 350.00 260.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n", + "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 256)\">\n", + "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-256 346,-256 346,4 -4,4\"/>\n", + "<!-- A -->\n", + "<g id=\"node1\" class=\"node\">\n", + "<title>A</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"207\" cy=\"-234\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"207\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\">A</text>\n", + "</g>\n", + "<!-- B -->\n", + "<g id=\"node2\" class=\"node\">\n", + "<title>B</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-162\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"171\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">B</text>\n", + "</g>\n", + "<!-- A->B -->\n", + "<g id=\"edge1\" class=\"edge\">\n", + "<title>A->B</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M198.65,-216.76C194.42,-208.55 189.19,-198.37 184.42,-189.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"187.68,-187.79 180,-180.49 181.46,-190.99 187.68,-187.79\"/>\n", + "</g>\n", + "<!-- C -->\n", + "<g id=\"node3\" class=\"node\">\n", + "<title>C</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-162\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"243\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\">C</text>\n", + "</g>\n", + "<!-- A->C -->\n", + "<g id=\"edge2\" class=\"edge\">\n", + "<title>A->C</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M215.35,-216.76C219.58,-208.55 224.81,-198.37 229.58,-189.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"232.54,-190.99 234,-180.49 226.32,-187.79 232.54,-190.99\"/>\n", + "</g>\n", + "<!-- D -->\n", + "<g id=\"node4\" class=\"node\">\n", + "<title>D</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">D</text>\n", + "</g>\n", + "<!-- B->D -->\n", + "<g id=\"edge3\" class=\"edge\">\n", + "<title>B->D</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M156.08,-146.5C146.23,-136.92 133.14,-124.19 121.97,-113.34\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"124.59,-111 114.98,-106.54 119.71,-116.02 124.59,-111\"/>\n", + "</g>\n", + "<!-- E -->\n", + "<g id=\"node5\" class=\"node\">\n", + "<title>E</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"171\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">E</text>\n", + "</g>\n", + "<!-- B->E -->\n", + "<g id=\"edge4\" class=\"edge\">\n", + "<title>B->E</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M171,-143.7C171,-136.41 171,-127.73 171,-119.54\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"174.5,-119.62 171,-109.62 167.5,-119.62 174.5,-119.62\"/>\n", + "</g>\n", + "<!-- F -->\n", + "<g id=\"node6\" class=\"node\">\n", + "<title>F</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"243\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"243\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">F</text>\n", + "</g>\n", + "<!-- C->F -->\n", + "<g id=\"edge5\" class=\"edge\">\n", + "<title>C->F</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M243,-143.7C243,-136.41 243,-127.73 243,-119.54\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"246.5,-119.62 243,-109.62 239.5,-119.62 246.5,-119.62\"/>\n", + "</g>\n", + "<!-- G -->\n", + "<g id=\"node7\" class=\"node\">\n", + "<title>G</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"315\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"315\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">G</text>\n", + "</g>\n", + "<!-- C->G -->\n", + "<g id=\"edge6\" class=\"edge\">\n", + "<title>C->G</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M257.92,-146.5C267.77,-136.92 280.86,-124.19 292.03,-113.34\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"294.29,-116.02 299.02,-106.54 289.41,-111 294.29,-116.02\"/>\n", + "</g>\n", + "<!-- H -->\n", + "<g id=\"node8\" class=\"node\">\n", + "<title>H</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">H</text>\n", + "</g>\n", + "<!-- D->H -->\n", + "<g id=\"edge7\" class=\"edge\">\n", + "<title>D->H</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M84.08,-74.5C74.23,-64.92 61.14,-52.19 49.97,-41.34\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"52.59,-39 42.98,-34.54 47.71,-44.02 52.59,-39\"/>\n", + "</g>\n", + "<!-- I -->\n", + "<g id=\"node9\" class=\"node\">\n", + "<title>I</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">I</text>\n", + "</g>\n", + "<!-- D->I -->\n", + "<g id=\"edge8\" class=\"edge\">\n", + "<title>D->I</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M99,-71.7C99,-64.41 99,-55.73 99,-47.54\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"102.5,-47.62 99,-37.62 95.5,-47.62 102.5,-47.62\"/>\n", + "</g>\n", + "<!-- J -->\n", + "<g id=\"node10\" class=\"node\">\n", + "<title>J</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"171\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"171\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">J</text>\n", + "</g>\n", + "<!-- E->J -->\n", + "<g id=\"edge9\" class=\"edge\">\n", + "<title>E->J</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M171,-71.7C171,-64.41 171,-55.73 171,-47.54\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"174.5,-47.62 171,-37.62 167.5,-47.62 174.5,-47.62\"/>\n", + "</g>\n", + "</g>\n", + "</svg>\n" + ], + "text/plain": [ + "<graphviz.graphs.Digraph at 0x10a59bcd0>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "draw_tree_dict(tree)" + ] + }, + { + "cell_type": "markdown", + "id": "121006f2", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ - "# Visualize a tree" + "## Generalized BFS (Breadth-First Search)" ] }, { "cell_type": "code", - "execution_count": 146, - "id": "9054d359", + "execution_count": 9, + "id": "ba62dbbc", "metadata": {}, "outputs": [], "source": [ - "from graphviz import Digraph\n", - "from IPython.display import display\n", + "def bfs(tree, start_node):\n", + " queue = [start_node]\n", + " result = []\n", "\n", - "def draw_tree(T):\n", - " dot = Digraph(format='png')\n", + " while queue:\n", + " node = queue.pop(0)\n", + " result.append(node)\n", + " children = tree.get(node, [])\n", "\n", - " def add_nodes_and_edges(tree, parent_name=None):\n", - " for parent, children in tree.items():\n", - " dot.node(parent, parent)\n", - " if parent_name:\n", - " dot.edge(parent_name, parent)\n", - " add_nodes_and_edges({child: [] for child in children}, parent)\n", + " for child in children:\n", + " if child is not None:\n", + " queue.append(child)\n", "\n", - " add_nodes_and_edges(T)\n", - " \n", - " display(dot)" + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "5ec0e5f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']\n" + ] + } + ], + "source": [ + "print(bfs(tree, \"A\"))" ] + }, + { + "cell_type": "markdown", + "id": "18c6b5e6", + "metadata": {}, + "source": [ + "## Generalized DFS (Depth-First Search)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0968d63f", + "metadata": {}, + "outputs": [], + "source": [ + "def dfs(tree, start_node):\n", + " stack = [start_node]\n", + " result = []\n", + "\n", + " while stack:\n", + " node = stack.pop()\n", + " result.append(node)\n", + " children = tree.get(node, [])\n", + "\n", + " for child in children:\n", + " if child is not None:\n", + " stack.append(child)\n", + "\n", + " return result\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c3f173c", + "metadata": {}, + "outputs": [], + "source": [ + "print(dfs(tree, \"A\"))" + ] + }, + { + "cell_type": "markdown", + "id": "6c9a2f9f", + "metadata": {}, + "source": [ + "## Tree property: are all nodes connected?\n", + "\n", + "Without having a first node and re-using the dfs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "405e887d", + "metadata": {}, + "outputs": [], + "source": [ + "def is_tree_connected(tree, start_node):\n", + " if not tree:\n", + " return True # An empty tree is considered connected.\n", + "\n", + " visited = set()\n", + " stack = []\n", + "\n", + " stack.append(start_node)\n", + "\n", + " while stack:\n", + " node = stack.pop()\n", + " if node not in visited:\n", + " visited.add(node)\n", + " stack.extend(tree.get(node, []))\n", + "\n", + " return len(visited) == len(tree)\n", + "\n", + "\n", + "is_tree_connected(tree, \"A\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5852f357", + "metadata": {}, + "outputs": [], + "source": [ + "dfs_check_connected(tree, \"A\")" + ] + }, + { + "cell_type": "markdown", + "id": "f76000b9", + "metadata": {}, + "source": [ + "## Tree property: does the tree have a cycle?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9a1907c6", + "metadata": {}, + "outputs": [], + "source": [ + "def has_cycle_dfs(root):\n", + " def dfs(node, parent, visited):\n", + " if node in visited:\n", + " if parent is not None and parent != visited[node]:\n", + " return True\n", + " return False\n", + "\n", + " visited[node] = parent\n", + "\n", + " for child in node.children:\n", + " if dfs(child, node, visited):\n", + " return True\n", + "\n", + " return False\n", + "\n", + " visited = {}\n", + " return dfs(root, None, visited)" + ] + }, + { + "cell_type": "markdown", + "id": "ffb17bd1", + "metadata": {}, + "source": [ + "## What if we add an extra node \"K\"?\n", + "\n", + "> tree[\"F\"] = [\"A\"]" + ] + }, + { + "cell_type": "markdown", + "id": "408393f0", + "metadata": {}, + "source": [ + "## Tree property: Check if the tree is an n-ary tree" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91ef03ea", + "metadata": {}, + "outputs": [], + "source": [ + "def is_binary_tree(tree, node, n = 2, visited=None):\n", + " if visited is None:\n", + " visited = set()\n", + "\n", + " if node in visited:\n", + " return True\n", + "\n", + " visited.add(node)\n", + " children = tree.get(node, [])\n", + "\n", + " if len(children) > n:\n", + " return False\n", + "\n", + " for child in children:\n", + " if not is_binary_tree(tree, child, n, visited):\n", + " return False\n", + "\n", + " return True\n", + "\n", + "is_binary_tree(tree, \"A\", 2)" + ] + }, + { + "cell_type": "markdown", + "id": "e4279f90", + "metadata": {}, + "source": [ + "## Get all the edges of a tree" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "858814ab", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_edges(graph):\n", + " edges = []\n", + " for node, neighbors in graph.items():\n", + " for neighbor in neighbors:\n", + " edges.append((node, neighbor))\n", + " return edges" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "7ab4d047", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('A', 'B'),\n", + " ('A', 'C'),\n", + " ('B', 'D'),\n", + " ('B', 'E'),\n", + " ('C', 'F'),\n", + " ('C', 'G'),\n", + " ('D', 'H'),\n", + " ('D', 'I'),\n", + " ('E', 'J')]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "generate_edges(tree)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "3db5ca0d", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_edges_dfs(graph, start_node):\n", + " edges = []\n", + " stack = [start_node]\n", + " visited = []\n", + "\n", + " while stack:\n", + " node = stack.pop()\n", + " visited.append(node)\n", + " for neighbor in graph[node]:\n", + " if neighbor not in visited:\n", + " edges.append((node, neighbor))\n", + " stack.append(neighbor)\n", + "\n", + " return edges\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f3425b5c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{('A', 'B'),\n", + " ('A', 'C'),\n", + " ('B', 'D'),\n", + " ('B', 'E'),\n", + " ('C', 'F'),\n", + " ('C', 'G'),\n", + " ('D', 'H'),\n", + " ('D', 'I'),\n", + " ('E', 'J')}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "generate_edges_dfs(tree, \"A\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7963ad24", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff0ba08e", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/notebooks/11-graphs.ipynb b/notebooks/11-graphs.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..fa910d207edce50f91032859227075510fc0747b --- /dev/null +++ b/notebooks/11-graphs.ipynb @@ -0,0 +1,2137 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "09fc003e", + "metadata": {}, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "## Lecture 11: Graphs\n", + "### Ecole Centrale de Lyon, Bachelor of Science in Data Science for Responsible Business\n", + "#### Romain Vuillemot\n", + "<center><img src=\"figures/Logo_ECL.png\" style=\"width:300px\"></center>" + ] + }, + { + "cell_type": "markdown", + "id": "74743087", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "---" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ead453f3", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "from graphviz import Digraph\n", + "from IPython.display import display\n", + "from utils import visualize_graph_nx, visualize_graph_w" + ] + }, + { + "cell_type": "markdown", + "id": "f3ebe7d2", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Outline\n", + "\n", + "- Definitions\n", + "- Data structures\n", + "- Properties\n", + "- Weighted graphs and spanning trees\n", + "- Shortest paths" + ] + }, + { + "cell_type": "markdown", + "id": "a5c1dca1", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "# Graphs\n", + "\n", + "\n", + "> A **graph** is an abstract data structure consisting of a set of vertices connected by edges.\n", + "\n", + "- Trees are a specific case of a graph (acyclic, connected graphs)\n", + "\n", + "Examples:\n", + "\n", + "- Messaging: the traveling salesman problem, postal routes\n", + "- Communication networks\n", + "- Traffic management: flow problems, minimum congestion paths, ...\n", + "- Air navigation (aircraft in sky corridors!)\n", + "- Closed transportation system (closed circuit): goods delivery, TSP (Traveling Salesman Problem).\n", + "- Printed circuit board wiring" + ] + }, + { + "cell_type": "markdown", + "id": "30f450b7", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Definition\n", + "\n", + "\n", + "Graph $G = (V, E)$ with:\n", + "\n", + "- $V$: set of nodes (vertices).\n", + "- $E \\in (V \\times V)$: set of edges (links) or arcs (if oriented).\n", + "\n", + "Properties:\n", + "\n", + "- **Connected graph**: with a path between any pair of nodes.\n", + "- **Directed graphs**: where edges have a specific direction.\n", + "- **Weighted graphs**: numerical values associated with nodes or edges.\n", + "- **Strongly connected graphs**: directed graphs where there is a path from any node to any other node.\n", + "- **Bipartite**: vertices can be divided into two sets with no edges within a set.\n", + "- **Dense graph**: with a high edge-to-vertex ratio, often with $|E| = O(|V|^2)$.\n", + "- **Path**: a sequence of connected nodes with vertice.\n", + "- **Cycle**: a path that starts and ends at the same vertex.\n", + "- **Degree**: number of edges connected to a node." + ] + }, + { + "cell_type": "markdown", + "id": "5cc0eab1", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Data structures: dict\n", + "\n", + "- Using a dictionnary with adjacency list (similar to trees without cycles and non-connected nodes)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "63e36dcf", + "metadata": {}, + "outputs": [], + "source": [ + "g = { \"a\" : [\"d\"],\n", + " \"b\" : [\"c\"],\n", + " \"c\" : [\"b\", \"c\", \"d\", \"e\"],\n", + " \"d\" : [\"a\", \"c\"],\n", + " \"e\" : [\"c\"], \n", + " \"f\" : [] \n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c0413aac", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['a', 'b', 'c', 'd', 'e', 'f'])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g.keys() # nodes" + ] + }, + { + "cell_type": "markdown", + "id": "b5995983", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Data structures: dict" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2ecde2a7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('a', 'd'),\n", + " ('b', 'c'),\n", + " ('c', 'b'),\n", + " ('c', 'c'),\n", + " ('c', 'd'),\n", + " ('c', 'e'),\n", + " ('d', 'a'),\n", + " ('d', 'c'),\n", + " ('e', 'c')]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def generate_edges(graph):\n", + " edges = []\n", + " for node, neighbors in graph.items():\n", + " for neighbor in neighbors:\n", + " edges.append((node, neighbor))\n", + " return edges\n", + "\n", + "generate_edges(g) # edges" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f7d6c3d1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('a', 'd'),\n", + " ('b', 'c'),\n", + " ('c', 'b'),\n", + " ('c', 'c'),\n", + " ('c', 'd'),\n", + " ('c', 'e'),\n", + " ('d', 'a'),\n", + " ('d', 'c'),\n", + " ('e', 'c')]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "[(vertex, neighbor) for vertex, neighbors \n", + " in g.items() for neighbor in neighbors]" + ] + }, + { + "cell_type": "markdown", + "id": "b39566f5", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graphs: node-link representation" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2e937dc7", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApQAAAHzCAYAAACe1o1DAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAnnUlEQVR4nO3de5jcVZ3g/09VpZJOB7orSCdEAXNBRhQMkp8yDjIwOjqM3FEHEFGE1tVRf8s6q7uyrs7w4KzrozM6rqs7D2hQLgoiSGAAwYfggApDogjDokK6gUDohAnVMd3pTnVV7R+5TEKAdPXpunW/Xn/RT7q+53DrvHO+5/s9mWq1Wg0AAJigbLMnAABAexOUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACSZ0ewJANRLpVqNwdGxKI6UojhSipFyOcqVauSymejI5aLQkY9CRz66Z82IbCbT7OkCtK1MtVqtNnsSAJNpuDQWa4rD0VccjlJl24+4TETs+sNu16/z2UwsKnTG4kJndOb9ORugVoISmDJK5Uo8uGFT9A9u2SMg92bH9y/snh1H9HRFPmdHEMB4CUpgShgYGo371xVjtFxJvlZHLhvLFhRi/pxZkzAzgKlPUAJt77HnhuKB9Zsm/bpL53XFkrlzJv26AFONezpAW6tXTEZEPLB+Uzz23FBdrg0wlQhKoG0NDI3WLSZ3eGD9phgYGq3rGADtTlACbalUrsT964oNGWvVumKUJmFvJsBUJSiBtvTghk2xtUGRN7L96XEAXpigBNrOUGks+ge31PRaoFT9g1tiuDTWwBEB2oegBNpOX3E4Gn2uTWb7uADsSVACbaVSrUZfcbihq5MR2156vqY4HBVvWgPYg6AE2srg6NjO4xQbrVTZdjY4ALtr60NrK9VtP9yLI6UojpRipFyOcqUauWwmOnK5KHTko9CRj+5ZMyKbafQNMqAeiiOlmj9TqVTin674Vtxx7ZXxzOP9MbOjI173pmPj3E9+JuYfeHDN48/tyNc8B4CprC1PyhkujcWa4nD0FYd3rlQ8/9zeXb/OZzOxqNAZiwud0Zlv64aGae+XzwxG/2Btt7z/8W8+HbddfXlERBz0qj+I4ob18fvic1HomRd/d8Md0f2y/cd1nUxELOzujNcf0F37xAGmsLa65V0qV2L1M8W4dc2G+N3God1uez3/N5ddvy5VqvG7jUNx65oNsfoZ75ODdjZSLtcUkwNrn4gff+87ERHx8S98Nb6y4s74xk/ui5cdsCCKG9bHP13xrXFfq7p9fAB21zbLdQNDo3H/umKMbo/BWpdVd3x//+CWeGbzaCxbUIj5c2ZN6hyB+ivXuH/ysYceiB03Yr72X/9jfO2//sfdfv23D6yq6/gA00FbBOVkn9U7Uq7EPWs3xtJ5XbFk7pxJuy5Qf7lsbfuhd93Vs+iw18aMmbv/QbLn5QfWdXyA6aDlg3KyY3JXO64rKqF9dORye+yZfilLDl8amUwmqtVqHH/6mXHS+3ojYltoPrL6vpi9z77jHjuzfXwAdtfSD+UMDI3GPWs31n2cYw7cz+1vaBN9xeH45cBgTZ/55mc/Fbdfc0VERMw78ODo6JwTzz69NoY3/z4++rd/H28548xxX+v187tjUaGzpvEBprqWXaEslStx/7piQ8Zata4Yb1vUE/lcWz2jBNNSYQKv7PnQX38hDlzyqvjJdd+Ldf1rIj9zZvS84qB43ZuOjcPf+Ed1Hx9gqmvZFcrVzxTj8Qae1buwe3YcdUChQaMBE1WpVuPmRwea8nLzfDYTJx4y33ttAZ6nJZfkhkpj0d/AmIzY9vT3cMkJGNDqsplt75VtxlneiwudYhLgBbRkUPYVh5vym0VfcbjBowITsbjQ2ZSzvO2dBHhhLReUlWo1+oq1nYIxGaoRsaY4HJXW3AEA7KIzPyMWds9u6JgLu2c7aQvgRbRcUA6OjjVlb1TEthN1Bkfd9oZ2cERPV1S3jka5ASfXdOSycURPV93HAWhXLffH7eJIqebP/Oiyb8RPV1wXz657OrYMbY45Xd1x2LI3xns/cVG8fNGSmsef6ylOaGmVSiUu/uvPxYo7/zk+e9nVdR9v2YKCt0AAvISW+wlZHCnVvH/yX+/7eTzzRH8U9u+JVyw6JDYPFuPe22+Jvzn/zNg6OjLu62RiYkELNM7Q0FD8xV/8RXz+85+P95zyjlg6b/wvJp+IpfO6vKcWYC9aboVypFyuef/ke//zf4uXL1wcM/LbVhYf+NlP4+Lzz4pn1z0dj6z+l3jdm44d13Wq28cHWtNTTz0Vp5xySvzmN7+JH/7wh3Haaadt/5VMXU7UcjwrwPi0XFCWJ7B/8tl1T8X/+dyn4vHf/N8YGR7a7ezejesH6j4+UH/3339/nHLKKTFjxoy4++6748gjj9z5a0vmzol9Zs6IVeuKMVKuJI/VkcvGsgUFK5MA49Ryt7xz2dpueD/z5OPxPz96fjyy+l8iImLxa18Xiw577c5fr9S44ljr+ED9XXPNNXHsscfGwQcfHPfdd99uMbnD/Dmz4m2LenY+/V3r/8k7vn9h9+x426IeMQlQg5YLyo5crqbfCPoefijGSlsjIuK/X3pVfPEHt8RpvR+d0NiZ7eMDraFarcbFF18cZ555ZpxxxhmxcuXKOOCAA170+/O5bBx1QCFOWNwTh+43J/K7/AHx+T9Xdv06n83EofvNiRMW98RRB3gAB6BWLXfLu9CRj+rg+L//oFcdGtlcLirlclzywXNi/wWviOKz6yc0dqVajZ/c/KNYt2D/ePOb3xxdXV4TAs2yZcuWOP/88+N73/teXHLJJXHRRRdFZpyn1HTmZ8Rre7risP33jcHRsSiOlKI4UoqRcjnKlWrkspnoyOWi0JGPQkc+umfNcAIOQIKWDMpaHLj4VfHRz/9dfP9/fTmKG9bHvnP3i/M+/ddx8fln1Tx2JpOJa7+7PO67687IZrOxbNmyOP744+P4448XmNBA69ati1NPPTUeeuihuPbaa+Nd73rXhK6TzWRibkfeq8AA6ixTrbbW0TCVajVufnSgKS83z2cz8Y4l86JvzZpYuXJlrFy5Mu688854+umnI5vNxlFHHbVbYHZ3dzd8jjDV/fKXv4xTTjklKpVK3HjjjbFs2bJmTwmAvWi5oIyIeGjDpvjdxqGGHr+YiYhD95sTr33eaRjVajUee+yxnYG5cuXKeOqpp6ZNYFaqVbcMaZjrr78+3vve98ZrXvOa+NGPfhQvf/nLmz0lAMahJYNyuDQWt67Z0PBxT1jcs9ezeqdLYA6XxmJNcTj6isM7V4szEbtF/q5f57OZWFTojMWFTucdU7NqtRpf+MIX4qKLLop3v/vdsXz58ujs7Gz2tAAYp5YMyoiI1c8Uo39wS8PGW9g9O446oFDz56rVaqx53i3yHYH5+te/fmdgHnvssW0RmKVyJR7csCn6B7fsEZB7s+P7F3bPjiN6ujwpy7iMjIzEBz/4wbjiiivic5/7XHz2s5+NbNZ/OwDtpGWDslSuxO19GyblJcV705HLxtsW9UxKAI03MN/85jdHoVBIn/wkGhgajfvXFWPUi6FpkIGBgTj99NNj9erVsXz58jjrrNofpgOg+Vo2KCO2Bc49azfWfZxjDtyvbuGza2Deddddceedd8batWt3BuZxxx23cwWzmYH52HNDjq6joX7961/HySefHFu3bo0bbrghjj766GZPCYAJaumgjKhf6OzQ6OCpVqvR19e32wrm2rVrI5PJ7HGLvFGBOdX+GdP6VqxYEe95z3vikEMOiRtvvDEOOuigZk8JgAQtH5QRU3v17PmBuXLlynjyyScbFphTYRWY9lGtVuPLX/5yfOpTn4pTTz01rrjiipgzxx82ANpdWwRlxLbwWbWuOCl7Klt5f1+1Wo3+/v7dVjB3BOaRRx65W2DOnTs3aaxSuRI/7tswKXsm92Yy96nSnrZu3Rof/vCH49vf/nZ8+tOfjksuucTDNwBTRNsEZcT0fAK5noG5+pliPD64pWHv+5zok/S0v2effTbOOOOMuPfee+PSSy+Nc889t9lTAmAStVVQ7jBcGou+4nCsqeEdiYsLnbFoCrwj8fmBuXLlynjiiSdqDsyh0ljc1qLv+mRqefjhh+Okk06KzZs3x/XXXx/HHHNMs6cEwCRry6DcwSku2zx/BXNHYC5dunS3wNxvv/12fib1NKLPnvvO+Nd/+Xm89g1viou/e924PvNipxExdd1yyy1x1llnxcEHHxwrVqyIhQsXNntKANRBWwclL2xvgXnc8X8S2cPeEGMJ/+YnEpQR21aLTzxk/pQOfLatpP/DP/xDfOITn4h3vOMdcdVVV8W+++7b7GkBUCftsZGQmixcuDDOO++8WL58eTz++OPR19cX3/rWt2Lp0qVxww03xF/9t88kxWSKUmXbqjJTV6lUig9/+MNx4YUXxic+8Ym44YYbxCTAFGcz2zSwIzDPO++8iIi4/7En4vFSNTLjXCXcPFiMb37uv8SqlbdH19z94oz/8P8nzac4Uoq5Hfmka9CaNm7cGO9617vi7rvvjssuuyzOP//8Zk8JgAYQlNNQbk53ZAeHx71/8n9/5j/Hvbf/U0REzOyYHd/54sUTHjsT24KSqec3v/lNnHTSSfHcc8/F7bffHscdd1yzpwRAg7jlPQ2NlMvjjslnnujfGZOnffCj8bVb/jm++INbo7R164TGrm4fn6nljjvuiKOPPjry+Xzce++9YhJgmhGU01C5Mv4NlE8++pudf/2mt58YERGvWHxIvPLQwxoyPq3vG9/4Rpxwwgnxh3/4h/Hzn/88lixZ0uwpAdBggnIaymXH/4T17i8B+PfPVRNeh17L+LSusbGx+PjHPx5/+Zd/GR/72Mfipptuiu7u7mZPC4AmEJTTUEcuF+NNuoMO+YOdf73j1vfTfY/FE799ZEJjZ7aPT3srFotx4oknxje/+c345je/GV/5yldixgxbsgGmK78DTEOFjnxUB8f3vQteuSje+KcnxH133Bo//Mevxb133BL/9szTkc3mohy1v/6nUq1G/yMPxStnHrbbi9ZpH48++micfPLJMTAwELfddlu85S1vafaUAGgyK5TTUKHGV/b85SVfjj98+4kxc1ZHDP/+93Hmxz8Zhy49akJjZzKZ+KuPfiT233//OPLII+PCCy+MG264ITZu3Dih69FYK1eujKOPPjoqlUr84he/EJMARISTcqalSrUaNz86sPMc9EbKZzNxxMyt8c8/vWvnaT5r1qyJTCYTr3vd63YeFfnHf/zHVjBbzKWXXhof+chH4rjjjotrr732Jc+KB2B6EZTTVOpZ3hPxYmd5P/7443HXXf8emH19fQKzhZTL5fjkJz8Zf//3fx8f+chH4qtf/Wrk815MD8C/E5TT1HBpLG5ds6Hh456wuCc68y+9dVdgto5NmzbF2WefHbfeemt89atfjY997GPNnhIALUhQTmOrnylG/+CWho23sHt2HHVAoebPCczm6Ovri5NPPjnWrl0b11xzTbz97W9v9pQAaFGCchorlStxe9+GGClX6j5WRy4bb1vUE/lc+nNgArP+7r777jj99NOju7s7VqxYEYcdNvEX2QMw9QnKaW5gaDTuWVv/J6yPOXC/mD9nVl2uPV0Ds1KtxuDoWBRHSvG7tU/H2mcGYmRrKV5+wPw4eMGCmDt7ZhQ68tE9a0ZkM+N/mfzy5cvjQx/6UPzRH/1RXHfddfGyl72sjn8XAEwFgpJ47LmheGD9prpdf+m8rlgyd07drv98Uz0wh0tjsaY4HH3F4ShVqlGtVqM8Nha5GTMik8lEtVqNSrkcuVwuIpOJfDYTiwqdsbjQ+ZL7V8vlclx00UXxxS9+MXp7e+PrX/96zJw5s4F/ZwC0K0FJRNQvKhsdky9kqgRmqVyJBzdsiv7BLZGJqOkJ/R3fv7B7dhzR07XH1oPNmzfHOeecEzfddFN86UtfigsvvDAyNaxqAjC9CUp2GhgajVXripOyp7Ijl41lCwp1u82d4sUCc+nSpTsD89hjj22pwBwYGo371xVjtA7/bp544ok4+eSTo6+vL66++uo48cQTk8cAYHoRlOymnqtgraq/v3+3wOzv72+pwKzn6vH6R34dp512WnR2dsaKFSvi8MMPn/RxAJj6BCUvaLg0Fn3F4VizfZ9eROwRmLt+nc9mYnGhMxbtZZ9eO2ilwKz3/tZv/+1no7jmkfjhD38YPT09dRsHgKlNUPKSdn2SuDhSipFyOcqVauSymejI5aLQkZ/Qk8TtpFmB2agn8N84f984sLBP3ccBYOoSlFCjRgRmqVyJH/dtmJQ9k3szme8IBWB6EpSQqB6BufqZYjw+uKVhZ61P9BQjAIgQlDDpUgNzqDQWt7XoOesA8EIEJdRZrYH50IZN8buNQw1bnYzY9oDVofvNidf2dDVwVACmCkEJDfZSgfknb3lLvPmCCyNyjV8pzGczceIh86fsw1UA1I+ghCbbNTB/9+TT8YmvL2/aXP7klfvH3I5808YHoD0JSmghfcXh+OVAMbbdhB6/arUat119edxx7ZXx1JrHIpvLxYFLXhUfvviLseiw8b+s/PXzu2NRobO2SQMw7dmBDy2kOFKKTGRq3j952SWfiVuu/HZEROxbmBuFnnnR/8jDsf6pJ8cdlJnt4wNArQQltJCRcrnmmFy/9sm49arlERFx9NveEf/py/878jNnxuDGf4vS6Mi4r1PdPj4A1EpQQgspV2rfgfLoQ7+KHTtXTvnAhyI/c2ZERHTv97KGjA8AjsaAFpLLNvcJ62aPD0B7EpTQQjpyuRofx4k45PAjI7P9VT83XX5plLZujYiI3z+3Mf7tmafHfZ3M9vEBoFaCElpIoSNf8x7KeQceFCe857yIiPj5bTfFh447Kv7TyW+JDx63LB596IFxX6e6fXwAqJU9lNBCJhp0F3zmkjhwyat2vjZo/VNPxCv/4LCY94qDGjI+ANOb91BCC6lUq3HzowNRasLDMU7KAWCi3PKGFpLNZGJRobPmfZSpMhGxuNApJgGYEEEJLWZxobPmfZSpqhFOyAFgwgQltJjO/IxY2D27oWMu7J4dnXlbqgGYGEEJLeiInq7oyDXmf8+OXDaO6OlqyFgATE2CElpQPpeNZQsKDRlr2YJC5BsUrwBMTX4XgRY1f86sWDqvviuHS+d1xfw5s+o6BgBTn6CEFrZk7py6ReXSeV2xZO6culwbgOnFeyihDQwMjcaqdcUYKVeSr9Wx/Xa6lUkAJoughDZRKlfiwQ2bon9wS2Qianq1ULVSiUw2Gwu7Z8cRPV32TAIwqfyuAm0in8vGUQcU4oTFPXHofnMin/33l5A//3Xku35dGtkSP75qefzpwfvFUQd4AAeAyWeFEtpUpVqNwdGxKI6UojhSipFyOcqVauSymejI5aLQkY9CRz76/u9Dseyoo2LFihVx0kknNXvaAExBghKmgWXLlsVBBx0UN9xwQ7OnAsAU5N4XTAO9vb1x0003xbp165o9FQCmIEEJ08DZZ58dM2fOjMsvv7zZUwFgCnLLG6aJ97///XHPPffE7373u8hknv8YDwBMnBVKmCZ6e3vjsccei7vuuqvZUwFgirFCCdNEtVqNV7/61fGGN7whrrjiimZPB4ApxAolTBOZTCZ6e3vjBz/4QTz33HPNng4AU4ighGnkfe97X5TL5bjyyiubPRUAphC3vGGaeec73xmPPvpo/OpXv/JwDgCTwgolTDO9vb3x61//OlatWtXsqQAwRQhKmGbe/va3x4EHHhiXXnpps6cCwBQhKGGayeVycf7558dVV10VQ0NDzZ4OAFOAoIRp6AMf+EBs3rw5rrnmmmZPBYApwEM5ME392Z/9WWzevDnuueeeZk8FgDZnhRKmqd7e3vjZz34WDz/8cLOnAkCbE5QwTZ1yyimx//77x2WXXdbsqQDQ5gQlTFOzZs2K973vffGd73wnRkdHmz0dANqYoIRp7IILLohnn302brzxxmZPBYA25qEcmOaOOeaY2GeffeK2225r9lQAaFNWKGGa6+3tjdtvvz36+/ubPRUA2pSghGnu3e9+d+yzzz7x7W9/u9lTAaBNCUqY5vbZZ584++yz41vf+laUy+VmTweANiQogejt7Y21a9fGj3/842ZPBYA25KEcIKrVahx55JFxyCGHxHXXXdfs6QDQZqxQApHJZKK3tzduvPHGGBgYaPZ0AGgzghKIiIhzzjkncrlcfOc732n2VABoM255Azudc845cf/998cjjzwSmUym2dMBoE1YoQR26u3tjd/+9rdx9913N3sqALQRK5TATpVKJQ499NA45phj4vLLL2/2dABoE1YogZ2y2WxccMEFce2110axWGz2dABoE4IS2M373//+2Lp1a1x99dXNngoAbcItb2APp556aqxduzZWrVrV7KkA0AasUAJ76O3tjdWrV8fq1aubPRUA2oCgBPbw53/+57FgwYK47LLLmj0VANqAoAT2MGPGjPjABz4QV155ZQwPDzd7OgC0OEEJvKDzzz8/BgcHne0NwF55KAd4UW9961tjbGws7rrrrmZPBYAWZoUSeFG9vb3x05/+NH772982eyoAtDBBCbyo008/PebOnevhHABekqAEXlRHR0ece+65sXz58iiVSs2eDgAtSlACL+mCCy6I9evXx0033dTsqQDQojyUA+zV0UcfHfvvv3/cfPPNzZ4KAC3ICiWwV729vXHrrbfGk08+2eypANCCBCWwV2eddVbMnj07li9f3uypANCCBCWwV/vuu2+ceeaZcdlll0WlUmn2dABoMYISGJfe3t54/PHH4yc/+UmzpwJAi/FQDjAu1Wo1Dj/88Dj88MPj+9//frOnA0ALsUIJjEsmk4ne3t64/vrr49lnn232dABoIYISGLdzzz03IiK++93vNnkmALQSt7yBmpx55pnx0EMPxUMPPRSZTKbZ0wGgBVihBGrS29sbDz/8cPziF79o9lQAaBGCEqjJW9/61njlK18Zl156abOnAkCLEJRATbLZbFxwwQXxve99LzZt2tTs6QDQAgQlULPzzjsvRkZGvD4IgIjwUA4wQSeeeGI8++yzce+99zZ7KgA0maAEJuT666+PM844I371wANx8KGHRXGkFMWRUoyUy1GuVCOXzURHLheFjnwUOvLRPWtGZD0VDjAlCUpgQgaHR+KTf/uleOu73xP5jtkREZGJiF1/oOz6dT6biUWFzlhc6IzO/IwGzxaAehKUQE1K5Uo8uGFT9A9uiWqlEpns+Ldi7wjMhd2z44iersjnbOMGmAoEJTBuA0Ojcf+6YoyWK8nX6shlY9mCQsyfM2sSZgZAMwlKYFwee24oHlg/+a8JWjqvK5bMnTPp1wWgcdxvAvaqXjEZEfHA+k3x2HNDdbk2AI0hKIGXNDA0WreY3OGB9ZtiYGi0rmMAUD+CEnhRpXIl7l9XbMhYq9YVozQJezMBaDxBCbyoBzdsiq0NiryR7U+PA9B+BCXwgoZKY9teDdTAMfsHt8RwaayBIwIwGQQl8IL6isMx3nNtPvyWN8Y7X/3y+P7XvpQ0Zmb7uAC0F0EJ7KFSrUZfcbihq5MR2156vqY4HBVvMwNoK4IS2MPg6FiUKs2JulKlGoOjbnsDtBNBCeyhOFKa0OfGSqW47PP/Pd5/9Gvi3P/vD+If/+bTUdpa++uAJjo+AM0xo9kTAFpPcaS089ztWvzTFZdFfuas6Ny3K9avfSJuu/ryyM+cFR/49F+P+xqZEJQA7cYKJbCHkXJ5Qvsn5/YcEN+44974xh2/iDefeFpERNx61fIY+v34XwdU3T4+AO1DUAJ7KE9w/+Sy4/80Zu+zT0REvPnEUyMiYqy0Ndb1r2nI+AA0h6AE9pDLjveFQbvLZCb2uckaH4DmEJTAHjpyuXG/g3JX9995e2zZvDkiIn52y4qIiJiRnxkLFi4e9zUy28cHoH14KAfYQ6EjH9XB2j/33IZn4iN/enR07tsVA08+HhERf3b2+2LOvl3jvkZ1+/gAtA9BCexhokH3jvdeECNbhuOnN14Xs+fsE8eedHq8968uatj4ADRHplp1JAWwu0q1Gjc/OtCUl5vns5k48ZD5kZ2k/ZgA1J89lMAesplMLCp0TmgfZYpMRCwudIpJgDYjKIEXtLjQ2ZSzvBcVOhs8KgCpBCXwgjrzM2Jh9+yGjrmwe3Z05m3tBmg3ghJ4UUf0dEVHrjE/Jjpy2TiiZ/xPgwPQOgQl8KLyuWwsW1BoyFjLFhQi36B4BWBy+ekNvKT5c2bF0nn1XTlcOq8r5s+ZVdcxAKgfQQns1ZK5c+oWlUvndcWSuXPqcm0AGsN7KIFxGxgajVXrijFSriRfq2P77XQrkwDtT1ACNSmVK/Hghk3RP7glMhE1vVpox/cv7J4dR/R02TMJMEUISmBChktj0VccjjXF4Z0n6jw/MHf9Op/NxOJCZywqdHo1EMAUIyiBJJVqNQZHx6I4UoriSClGyuUoV6qRy2aiI5eLQkc+Ch356J41wwk4AFOUoAQAIIkNTAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACQRlAAAJBGUAAAkEZQAACSZ0ewJAACwu0q1GoOjY1EcKUVxpBQj5XKUK9XIZTPRkctFoSMfhY58dM+aEdlMptnTjUy1Wq02exIAAEQMl8ZiTXE4+orDUapsS7RMROwaa7t+nc9mYlGhMxYXOqMz37x1QkEJANBkpXIlHtywKfoHt+wRkHuz4/sXds+OI3q6Ip9r/I5GQQkA0EQDQ6Nx/7pijJYrydfqyGVj2YJCzJ8zaxJmNn6CEgCgSR57bigeWL9p0q+7dF5XLJk7Z9Kv+2I85Q0A0AT1ismIiAfWb4rHnhuqy7VfiKAEAGiwgaHRusXkDg+s3xQDQ6N1HWMHQQkA0EClciXuX1dsyFir1hWjNAl7M/dGUAIANNCDGzbF1gZEXkTEyPanx+tNUAIANMhQaSz6B7fU9FqgVP2DW2K4NFbXMQQlAECD9BWHo9ZzbarValz+xYuj99jXx7sOe0W889Uvj/Vrnxz35zPbx60nRy8CADRApVqNvuJwzauT991xa9z4rW9GRMSBS14Vs/fZN/IzZ47789WIWFMcjsP237duxzQKSgCABhgcHdt5nGItnnz0NxER0TV3v/jKTSsjM4EoLFW2nQ0+tyNf82fHQ1ACADRAcaRU82c+e+4741//5ecREbHpuY3xrsNeERER1z3y9ITGF5QAAG2sOFKq+ZzuAw85NNY90R8bB9bFjPzMWPSawyc0diYmFrTjJSgBABpgpFyuef/khz73P6J7v5fFNV//u5jbMy++8P2bJjR2dfv49eIpbwCABihPYP9ku4wvKAEAGiCXrc8T1q0wvqAEAGiAjlyu5ndQTpbM9vHrRVACADRAoSPf0BNydlXdPn69CEoAgAaoZ9A1e/xMtVpt7g5RAIBpoFKtxs2PDkzo5eap8tlMnHjI/LqdlGOFEgCgAbKZTCwqdDZ8H2UmIhYXOusWkxGCEgCgYRYXOhu+j7IaEYsKnXUdQ1ACADRIZ35GLOye3dAxF3bPjs58fc+yEZQAAA10RE9XdOQak2AduWwc0dNV93EEJQBAA+Vz2Vi2oNCQsZYtKES+AfEqKAEAGmz+nFmxdF59Vw6XzuuK+XNm1XWMHQQlAEATLJk7p25RuXReVyyZO6cu134h3kMJANBEA0OjsWpdMUbKleRrdWy/nd6olckdBCUAQJOVypV4cMOm6B/cEpmIml4ttOP7F3bPjiN6uhqyZ3KPOQhKAIDWMFwai77icKwpDu88Uef5gbnr1/lsJhYXOmNRobPurwZ6KYISAKDFVKrVGBwdi+JIKYojpRgpl6NcqUYum4mOXC4KHfkodOSje9aMup6AM16CEgCAJJ7yBgAgiaAEACCJoAQAIImgBAAgiaAEACCJoAQAIImgBAAgiaAEACCJoAQAIImgBAAgiaAEACCJoAQAIImgBAAgiaAEACDJ/wMyAycb7YThjwAAAABJRU5ErkJggg==", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "visualize_graph_nx(g)" + ] + }, + { + "cell_type": "markdown", + "id": "e1996237", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## DFS\n", + "\n", + "> Depth-First Search (DFS) starts exploring graphs at a source node, explores as far as possible along each branch before backtracking. \n", + "\n", + "- Similar than for trees \n", + "- But needs to memorize visited nodes \n", + "\n", + "Steps:\n", + "\n", + "1. Put the source node into the stack.\n", + "2. Remove the node at the top of the stack to process it.\n", + "3. Put all unexplored neighbors into the stack (at the top).\n", + "4. If the stack is not empty, go back to step 2." + ] + }, + { + "cell_type": "markdown", + "id": "9a8d7a69", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## DFS with external visited list (iterative)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7a863da7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a d c b e " + ] + } + ], + "source": [ + "def dfs(graph, start_node):\n", + " visited = set()\n", + " stack = [start_node]\n", + "\n", + " while stack:\n", + " node = stack.pop()\n", + " if node not in visited:\n", + " print(node, end=' ')\n", + " visited.add(node)\n", + " for neighbor in reversed(graph[node]):\n", + " if neighbor not in visited:\n", + " stack.append(neighbor)\n", + " \n", + "dfs(g, 'a') # start from node 'a'." + ] + }, + { + "cell_type": "markdown", + "id": "865e6d9e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## DFS with external visited list (recursive)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9fe25ca1", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a d c b e " + ] + } + ], + "source": [ + "def dfs_rec(graph, start_node, visited=set()):\n", + " if start_node not in visited:\n", + " print(start_node, end=' ')\n", + " visited.add(start_node)\n", + " for neighbor in graph[start_node]:\n", + " if neighbor not in visited:\n", + " dfs_rec(graph, neighbor, visited)\n", + "\n", + "dfs_rec(g, 'a') # start from node 'a'." + ] + }, + { + "cell_type": "markdown", + "id": "8c628bee", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## DFS with internal visited list (recursive)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "efeb1fa0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A B D E F C " + ] + } + ], + "source": [ + "def dfs(graph, start_node):\n", + " if start_node not in graph:\n", + " return\n", + "\n", + " print(start_node, end=' ')\n", + " graph[start_node]['visited'] = True\n", + "\n", + " for neighbor in graph[start_node]['neighbors']:\n", + " if not graph[neighbor]['visited']:\n", + " dfs(graph, neighbor)\n", + "\n", + "graph = {\n", + " 'A': {'neighbors': ['B', 'C'], 'visited': False},\n", + " 'B': {'neighbors': ['A', 'D', 'E'], 'visited': False},\n", + " 'C': {'neighbors': ['A', 'F'], 'visited': False},\n", + " 'D': {'neighbors': ['B'], 'visited': False},\n", + " 'E': {'neighbors': ['B', 'F'], 'visited': False},\n", + " 'F': {'neighbors': ['C', 'E'], 'visited': False}\n", + "}\n", + "\n", + "dfs(graph, 'A')" + ] + }, + { + "cell_type": "markdown", + "id": "2e6cee31", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## BFS\n", + "\n", + "> Breadth-First Search (BFS) starts exploring graphs at a source node, explores all of its neighbors at the current depth before moving on to nodes at the next depth level.\n", + "\n", + "- Similar to DFS, it also requires tracking visited nodes to avoid revisiting them.\n", + "\n", + "Steps for BFS:\n", + "\n", + "1. Put the source node into the queue.\n", + "2. Remove the node at the front of the queue to process it.\n", + "3. Explore all unvisited neighbors of the processed node and enqueue them at the back of the queue.\n", + "4. If the queue is not empty, go back to step 2." + ] + }, + { + "cell_type": "markdown", + "id": "67afb125", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## BFS with external visited list (iterative)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "99dcb888", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A B C D E F " + ] + } + ], + "source": [ + "def bfs(graph, start_node):\n", + " visited = set()\n", + " queue = [start_node]\n", + "\n", + " while queue:\n", + " node = queue.pop(0)\n", + " if node not in visited:\n", + " print(node, end=' ')\n", + " visited.add(node)\n", + " for neighbor in graph.get(node, []):\n", + " if neighbor not in visited:\n", + " queue.append(neighbor)\n", + "\n", + "graph = {\n", + " 'A': ['B', 'C'],\n", + " 'B': ['A', 'D', 'E'],\n", + " 'C': ['A', 'F'],\n", + " 'D': ['B'],\n", + " 'E': ['B', 'F'],\n", + " 'F': ['C', 'E']\n", + "}\n", + "\n", + "bfs(graph, 'A')" + ] + }, + { + "cell_type": "markdown", + "id": "b48f3a6e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## BFS with backtracking\n", + "\n", + "To memorize the path used to visit nodes." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "071787a7", + "metadata": {}, + "outputs": [], + "source": [ + "def bfs_with_backtracking(graph, start_node, seeked_node):\n", + " visited = {node: False for node in graph}\n", + " path = {node: None for node in graph}\n", + " queue = [start_node]\n", + " found = False\n", + "\n", + " while queue:\n", + " current_node = queue.pop(0)\n", + " visited[current_node] = True\n", + "\n", + " for neighbor in graph[current_node]:\n", + " if not visited[neighbor]:\n", + " queue.append(neighbor)\n", + " visited[neighbor] = True\n", + " path[neighbor] = current_node\n", + "\n", + " if neighbor == seeked_node:\n", + " found = True\n", + " break\n", + "\n", + " if found:\n", + " break\n", + "\n", + " if not found:\n", + " return \"Path not found\"\n", + "\n", + " node = seeked_node\n", + " path_sequence = []\n", + " while node is not None:\n", + " path_sequence.insert(0, node)\n", + " node = path[node]\n", + "\n", + " return path_sequence" + ] + }, + { + "cell_type": "markdown", + "id": "7376c14d", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## BFS with backtracking\n", + "\n", + "Path re-construction from the BFS exploration:\n", + "\n", + "```python\n", + " if not found:\n", + " return \"Path not found\"\n", + "\n", + " node = seeked_node\n", + " path_sequence = []\n", + " while node is not None:\n", + " path_sequence.insert(0, node)\n", + " node = path[node]\n", + "\n", + " return path_sequence\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ebc07421", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Path from A to F: ['A', 'C', 'F']\n" + ] + } + ], + "source": [ + "graph = {\n", + " 'A': ['B', 'C'],\n", + " 'B': ['A', 'D', 'E'],\n", + " 'C': ['A', 'F'],\n", + " 'D': ['B'],\n", + " 'E': ['B', 'F'],\n", + " 'F': ['C', 'E']\n", + "}\n", + "\n", + "start_node = 'A'\n", + "seeked_node = 'F'\n", + "path = bfs_with_backtracking(graph, start_node, seeked_node)\n", + "print(f\"Path from {start_node} to {seeked_node}: {path}\")" + ] + }, + { + "cell_type": "markdown", + "id": "12bd1417", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graph property: path between two nodes?\n", + "\n", + "INPUT: a list of edges" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "4eae7ecd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There is a path between 0 and 5: True\n" + ] + } + ], + "source": [ + "def has_path(edges, n, start, end):\n", + " voisins = [[] for i in range(n)]\n", + " for i, j in edges:\n", + " voisins[i].append(j)\n", + " voisins[j].append(i)\n", + "\n", + " stack = [start]\n", + " visited = set(stack)\n", + " while stack:\n", + " cur = stack.pop()\n", + " if cur == end:\n", + " return True\n", + " for v in voisins[cur]:\n", + " if v not in visited:\n", + " stack.append(v)\n", + " visited.add(v)\n", + " return False\n", + "\n", + "edges = [(0, 1), (0, 2), (1, 3), (2, 4), (3, 5), (4, 5)]\n", + "num_nodes = 6 # number of unique nodes\n", + "start_node = 0; end_node = 5\n", + "result = has_path(edges, num_nodes, start_node, end_node)\n", + "print(f\"There is a path between {start_node} and {end_node}: {result}\")" + ] + }, + { + "cell_type": "markdown", + "id": "0f3d7abc", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Data structures: OOP\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "5f5bbbc5", + "metadata": {}, + "outputs": [], + "source": [ + "class Graph:\n", + " def __init__(self):\n", + " self.graph = {}\n", + "\n", + " def add_vertex(self, vertex):\n", + " if vertex not in self.graph:\n", + " self.graph[vertex] = []\n", + "\n", + " def add_edge(self, vertex1, vertex2):\n", + " if vertex1 in self.graph and vertex2 in self.graph:\n", + " self.graph[vertex1].append(vertex2)\n", + " self.graph[vertex2].append(vertex1) \n", + "\n", + " def get_nodes(self):\n", + " return list(self.graph.keys())\n", + "\n", + " def get_edges(self):\n", + " edges = []\n", + " for vertex, neighbors in self.graph.items():\n", + " for neighbor in neighbors:\n", + " if (vertex, neighbor) not in edges and (neighbor, vertex) not in edges:\n", + " edges.append((vertex, neighbor))\n", + " return edges\n", + "\n", + " def __str__(self):\n", + " return str(self.graph)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "b8565be4", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Nodes: ['a', 'b', 'c', 'd', 'e', 'f']\n", + "Edges: [('a', 'd'), ('b', 'c'), ('c', 'c'), ('c', 'd'), ('c', 'e')]\n" + ] + } + ], + "source": [ + "g_obj = Graph()\n", + "\n", + "for vertex in [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"]:\n", + " g_obj.add_vertex(vertex)\n", + "\n", + "# Add edges\n", + "g_obj.add_edge(\"a\", \"d\")\n", + "g_obj.add_edge(\"b\", \"c\")\n", + "g_obj.add_edge(\"c\", \"b\")\n", + "g_obj.add_edge(\"c\", \"c\")\n", + "g_obj.add_edge(\"c\", \"d\")\n", + "g_obj.add_edge(\"c\", \"e\")\n", + "g_obj.add_edge(\"d\", \"a\")\n", + "g_obj.add_edge(\"d\", \"c\")\n", + "g_obj.add_edge(\"e\", \"c\")\n", + "\n", + "\n", + "print(\"Nodes:\", g_obj.get_nodes())\n", + "g_obj.get_edges() == generate_edges(g)\n", + "print(\"Edges:\", g_obj.get_edges())" + ] + }, + { + "cell_type": "markdown", + "id": "f33ed24b", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## DFS using oop\n", + "\n", + "(Only explores a single connex component)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "ac540bec", + "metadata": {}, + "outputs": [], + "source": [ + "def dfs(self, start_vertex, visited = set()):\n", + " stack = [start_vertex]\n", + "\n", + " while stack:\n", + " vertex = stack.pop()\n", + " if vertex not in visited:\n", + " print(vertex, end=' ')\n", + " visited.add(vertex)\n", + " neighbors = self.graph[vertex]\n", + " for neighbor in neighbors:\n", + " if neighbor not in visited:\n", + " stack.append(neighbor)\n", + "\n", + "Graph.dfs = dfs # update the Graph class" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "aec8122f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a d c e b " + ] + } + ], + "source": [ + "g_obj.dfs(\"a\")" + ] + }, + { + "cell_type": "markdown", + "id": "c5360edc", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## DFS using oop\n", + "\n", + "Explores all the graph components" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "85b84e94", + "metadata": {}, + "outputs": [], + "source": [ + "def components(self):\n", + " visited = set()\n", + "\n", + " for vertex in self.graph:\n", + " if vertex not in visited:\n", + " self.dfs(vertex, visited)\n", + " print()\n", + "Graph.components = components # update the Graph class" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "643b438b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a d c e b \n", + "f \n" + ] + } + ], + "source": [ + "g_obj.components()" + ] + }, + { + "cell_type": "markdown", + "id": "09e71c1e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graph property: can a graph be n-colored?\n", + "\n", + "Two adjacent vertices (connected by an edge) cannot have the same color when properly colored. Example with $n = 2$ (i.e. can a graph be colored with 2 colors)." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "0395ed9d", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "class Node:\n", + " def __init__(self, v = None, n = []):\n", + " self.v = v\n", + " self.n = n\n", + " self.visited = False\n", + "\n", + "def two_color(r):\n", + " \n", + " stack = [r]\n", + " \n", + " while len(stack) > 0:\n", + " c = stack.pop(0)\n", + " for n in c.n:\n", + " if(c.v == n.v): # neighbours have same color\n", + " return False\n", + " if not n.visited:\n", + " stack.append(n)\n", + " n.visited = True \n", + "\n", + " return True" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "8fda865c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "n1 = Node(\"gray\")\n", + "n2 = Node(\"black\")\n", + "n3 = Node(\"gray\")\n", + "n4 = Node(\"gray\")\n", + "n5 = Node(\"black\")\n", + "n6 = Node(\"gray\")\n", + "\n", + "n1.n = [n2]\n", + "n2.n = [n1, n3, n4]\n", + "n3.n = [n2, n5]\n", + "n4.n = [n2, n5]\n", + "n5.n = [n3, n4, n6]\n", + "n6.n = [n5]\n", + "\n", + "print(two_color(n1)) " + ] + }, + { + "cell_type": "markdown", + "id": "d6584072", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Data structure: Adjacency matrix\n", + "\n", + "- Square: it has the same number of rows and columns.\n", + "- A 1 in a cell $m_{ij}$ indicates a link between nodes `i` and `j`.\n", + "- A 1 on the diagonal would indicate a loop.\n", + "- It is symmetric: $m_{ij} = m_{ji}$ for an undirected graph.\n", + "- For valued graphs, cells contain values (instead of `1`).\n" + ] + }, + { + "cell_type": "markdown", + "id": "95501f5c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Adjacency matrix (example)\n", + "\n", + "Given the graph `G`, what is its corresponding adjacency matrix?\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e045bef6", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 0, 0, 1, 0, 0]\n", + "[0, 0, 1, 0, 0, 0]\n", + "[0, 1, 1, 1, 1, 0]\n", + "[1, 0, 1, 0, 0, 0]\n", + "[0, 0, 1, 0, 0, 0]\n", + "[0, 0, 0, 0, 0, 0]\n" + ] + } + ], + "source": [ + "nodes = sorted(g.keys())\n", + "num_nodes = len(nodes)\n", + "adj_matrix = [[0] * num_nodes for _ in range(num_nodes)]\n", + "xf\n", + "for i, node in enumerate(nodes):\n", + " connected_nodes = g[node]\n", + " for connected_node in connected_nodes:\n", + " j = nodes.index(connected_node)\n", + " adj_matrix[i][j] = 1\n", + "\n", + "for row in adj_matrix:\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "id": "99ade3d5", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Adjacency matrix (OOP)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "c1419547", + "metadata": {}, + "outputs": [], + "source": [ + "class GraphAdj:\n", + "\n", + " def __init__(self, n):\n", + " self.__n = n\n", + " self.__g = [[0 for _ in range(n)] for _ in range(n)]\n", + "\n", + " for i in range(0, self.__n):\n", + " for j in range(0, self.__n):\n", + " self.__g[i][j] = 0\n", + "\n", + "\n", + " def addEdge(self, x, y):\n", + " if (x < 0) or (x >= self.__n):\n", + " print(\"Vertex {} does not exist!\".format(x))\n", + " if (y < 0) or (y >= self.__n):\n", + " print(\"Vertex {} does not exist!\".format(y))\n", + "\n", + " if(x == y):\n", + " print(\"Same Vertex!\")\n", + " else:\n", + " self.__g[y][x] = 1\n", + " self.__g[x][y] = 1\n", + "\n", + " def displayAdjacencyMatrix(self):\n", + " for i in range(0, self.__n):\n", + " print()\n", + " for j in range(0, self.__n):\n", + " print(\"\", self.__g[i][j], end = \"\")\n", + "\n", + " def removeEdge(self, x, y):\n", + " if (x < 0) or (x >= self.__n):\n", + " print(\"Vertex {} does not exist!\".format(x))\n", + " if (y < 0) or (y >= self.__n):\n", + " print(\"Vertex {} does not exist!\".format(y))\n", + " if(x == y):\n", + " print(\"Same Vertex!\")\n", + " else:\n", + " self.__g[y][x] = 0\n", + " self.__g[x][y] = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "079f999c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " 0 1 1 1 1 0\n", + " 1 0 0 1 0 0\n", + " 1 0 0 1 1 1\n", + " 1 1 1 0 0 1\n", + " 1 0 1 0 0 0\n", + " 0 0 1 1 0 0" + ] + } + ], + "source": [ + "obj = GraphAdj(6)\n", + "\n", + "obj.addEdge(0, 1)\n", + "obj.addEdge(0, 2)\n", + "obj.addEdge(0, 3)\n", + "obj.addEdge(0, 4)\n", + "obj.addEdge(1, 3)\n", + "obj.addEdge(2, 3)\n", + "obj.addEdge(2, 4)\n", + "obj.addEdge(2, 5)\n", + "obj.addEdge(3, 5)\n", + "\n", + "obj.displayAdjacencyMatrix()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "ecf8ed5a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " 0 1 1 1 1 0\n", + " 1 0 0 1 0 0\n", + " 1 0 0 0 1 1\n", + " 1 1 0 0 0 1\n", + " 1 0 1 0 0 0\n", + " 0 0 1 1 0 0" + ] + } + ], + "source": [ + "obj.removeEdge(2, 3);\n", + "obj.displayAdjacencyMatrix();" + ] + }, + { + "cell_type": "markdown", + "id": "d6ec3132", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graph property: is a graph connected? (matrix)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "1542423b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The graph is connected: False\n" + ] + } + ], + "source": [ + "def is_connected(graph):\n", + " n = len(graph)\n", + " visited = [False] * n\n", + " stack = [0]\n", + " while stack:\n", + " node = stack.pop()\n", + " if not visited[node]:\n", + " visited[node] = True\n", + " for i in range(n):\n", + " if graph[node][i] == 1 and not visited[i]:\n", + " stack.append(i)\n", + " return visited.count(True) == len(graph)\n", + "\n", + "g_m = [\n", + " [0, 0, 0, 0, 0],\n", + " [0, 0, 1, 0, 0],\n", + " [0, 1, 0, 1, 0],\n", + " [0, 0, 1, 0, 1],\n", + " [0, 0, 0, 1, 0]\n", + "]\n", + "\n", + "# We do a DFS\n", + "is_graph_connected = is_connected(g_m)\n", + "print(f\"The graph is connected: {is_graph_connected}\")" + ] + }, + { + "cell_type": "markdown", + "id": "99e73122", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graph property: how many connected components? (matrix)" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "id": "a4809d88", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def dfs(adj_matrix, node, visited):\n", + " visited[node] = True\n", + " for neighbor, connected in enumerate(adj_matrix[node]):\n", + " if connected and not visited[neighbor]:\n", + " dfs(adj_matrix, neighbor, visited)\n", + "\n", + "def count_connected_components(adj_matrix):\n", + " num_nodes = len(adj_matrix)\n", + " visited = [False] * num_nodes\n", + " components = 0\n", + "\n", + " for i in range(num_nodes):\n", + " if not visited[i]:\n", + " dfs(adj_matrix, i, visited)\n", + " components += 1\n", + "\n", + " return components" + ] + }, + { + "cell_type": "markdown", + "id": "a886fa9c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graph property: is there a self-connected node? (matrix)\n", + "\n", + "I.e is there for instance a node A $\\rightarrow$ A" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "id": "81106cd6", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def has_ones_in_diagonal(matrix):\n", + " for i in range(len(matrix)):\n", + " if matrix[i][i] == 1:\n", + " return True\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "70044448", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# We check if there is a `1` in the diagonal\n", + "has_ones_in_diagonal(g_m)" + ] + }, + { + "cell_type": "markdown", + "id": "083bc1ef", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graph property: is a graph oriented? (matrix)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "9e0860f6", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "# check if the matrix is equal to its transpose.\n", + "def is_symmetric(matrix):\n", + " rows = len(matrix)\n", + " cols = len(matrix[0])\n", + "\n", + " for i in range(rows):\n", + " for j in range(cols):\n", + " if matrix[i][j] != matrix[j][i]:\n", + " return False\n", + " return True" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "0f6f774b", + "metadata": {}, + "outputs": [], + "source": [ + "g_empty = []\n", + "n = 5\n", + "for i in range(n):\n", + " row = []\n", + " for j in range(n):\n", + " row.append(0)\n", + " g_empty.append(row)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "0ae92b34", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 0, 0, 0, 0]\n", + "[0, 0, 0, 0, 0]\n", + "[0, 0, 0, 0, 0]\n", + "[0, 0, 0, 0, 0]\n", + "[0, 0, 0, 0, 0]\n" + ] + } + ], + "source": [ + "for r in g_empty:\n", + " print(r, end=\"\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "3928df4f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_symmetric(g_empty)" + ] + }, + { + "cell_type": "markdown", + "id": "2be90e37", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graph property: is a graph connected? (dict)\n", + "\n", + "We check if the dfs equals the number of nodes." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "93eb41a0", + "metadata": {}, + "outputs": [], + "source": [ + "def is_connected(graph):\n", + " if not graph:\n", + " return True\n", + "\n", + " visited = set()\n", + " start_node = list(graph.keys())[0]\n", + "\n", + " def dfs(node):\n", + " visited.add(node)\n", + " for neighbor in graph[node]:\n", + " if neighbor not in visited:\n", + " dfs(neighbor)\n", + "\n", + " dfs(start_node)\n", + "\n", + " return len(visited) == len(graph)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "b53901d2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "is_connected(g)" + ] + }, + { + "cell_type": "markdown", + "id": "3c584a2a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Weighted graphs\n", + "\n", + "Graph with numerical values associated with nodes or edges.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "e6aae369", + "metadata": {}, + "outputs": [], + "source": [ + "graph_w = {\n", + " \"a\": [(\"d\", 1)],\n", + " \"b\": [(\"c\", 3)],\n", + " \"c\": [(\"a\", 1), (\"b\", 3), (\"d\", 1), (\"e\", 1)],\n", + " \"d\": [(\"a\", 1), (\"c\", 1)],\n", + " \"e\": [(\"c\", 1)],\n", + " \"f\": []\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "id": "e6ffdf83", + "metadata": {}, + "outputs": [], + "source": [ + "def greedy_heuristic_shortest_path(graph, start, end):\n", + " current_node = start\n", + " visited = set()\n", + "\n", + " while current_node != end:\n", + " visited.add(current_node)\n", + " min_weight = float('inf')\n", + " next_node = None\n", + "\n", + " # Find the neighboring unvisited node with the smallest weight\n", + " for neighbor, weight in graph[current_node]:\n", + " if neighbor not in visited and weight < min_weight:\n", + " min_weight = weight\n", + " next_node = neighbor\n", + "\n", + " if next_node is None:\n", + " return float('inf') # No path found\n", + "\n", + " current_node = next_node\n", + "\n", + " return 0 # Path found from start to end" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "id": "93ae7fa1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "greedy_heuristic_shortest_path(graph_w, \"a\", \"e\")" + ] + }, + { + "cell_type": "markdown", + "id": "729aa7e8", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Weighted graphs" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "c4f13ab0", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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0oVS8n68Wq3brUiHw8h4NQDfVzcFp2vXrlq6dOk5p+Cg/r788svT2+EnTpzQ4MGDlZWVpZ/85Ce8DwL4lpAvlGe60PFqcRHS6rlv6Oa2SfrPn43xe14A3+btFBzUT3l5uWbPnq3s7Gxt375dV111lbKysjR8+HDFx8ebjgcgAIRVoTzT+Y5Xe/DBB7V161Z98cUXfCIHDGIKjnm2bWvlypWaMmWKnE6nmjRpooyMDD322GNKSUkxHQ+AQWFbKM/XypUr1bdvX61evVq9evUyHQcIS1VVVUpPT9e2bdu0du1aHlweAPbu3aucnBy98sorOnnypIYOHaqsrCz16tWLD99AGKJQnoPH49Hll1+um266SW+88YbpOEDYqa2t1bBhw7RgwQItX76cB5cHmLKyMr3xxhvKzs5Wfn6+rrnmGmVlZenBBx/k/lYgjHBU+RwiIiI0btw4/f3vf9exY8dMxwHCyplTcN5++23KZABKTEzUo48+qh07dmjp0qVq166dxo0bp3bt2umpp57SgQMHTEcE4AcUyvMwatQo1dbW6s033zQdBQgrZ07Bufvuu03HwVlYlqU77rhDubm5Kigo0MiRI5WTk6NLL71U999/v9auXcvscCCEseV9nu69917t3LlT27Zt4/4gwA9effVVjRkzhik4QezkyZN6/fXXlZ2drYKCAl133XXKysrSsGHDFBsbazoeAB+iUJ6npUuXql+/flq3bp1uvvlm03GAkMYUnNDi8Xi0bNkyZWdna9GiRWrevLkyMzP16KOPqk2bNqbjAfABCuV58ng86tChg/r06aOZM2eajgOELKbghLaCggLl5OTo1VdfVWVlpe655x5NmjRJN910Ex8cgCDGPZTnqe5wzttvv63S0lLTcYCQlJ+frwEDBuj666/XW2+9RZkMQampqZo8ebIOHjyov/71r9qyZYt69uypG2+8UW+88YZcLpfpiADqgUJ5AUaPHq3q6mq99dZbpqMAIefQoUNKT09Xq1at5HQ6eeRMiGvcuLEmTpyo/Px8LVy4UM2aNdNDDz2klJQUPf300zp8+LDpiAAuAFveF2jIkCHav3+/tmzZwvYM4CNMwYF0aoV66tSpmjVrllwul+6//35lZWWpR48epqMBOAdWKC9QRkaGPvnkE23evNl0FCAkVFVVaciQITp48KCWLFlCmQxjnTt31tSpU1VUVKTnn39e69ev10033aQePXpo9uzZqq6uNh0RwI+gUF6g9PR0tW3bVi+++KLpKEDQq62t1ciRI7Vhwwbl5uYyUhGSpKSkJD3++OMqKChQbm6ukpKSNGLECF1yySX63e9+pyNHjpiOCOA7KJQXKDIyUmPHjtWcOXN08uRJ03GAoMUUHJxLZGSkBg4cqKVLl2r79u0aOnSo/vSnPyklJUUjR47Upk2bTEcE8A0KZT2MGTNGFRUVevvtt01HAYIWU3BwIbp27app06bp4MGDeu655/SPf/xDN954o26++WbNmTOH7XDAMA7l1NOAAQN09OhRbdy40XQUIOgwBQfeqq2t1cKFC5Wdna0VK1aoVatWeuyxx5SZmank5GTT8YCwQ6Gsp/nz52vIkCHaunWrrr32WtNxgKCxcOFC3X333UzBgc98/vnnmjp1ql5//XXV1tbqwQcf1MSJE3X99debjgaEDQplPbndbqWkpGjo0KHKyckxHQcICkzBQUM6duyYXnnlFU2dOlWFhYVKS0tTVlaWhg4dqqioKNPxgJDGPZT15HA4NGbMGL355psqLy83HQcIeEzBQUNr2rSpfvnLX2rPnj2aN2+eoqKi9MADD+jSSy/Vs88+q6NHj5qOCIQsVii9sG/fPl122WV69dVXNWrUKNNxgIB16NAh9ezZU4mJiVqzZo2aNGliOhLCxLZt2zRlyhS9+eabsm1bw4cPV1ZWFrcqAT5GofRSv379dPLkSa1bt850FCAgnTkFZ926dWrXrp3pSAhDX3/9tV5++WXl5OTowIED6tWrl7KysjRkyBA5HA7T8YCgx5a3lzIyMvTxxx/r888/Nx0FCDjfnYJDmYQpzZo1069+9Svt3btXc+fOlWVZuu+++3TZZZfpueee09dff31er/P5559r5syZ+uKLLxo4MRBcKJReGjx4sJKTk/XSSy+ZjgIEFKbgIBA5HA7dc889+uijj7R161bdcccdeuaZZ9S2bVtlZGSopKTkR7+3pqZGa9eu1TvvvKOePXuqT58+OnTokB/TA4GLLW8f+NWvfqWXXnpJRUVFiouLMx0HMM62bU2cOFHTp0/XvHnzeHA5AtrRo0f10ksvae7cudqwYcNZT4QfO3ZMkZGRGjVqlHbu3Kn58+erU6dOfkwLBCYKpQ/s2rVLqampeuONNzRixAjTcQDj/vCHP+ipp57SjBkzlJmZaToOcF5qa2tlWZYiIn5488627dPPTU1MTNQzzzyjSZMm8UgiQGx5+0SnTp3Up08ftr0BnZqC89RTT+mZZ56hTCKoREZG/miZlE4VTkmaPn26YmJilJ6eTpkEvkGh9JHMzEytXr1a+fn5pqMAxixcuFAZGRn62c9+xkhFhJy60+B/+ctfdP/99+uyyy4znAgIHBRKHxk6dKiaNWuml19+2XQUwIj169frvvvu06BBg5STk8NIRYSUutXJVatW6csvv9SwYcMUHx9vOBUQOLiH0oeeeOIJvfbaayoqKlJMTIzpOIDf5OfnKy0tTV27dtXSpUs5nIaQdccdd8jhcGj27Nlq2rSp6ThAwKBQ+tAXX3yhrl276u2339YDDzxgOg7gF0zBQSjzeDx655131LhxY6WmpqpLly566623dP/99//o10s6672YQCjid7wPdenSRb169dKLL75oOgrgF6Wlperfv79qa2uVl5dHmURI2rlzpwYNGqQuXbooPj7+rPdORkRE6K677tLf/vY3lZaW+jElYBaF0scyMjK0cuVK7d6923QUoEFVVVXp7rvv1oEDB5iCg5AVERGhZ555RjU1NZo2bZouvvhi3XLLLXruuedUUVHxva8vLS3VxRdfrP/3//6f2rRpowkTJnBYE2GBQulj9957ry666CIO5yCkMQUH4SYyMlKZmZnau3ev3nvvPUVGRv7goZykpCS9+eab2r9/v5544gn9/e9/V5cuXZSenq5Fixad3hIHQg33UDaArKwsvfPOOzpw4ICio6NNxwF8yrZtZWVladq0aUzBAc7B5XLp3Xff1eTJk7V582Z17NhREydO1KhRo9S4cWPT8QCfYYWyAWRkZKi4uFi5ubmmowA+99xzz2nq1KmaPn06ZRI4h5iYGI0cOVKbNm3SunXrdMMNN+iJJ55QmzZtlJWVpYKCAtMRAZ9ghbKB3HzzzUpKStLixYtNRwF85tVXX9WYMWP0zDPP6OmnnzYdBwhKRUVFeuGFFzRjxgwdPXpU/fv3V1ZWlu68805OhyNoUSgbyMyZMzVu3Djt3btX7du3Nx0H8NrChQt19913a+zYsXrhhRd4cDngpaqqKr3zzjuaPHmytm7dqtTUVE2cOFEPP/ywGjVqZDoecEEolA2kvLxcrVq10qRJk/T73//edBzAKxs2bFCfPn3Ur18/zZ07V5GRkaYjASHDtm2tW7dO2dnZeu+995SQkKDRo0drwoQJ6tixo+l4wHmhUDagRx99VE6nU/v37z89AxYINjt37lRaWpq6dOnCFByggR08eFDTp0/XjBkzdOzYMd11112aNGmSbr/9dnYFENAolA1o69at6tatm+bPn6/BgwebjgNcMKbgAGZUVlbq7bff1uTJk/Xpp5+qS5cumjhxokaOHKnExETT8YDvoVA2sBtuuEEtW7bUggULTEcBLkhpaal69+6tY8eOad26dTy4HDDAtm2tWbNG2dnZev/999WoUSONHTtW48ePP+vEHsDfOE7WwDIzM5WXl6cDBw6YjgKct7opOIWFhVq8eDFlEjDEsiz17t1bc+fO1b59+/TII49o1qxZ6tixo+6++26tWLFCrAshEFAoG9iDDz6ouLg4zZw503QU4LycOQVnwYIFuuKKK0xHAiApJSVFzz33nA4cOKAXX3xR+/bt0+23364rr7xSM2bMUHl5uemICGMUygbWqFEjPfjgg3rllVdUW1trOg5wVrZt6/HHH9e8efM0Z84cpaWlmY4E4Dvi4+M1btw4ffrpp/rwww91+eWX67HHHlPbtm31y1/+Ul9++aXpiAhDFEo/yMjI0IEDB7RkyRLTUYCzOnMKzpAhQ0zHAXAWlmXpJz/5iebNm6c9e/YoIyNDL7/8sjp06KChQ4fqww8/ZDscfsOhHD+wbVvXXXedLr30Ur3//vum4wA/aNasWRo9ejRTcIAgVl5ertmzZys7O1vbt2/XVVddpaysLA0fPlzx8fGm4yGEUSj9JCcnR5MmTdKBAwfUqlUr03GAb2EKDhBabNvWhx9+qOzsbDmdTjVp0kQZGRl67LHHlJKSYjoeQhCF0k+OHz+u1q1b6ze/+Y2eeuop03GA05iCA4S2vXv3atq0aXr55Zd18uRJDR06VFlZWerVqxcfHuEzFEo/GjVqlFavXq3du3crIoLbV2Fe3RSczp07a9myZUzBAUJYWVmZ3njjDWVnZys/P1/XXHONsrKyTj+NBPAGhdKP1q1bp7S0NC1dulR33HGH6TgIc3VTcBISErRmzRo1bdrUdCQAfmDbtpYvX67s7GwtXLhQTZs2VWZmph599FGeOYt6o1D6kW3buvLKK9W1a1f9/e9/Nx0HYYwpOAAkaffu3crJydHMmTNVXl6un/70p8rKylJaWhrb4bgg7Lv6kWVZyszM1AcffKCvvvrKdByEqaqqKg0ZMoQpOADUsWNH/d///Z8OHjyoyZMna9u2berVq5euv/56zZo1S1VVVaYjIkhQKP1s5MiRioyM1GuvvWY6CsJQ3RSc9evXKzc3lyk4ACSdGsIxfvx47dixQ4sXL1arVq00evRopaSk6De/+Y2KiopMR0SAY8vbgBEjRmjjxo3auXMnWwrwG9u2lZWVpWnTpum9997jweUAzqqgoEA5OTl69dVXVVlZqXvuuUeTJk3STTfdxN9d+B5WKA3IyMjQrl279NFHH5mOgjDCFBwAFyI1NVWTJ0/WwYMH9de//lVbtmxRz549deONN+qNN96Qy+UyHREBhBVKA2zbVufOnXX99dfrrbfeMh0HYYApOAC85fF4tGTJEmVnZ2vx4sVKTk7WI488okceeYSBHaBQmvLnP/9Z//Vf/6VDhw6pWbNmpuMghDEFB4Cv7dy5U1OmTNGsWbPkcrl0//33KysrSz169DAdDYaw5W3Iww8/LNu29frrr5uOghC2YcMG3XfffRo4cKBycnIokwB84vLLL9fUqVNVVFSk559/XuvXr9dNN92kHj16aPbs2aqurjYdEX7GCqVBDzzwgD777DNt376dv+jhc0zBAeAvtbW1ysvLU3Z2tpYtW6aWLVvqkUce0c9+9jO1bNnSdDz4AYXSoBUrVuj222/XmjVrdMstt5iOgxDCFBwApuzYsUNTp07Va6+9ppqaGj3wwAPKyspS9+7dTUdDA6JQGuTxeNSpUyfdcsstPJcSPsMUHACB4Pjx45o5c6amTp2qffv26aabblJWVpbuueceRUdHm44HH+MeSoMiIiKUkZGhd999VyUlJabjIAS4XC6m4AAICBdddJF+8YtfaNeuXZo/f74SEhI0fPhwtW/fXv/7v/+r4uJi0xHhQxRKw0aNGiW3263Zs2ebjoIgxxQcAIEoMjJSgwcP1vLly/XZZ59p8ODBevbZZ9WuXTuNGjVKmzdvNh0RPsCWdwC45557tGvXLn366acczkG9MAUHQDA5duzY6e3w/fv3Ky0tTVlZWRo6dKiioqJMx0M9sEIZADIyMvTZZ59p48aNpqMgSNVNwZk2bRplEkDAa9q0qf7zP/9Te/bs0fvvv6+oqCg98MADuvTSS/Xss8/q6NGjpiPiArFCGQBqa2vVoUMH9e3bV6+88orpOAgyCxcu1MCBA/X000/rmWeeMR0HAOpl27ZtmjJlit58803Ztq3hw4crKytL1157relo31Je49GRCreKK92qqrVVa9uKtCzFRlpKjnOoZbxDCVHht15HoQwQv//97/Xcc8/p8OHDaty4sek4CBIej0fV1dV65ZVX9Nhjj3HLBICg9/XXX+vll19WTk6ODhw4oF69eikrK0tDhgyRw+Ewkqm40q0tR6tUUOpShftUbbIknfmWa9tSXaGKd1hKTYpRt+axSo4zk9nfKJQBoqioSCkpKcrJydEjjzxiOg6CiMfjkWVZlEkAIcXtdmv+/PnKzs7W6tWr1a5dOz322GPKyMjwy8hi27ZVUFqtDV9V6lCFW5b+XRjPR4Qkj6TW8Q71aBGn1KTokH6fplAGkMGDB6uoqIgTbwAAnOGTTz7RlClTNHv2bFmWpREjRmjixIm6+uqrG+R6ZTUeLS48qd0nai64SH5X3fd3bByl9JRGSgzR7XAKZQBZsGCBBg0apE8++UTXXHON6TgAAASUf/3rX3rppZeUk5OjoqIi/eQnP1FWVpYGDRrks+3w/BKX8grLVO2xvSqS32VJio6w1D8lUZ2bxPjwlQMDhTKA1NbWatGiRbrrrrsUGRlpOg4M27hxoxo1aqQuXbqYjgIAAaWmpkYffPCBsrOztXbtWl1yySUaP368xo4d69Wo2Y3FlVpZVO7DpD+sb5sEdU+Oa/Dr+BOFMsDU1tZSJqHc3Fzdfffd6tevn/785z/zkHIA+BFbtmxRdna25syZo8jISI0cOVITJ07UlVdeeUGv468yWSfUSiWFEggwX375pe655x7dcMMNWrJkiS6++GJNnz5d119/vSIiQvPeGwDwVnFxsV588UVNmzZNhw8f1m233aasrCwNHDjwnAs1+SUuffDlST8l/bch7RuFzPY3fzsBAebAgQO66qqr9Mtf/lJffPGFXC6Xhg8fro8++khut9t0PAAISMnJyfrNb36j/fv3a86cOaqsrNSQIUPUsWNH/eUvf1FJSckPfl9ZjUd5hWV+TntKXmGZyms8Rq7ta6xQBpmamhrt3btX8fHxateunek4aABut1vbt2//1sGsPn366IsvvtCMGTPUv39/RUdHq7CwUC6XS506dTKYFgAC16ZNmzRlyhS9/fbbioqK0kMPPaSJEyeqa9eukk49Gui9vSe050SNTw/gnC9LUsekaP300kZB/0ghViiDzOHDhzV58mQmooQwh8NxukxWV1dLkj788EPdeuutGjFihN599119+umnGjBggHJzc01GBYCA1r17d73++usqLCzUr371K33wwQe64oordMcddyg3N1f5JVXabahMSqceJ7SrtFoFpdWGEvgOK5RBwu12KzIyUpZlae3atRo+fLjefPNN9e7d23Q0NDC32336cRiTJk3SjBkzFB8fr65du2rt2rWG0wFA8KiurtbcuXOVnZ2tDRs26PE5K9Qi9UrJMre+ZklqneDQyNSLjGXwBVYoA1BFRYUqKyvl8XhO3zPncDhkWZb27t2rV199VQcPHmR1Kkw4HA7V1tZKkiZPnqxmzZrp6quvpkwCwAWKjo7W8OHDtX79ei3fsEUtLr/aaJmUTq1SFpWfmg0ezCiUAeiZZ57R//zP/ygiIuL0ytTMmTN1zTXXqGPHjvrkk0/0/PPPa/jw4YaToiH80KZBZGSkXC6X7r//fpWVlSkvL89AMgAIHTUtOylQ7lqMkLTlaJXpGF6hUAag1NRUvfzyy1q3bp1Gjx6t+Ph4PfHEE7ruuuu0cOFCLVy4UJMmTdJ1111nOip87OjRoyopKfnR09xpaWnavn274uJC59llAGBCQanL2L2T3+WRtKvUZTqGV7iHMkAlJyfrX//6l3r06KFhw4apb9++uuyyyxQfH286GhpIaWmpbr31VsXHx2v16tU/OEbMtu2gPwkIAKaV13g05fNjpmN8z8QrmyohSGd9+2bwJXxuxIgRWrRokebNm6cWLVrwQOsQ53K5NGTIEO3fv19r16790Zm0lEkA8N6RisC8X/FIhVsdkqJNx6gXWkqAeuSRR1RQUKDy8nLKZIjzeDwaOXKk1q9fr9zcXMYsAkADK650e33/5L4tH2vG2EF6pvdlevbOKzTv9z9X5cnSer+e9U2uYMUKZYBKTU1VVlaWjhw5oo4dO5qOgwZi27Yef/xxvffee3rvvfd0yy23mI4EACGvqtaWZUn1venvWNF+zZo4TInNktVr5HhVHP9a6+a8pOrKCg17dka9XtOyJFdt8N6FSKEMYM8//7yioqJMx0AD+uMf/6gpU6bohRde0JAhQ0zHAYCwUOvl8ZGCdStVXVmhYwe/1PLpz53+9fy1y7x6XXcQH2uhUAYwymRomzVrlp588kk9/fTT+tnPfmY6DgCEjUgv70evO8/cpXc/9Xww4/SvezzezeV2BPF98hTKIHLmxBQEt0WLFmncuHHKzMzU008/bToOAISV2Eir3tvdknR5Wl9Fx8Vr98Y1Srn6BiU0aabDBdtV+tUhpd7cp16vadtSTGTwFkpOewSJ2tpaTZs2TUuWLDEdBV7asGGD7rvvPg0cOFA5OTmc3AYAP0uOc3j1DMqmbS7RqClvq+0V1+qj16ZqwV/+W/s/3agON9Z/HLL9Ta5gxXMog8gtt9yiuLg4LVvm3T0aMGfnzp1KS0vT5ZdfruXLl/OAcgAwgOdQ+l5wpg5TmZmZWr58ufbs2WM6Curh0KFD6tevn1q0aKHc3FzKJAAYkhAVoXhHYO0OJTisoC2TEoUyqNx7771KSkrSK6+8YjoKLlBpaanuuusuud1uLV68WE2bNjUdCQDCWmpSTEDN8u6UFGM6hlcolEEkPj5eI0eO1MyZM1VTU2M6Ds7TmVNwlixZonbt2pmOBABhr1vz2ICa5d2teazpGF6hUAaZjIwMffXVV1qwYIHpKDgPTMEBgMCUHOdQ63iH8VVKS1KbBEdQH8iRKJRB5+qrr1aPHj304osvmo6CczhzCs6cOXOYggMAAaZHizjjq5S2pBuTg/+eegplEMrIyNCSJUu0f/9+01FwFnVTcKZNm8YUHAAIQKlJ0erYOMrYKqUlqVNStFKTog0l8B0KZRB64IEHlJiYyOGcAMYUHAAIfJZlKT2lkaIjzFTK6AhL6e0SQ+J5xBTKIJSYmKjhw4dr5syZcrvdpuPgO+qm4GRkZDAFBwACXGJUhPqnJBq5dv+UxKB+VNCZQuPfIgxlZmaqqKhIeXl5pqPgDHVTcAYMGKBp06aFxKdOAAh1nZvEqG+bBL9es2+bBHVuEtyPCjoTk3KC2PXXX682bdrI6XSajgJJBQUF6tmzJ1NwACBIbSqu1Iqi8ga/Tt82CeoeAgdxzsQKZRDLzMzUwoULdfDgQdNRwt7hw4eZggMAQa57cpyGtG+kmAjL5wd1LEkxEZaGtG8UcmVSolAGtQcffFCxsbF69dVXTUcJa6Wlperfv79qamqYggMAQa5zkxhldG2iDo2jJMnrYln3/R2TopXZtUlIbXOfiS3vIDdu3DgtW7ZMe/fuVWRkpOk4Ycflcik9PV2ffPKJ1q5dy4PLASBE2LatgtJqbfiqUocq3IrQqYk256vu69skOHRjcpxSk6JD+r56CmWQ27Bhg2666Sbl5eUpPT3ddJyw4vF4NGzYMDmdTi1btky9evUyHQkA0ACKK93acrRKu0pdKnefqk2WpDP7oW3r9EPSExyWOiXFqFvz2KCfgHO+KJRBzrZtXXvtterQoYPmzZtnOk7YsG1bkyZNUk5Ojt577z0eXA4AYaK8xqMjFW4VV7rlqrXltm05LEsxkZaS4xxqGe8ImUcBXYjwqM0hzLIsZWRk6Oc//7kOHz6sVq1amY4UFuqm4EyfPp0yCQBhJCEqQh2SotUhBKbb+FL4VegQNGLECDkcDs2aNct0lLBQNwXnt7/9rR555BHTcQAAMI4t7xDx8MMPa+3atdq1a5ciIvic0FDy8vI0aNAgjRkzRjNmzAjpG6wBADhfNI8QkZmZqb1792rlypWmo4SsDRs26N5772UKDgAA38EKZYiwbVtXXHGFrrrqKr3zzjum44QcpuAAAPDjWKEMEZZlKTMzU++//76OHj1qOk5IqZuCk5yczBQcAAB+AIUyhIwcOVKWZem1114zHSVknDkFZ8mSJUzBAQDgB7DlHWL+4z/+Q//85z+Vn5/PPX5ecrlc6t+/v7Zu3coUHAAAzoIVyhCTkZGhgoICrV692nSUoObxePTQQw9p3bp1cjqdlEkAAM6CQhlibr31VnXq1EkvvfSS6ShBy7ZtPf7445o7d67efvttRioCAHAOFMoQUzc5Z+7cuTp27JjpOEHpT3/6k6ZMmaKcnBym4AAAcB4olCHo4Ycflsfj0RtvvGE6StB57bXX9Otf/5opOAAAXAAO5YSo+++/X9u3b9fnn3/O4ZzzxBQcAADqhxXKEJWZmakdO3bo448/Nh0lKDAFBwCA+mOFMkR5PB517NhRvXv31qxZs0zHCWhnTsFZtmyZ4uPjTUcCACCosEIZoiIiIpSRkaF3331Xx48fNx0nYH13Cg5lEgCAC0ehDGGjRo1SdXW1Zs+ebTpKQGIKDgAAvsGWd4j76U9/qj179uiTTz7hvsAzMAUHAADfYYUyxGVkZGjbtm3atGmT6SgBgyk4AAD4FoUyxN15551KSUlhcs43bNvWz3/+c82dO1dz5sxhCg4AAD5AoQxxkZGRGjt2rObMmaOTJ0+ajmPcn/70J2VnZysnJ0dDhw41HQcAgJBAoQwDY8aMUWVlpebMmWM6ilFMwQEAoGFwKCdMDBo0SIcPH9Y///lP01GMYAoOAAANhxXKMJGZmanNmzdry5YtpqP43caNG5mCAwBAA2KFMky43W5dcsklGjx4sKZPn246jt8UFBQoLS1NqampTMEBAKCBsEIZJhwOh8aOHavZs2ervLzcdBy/qJuC07x5c6bgAADQgCiUYWTs2LEqKyvTO++8YzpKgztzCs7ixYuZggMAQANiyzvM9O/fX8ePH9fHH39sOkqDcblcuuuuu7RlyxatWbNGV155pelIAACENFYow0xGRobWr1+vzz77zHSUBlE3Becf//iHnE4nZRIAAD+gUIaZQYMGqUWLFiE5OYcpOAAAmEGhDDNRUVEaPXq03njjDVVWVpqO41NMwQEAwAwKZRgaN26cjh8/rrlz55qO4jNMwQEAwBwO5YSp22+/XS6XS2vWrDEdxWtMwQEAwCxWKMNUZmam1q5dqy+++MJ0FK8wBQcAAPMolGHq7rvv1sUXXxzUh3MKCgo0YMAAXXvttZozZ44cDofpSAAAhCUKZZiKiYnRqFGj9Prrr6uqqsp0nAvGFBwAAAIHhTKMjRs3Tl9//bXef/9901EuCFNwAAAILBzKCXM/+clPFBERoZUrV5qOcl7qpuBs3rxZa9eu5cHlAAAEAFYow1xGRoY+/PBD7dq1y3SUc2IKDgAAgYlCGebuueceNWnSRC+//LLpKGf13Sk4vXv3Nh0JAAB8g0IZ5mJjY/XQQw/p1VdfVXV1tek4P4opOAAABC4KJZSRkaGjR4/K6XSajvKDmIIDAEBg41AOJElpaWlKSEjQ0qVLTUf5lropOKNHj9aLL77Ig8sBAAhArFBC0qnJOcuWLdO+fftMRzntzCk406dPp0wCABCgKJSQJN13331KSkoKmMM5TMEBACB4sOWN0yZMmKB58+Zp//79ioqKMpbj8OHD6tmzp+Li4rR27VoeXA4AQIBjhRKnZWRk6PDhw1q4cKGxDEzBAQAg+LBCiW/p0aOHLr74YiOlkik4AAAEJ1Yo8S0ZGRnKy8tTYWGhX6/LFBwAAIIXhRLfMmzYMCUkJGjmzJl+uyZTcAAACG4USnxLYmKihg8frldeeUW1tbV+uSZTcAAACG4USnxPZmamDh48qMWLFzf4teqm4Pz3f/83U3AAAAhSHMrBD+rWrZvatWun+fPnN9g1mIIDAEBooFDiB73wwguaMGGC9u/frzZt2kiSyms8OlLhVnGlW1W1tmptW5GWpdhIS8lxDrWMdygh6vwWvTdu3Kg+ffro9ttv13vvvceDywEACGIUSvygEydOqFWrVnry2ed1w9CHVFDqUoX71G8VS9KZi4m2LdX9Jop3WEpNilG35rFKjvvhklhQUKC0tDSlpqZq2bJlio+Pb9h/GQAA0KAolPge27ZVUFqtt9Z9rrhWl8jSvwvj+YiQ5JHUOt6hHi3ilJoUfXo7+8iRI7r55puZggMAQAihUOJbymo8Wlx4UrtP1JxaevTivsa6ItqxcZTSUxrJU1mmW2+9VUePHtW6deuUkpLis9wAAMAcblzDafklLuUVlqna881nDC8PydR9UtlzokYv7jimTbP+T/v27dPatWspkwAAhBBWKCFJ2lhcqZVF5Q32+rbHIysiQu2rjmjYzUzBAQAglPAcSjR4mZQkK+LUb7UvY1tqU3Flg14LAAD4F4UyzOWXuBq8TH7XiqJy5Ze4/HpNAADQcCiUYaysxqO8wjIj184rLFN5jcfItQEAgG9RKMOUbdtaXHjy3wdw/KzaY2vxgTJxCy8AAMGPQhmmCkqrtftEzQU9X9KXbEm7SqtVUFptKAEAAPAVCmWY2vBVpUxPzrZ06kAQAAAIbhTKMFRc6dahCrex1ck6tqSi8lOzwQEAQPCiUIahLUerjK9O1onQqTwAACB4USjDUEGpy/jqZB2PpF2lPEIIAIBgRqEMM+U1HlW4fVMna91uPdmtuZ7s1ty7TG6bRwgBABDEKJRh5khFYN6vGKi5AADAuTlMB4B/FVe6ZUn13vL+5/y3tGz6c7IsSzc/MNYnmaxvcnVIivbJ6wEAAP9ihTLMVNXasup5Iqd4b4He/99fqLqiXL0fnqgDn2/xSSbLkly1gXJXJwAAuFAUyjBT68Vkmt0bP5KntlZX3zlEPYeN05CnnvdZLjcTcwAACFoUyjATWd/lSel7YxJ9OTbR4UUuAABgFvdQhpnYSEv17YEde9wqKyJC25Z+oBYdu2jvP9f6JJNtSzGRFEoAAIIVK5RhJjnOUe8DOS0uu1xD/+vPio6L16qZ/6e2V1znk0z2N7kAAEBwsmxf7lsi4JXXeDTl82OmY3zPxCubKiGKzzcAAAQj/gYPMwlREYp3BNb2coLDokwCABDE+Fs8DKUmxQTULO9OSTGmYwAAAC9QKMNQt+axATXLu1vzWNMxAACAFyiUYSg5zqHW8Q7jq5SWpDYJDg7kAAAQ5CiUYapHizjjq5S2pBuT4wynAAAA3qJQhqnUpGh1bBxlbJXSktQpKVqpzO8GACDoUSjDlGVZSk9ppOgIM5UyOsJSertEWUzIAQAg6FEow1hiVIT6pyQauXb/lEQeFQQAQIjgb/Qw17lJjPq2SfDrNfu2SVDnJjwqCACAUEGhhLonx/mtVPZtk6DuHMQBACCkMHoRp+WXuJRXWKZqj+3TE+CWTt0z2T8lkZVJAABCEIUS31JW49HiwpPafaJGluRVsaz7/k5J0Upvxz2TAACEKgolvse2bRWUVmvDV5U6VOFWhE5NtDlfdV/fJsGhG5PjlJoUzWluAABCGIUSZ1Vc6daWo1XaVepSufvUbxVL0pn90Lb/vZKZ4LDUKSlG3ZrHMgEHAIAwQaHEeSuv8ehIhVvFlW65am25bVsOy1JMpKXkOIdaxjvY1gYAIAxRKAEAAOAVlpMAAADgFQolAAAAvEKhBAAAgFcolAAAAPAKhRIAAABeoVACAADAKxRKAAAAeIVCCQAAAK9QKAEAAOAVCiUAAAC8QqEEAACAVyiUAAAA8AqFEgAAAF6hUAIAAMArFEoAAAB4hUIJAAAAr1AoAQAA4BUKJQAAALxCoQQAAIBXKJQAAADwCoUSAAAAXqFQAgAAwCsUSgAAAHiFQgkAAACvUCgBAADgFQolAAAAvEKhBAAAgFcolAAAAPAKhRIAAABeoVACAADAKxRKAAAAeOX/A8BSGmdJRJYaAAAAAElFTkSuQmCC", + "text/plain": [ + "<Figure size 640x480 with 1 Axes>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "visualize_graph_w(graph_w)" + ] + }, + { + "cell_type": "markdown", + "id": "0eb464fd", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Spanning Trees\n", + "\n", + "> A **Minimum Spanning Tree (MST)** of a graph is a subset of edges that connects all vertices while minimizing the total sum of the edge values.\n", + "\n", + "- If a graph has $N$ vertices, its MST (Minimum Spanning Tree) will have $N-1$ edges.\n", + "\n", + "- A graph can have multiple spanning trees, but the MST is the one with the lowest weight.\n", + "\n", + "- A tree has only one spanning tree: itself.\n", + "\n", + "\n", + "Question: What is the minimum spanning tree of this graph?" + ] + }, + { + "cell_type": "markdown", + "id": "de9abb12", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + " <img src=\"figures/spanning-tree.png\" style=\"height:10cm;\">" + ] + }, + { + "cell_type": "markdown", + "id": "3a8c8f2a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + " <img src=\"figures/spanning-tree-sol-1.png\" style=\"height: 10cm;\">" + ] + }, + { + "cell_type": "markdown", + "id": "95fa19f1", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + " <img src=\"figures/spanning-tree-sol-2.png\" style=\"height: 10cm;\">" + ] + }, + { + "cell_type": "markdown", + "id": "1e7c93c1", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Weighted Graph MST finding: Prim's Algorithm\n", + "\n", + "\n", + "1. Start with an initial tree reduced to a single vertex of the graph.\n", + "\n", + "2. At each iteration, expand the tree by adding the available free vertex with the smallest possible weight.\n", + "\n", + "3. Stop when the tree becomes spanning.\n", + "\n", + "\n", + "Programming Strategy?" + ] + }, + { + "cell_type": "markdown", + "id": "b6aeca7f", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "Greedy" + ] + }, + { + "cell_type": "markdown", + "id": "02e04cbb", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + " <img src=\"figures/prim-kruskal.png\" style=\"height:15cm;\">" + ] + }, + { + "cell_type": "markdown", + "id": "1c710862", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Weighted Graph MST finding: Prim's Algorithm" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "30da5bfd", + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "from heapq import heapify, heappop, heappush\n", + "\n", + "def prim(graph):\n", + " mst = []\n", + " start_vertex = list(graph.keys())[0]\n", + " priority_queue = [(0, start_vertex)]\n", + " visited = set()\n", + " \n", + " while priority_queue:\n", + " weight, current_vertex = heappop(priority_queue)\n", + " if current_vertex not in visited:\n", + " mst.append((current_vertex, weight))\n", + " visited.add(current_vertex)\n", + " for neighbor, edge_weight in graph[current_vertex]:\n", + " if neighbor not in visited:\n", + " heappush(priority_queue, (edge_weight, neighbor))\n", + " return mst" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "bc4f1d67", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('a', 0), ('d', 1), ('c', 1), ('b', 1), ('e', 1)]" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "prim(graph_w)" + ] + }, + { + "cell_type": "markdown", + "id": "3589712a", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graphs: shortest paths\n", + "\n", + "What is the shortest path from $s \\rightarrow z$? \n", + "\n", + "<img src=\"figures/bellman-solo.png\" style=\"height:5cm;\">\n", + "\n", + "Approaches:\n", + "\n", + "1. **BFS with local minimum (greedy):**\n", + "2. **BFS with global minimum (dynamic programming):**\n", + "3. Other?" + ] + }, + { + "cell_type": "markdown", + "id": "b43e0f7b", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graphs: shortest paths (BFS)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "bbd17edb", + "metadata": {}, + "outputs": [], + "source": [ + "graph_s = {\n", + " \"s\": [(\"t\", 6), (\"y\", 7)],\n", + " \"t\": [(\"x\", 5), (\"y\", 8), (\"z\", -4)],\n", + " \"y\": [(\"x\", -3), (\"z\", 9)],\n", + " \"x\": [(\"t\", -2)],\n", + " \"z\": [(\"s\", 2), (\"x\", 7)]\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "2c7eaade", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "def bfs_path(graph, start, end):\n", + " if start == end:\n", + " return [start]\n", + "\n", + " visited = set()\n", + " queue = [(start, [], 0)]\n", + "\n", + " while queue:\n", + " queue.sort(key=lambda x: x[2])\n", + " current, path, cost = queue.pop(0)\n", + " visited.add(current)\n", + "\n", + " for neighbor, edge_cost in graph[current]:\n", + " if neighbor not in visited:\n", + " if neighbor == end:\n", + " return path + [current, neighbor]\n", + " queue.append((neighbor, path + [current], cost + edge_cost))\n", + "\n", + " return None\n" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "70e22a2d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Path from s to z : ['s', 't', 'z']\n" + ] + } + ], + "source": [ + "start_node = 's'\n", + "end_node = 'z'\n", + "\n", + "path = bfs_path(graph_s, start_node, end_node)\n", + "if path:\n", + " print(\"Path from\", start_node, \"to\", end_node, \":\", path)\n", + "else:\n", + " print(\"No path found from\", start_node, \"to\", end_node)" + ] + }, + { + "cell_type": "markdown", + "id": "73bd4b61", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Graphs: shortest paths (Bellman-Ford)\n", + "\n", + "- **Objective**: Determine the shortest paths from a single source to all other nodes in the graph.\n", + " \n", + "- **Initialization**: Assign an initial distance value of 0 to the source node and infinity to all other nodes.\n", + " \n", + "- **Iterative Relaxation of Edges**:\n", + "\n", + " - Perform $|V| - 1$ iterations ($V$ being the number of vertices).\n", + " \n", + " - For each edge $(u, v)$, update the distance if the distance to node $v$ through node $u$ is shorter than the current distance to $v$.\n", + " \n", + "- **Detection of Negative Cycles**:\n", + "\n", + " - After the $|V| - 1$ iterations, check for negative cycles by iterating through all edges.\n", + " \n", + " - If a shorter path is found, a negative cycle exists.\n" + ] + }, + { + "cell_type": "markdown", + "id": "185e01a2", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "<img src=\"figures/bellman-full.png\" style=\"height:10cm;\">" + ] + }, + { + "cell_type": "markdown", + "id": "45225f32", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + " <img src=\"figures/bellman-algo.png\" style=\"height:10cm;\">" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "c347393e", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "def bellman_ford(graph, src):\n", + " dist = {node: float(\"inf\") for node in graph}\n", + " dist[src] = 0\n", + "\n", + " for _ in range(len(graph) - 1):\n", + " for u in graph:\n", + " for v, w in graph[u]:\n", + " if dist[u] != float(\"inf\") and dist[u] + w < dist[v]:\n", + " dist[v] = dist[u] + w\n", + "\n", + " for u in graph:\n", + " for v, w in graph[u]:\n", + " if dist[u] != float(\"inf\") and dist[u] + w < dist[v]:\n", + " print(\"Le graphe contient des cycles négatifs\")\n", + " return\n", + "\n", + " return dist" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "64704f08", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'s': 0, 't': 2, 'y': 7, 'x': 4, 'z': -2}" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bellman_ford(graph_s, 's')" + ] + }, + { + "cell_type": "markdown", + "id": "94475748", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Dijkstra's Algorithm \n", + "\n", + "- **Objective:** Determine the shortest paths between sources $S$ and nodes in the graph accessible from $S$.\n", + "\n", + "- Incremental and greedy construction of a set of visited nodes $E$ accessible from initial vertex $S$.\n", + "\n", + "- **Initialization:** $E_{0}$ is an empty list and $G = \\{S\\}$.\n", + "\n", + "- Move to the next step:\n", + "\n", + " - $E_{i+1} = E_{i} \\cup \\{ $ node from $G$ outside of $E_{i}$ closest to $S$ by following a path that only passes through nodes in $E_{i} \\}$.\n", + "\n", + "- The vertices entering $E$ in ascending order of distance to $S$.\n", + "\n", + "\n", + "Warning: assumes costs $> 0$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "949dbfe2", + "metadata": {}, + "outputs": [], + "source": [ + "graph_d = {\n", + " \"s\": [(\"t\", 6), (\"y\", 4)],\n", + " \"t\": [(\"x\", 3), (\"y\", 2)],\n", + " \"y\": [(\"t\", 1), (\"x\", 9), (\"z\", 3)],\n", + " \"x\": [(\"z\", 4)],\n", + " \"z\": [(\"s\", 7), (\"x\", 5)]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "ceca3bde", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + " <img src=\"figures/dijkstra-algo.png\" style=\"height:10cm;\">" + ] + }, + { + "cell_type": "markdown", + "id": "6d612ac9", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + " <img src=\"figures/dijkstra-full.png\" style=\"height:10cm;\">" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "2770567c", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "def dijkstra(graph, initial):\n", + " visited = {initial: 0}\n", + " path = {}\n", + " nodes = set(graph.keys())\n", + " while nodes:\n", + " min_node = None\n", + " for node in nodes:\n", + " if node in visited:\n", + " if min_node is None:\n", + " min_node = node\n", + " elif visited[node] < visited[min_node]:\n", + " min_node = node\n", + "\n", + " if min_node is None:\n", + " break\n", + "\n", + " nodes.remove(min_node)\n", + " current_weight = visited[min_node]\n", + " for edge, weight in graph[min_node]:\n", + " weight = current_weight + weight\n", + " if edge not in visited or weight < visited[edge]:\n", + " visited[edge] = weight\n", + " path[edge] = min_node\n", + "\n", + " return visited, path" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "7b04d1dc", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "({'s': 0, 't': 5, 'y': 4, 'x': 8, 'z': 7},\n", + " {'t': 'y', 'y': 's', 'x': 't', 'z': 'y'})" + ] + }, + "execution_count": 46, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dijkstra(graph_d, 's')" + ] + }, + { + "cell_type": "markdown", + "id": "82240937", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Summary of shortest path finding\n", + "\n", + "- Principle of minimizing a cost (optimal sub-problem)\n", + "- Principle of algorithms (Bellman-Ford, Dijkstra, Floyd-Warshall) is to overestimate the weights of the vertices and adjust the cost using a *relaxation* method.\n", + "- The Bellman-Ford algorithm is similar to Dijkstra's. We find the notion of relaxation: $d(j) \\rightarrow \\min(d(j), d(x) + G(x, j))$.\n", + "- Dijkstra does not tolerate negative costs and uses a priority queue to process edges in the correct order and relax each edge only once.\n", + "- Bellman-Ford processes edges in an arbitrary order. It tolerates negative costs. For these reasons, multiple iterations might be necessary.\n", + "- Dijkstra with a cost graph of $1$ resembles breadth-first search (the queue becomes a stack).\n", + "\n" + ] + } + ], + "metadata": { + "celltoolbar": "Slideshow", + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/figures/bellman-algo.png b/notebooks/figures/bellman-algo.png new file mode 100755 index 0000000000000000000000000000000000000000..762b830dab15c89cfbee4752b34bf3bb8e7f5d98 Binary files /dev/null and b/notebooks/figures/bellman-algo.png differ diff --git a/notebooks/figures/bellman-full.png b/notebooks/figures/bellman-full.png new file mode 100755 index 0000000000000000000000000000000000000000..6497fef7c849a6a8a0f36c87297f82242000cb27 Binary files /dev/null and b/notebooks/figures/bellman-full.png differ diff --git a/notebooks/figures/bellman-solo.png b/notebooks/figures/bellman-solo.png new file mode 100755 index 0000000000000000000000000000000000000000..70e9cc4ccaecba740b2e6968d5e6758f070fcd28 Binary files /dev/null and b/notebooks/figures/bellman-solo.png differ diff --git a/notebooks/figures/dijkstra-algo.png b/notebooks/figures/dijkstra-algo.png new file mode 100755 index 0000000000000000000000000000000000000000..cf8fd81a902d4ec2a9ad3ba0ced4b1354353bab1 Binary files /dev/null and b/notebooks/figures/dijkstra-algo.png differ diff --git a/notebooks/figures/dijkstra-full.png b/notebooks/figures/dijkstra-full.png new file mode 100755 index 0000000000000000000000000000000000000000..79e2a8bad7f8c121bec9151f1d13a0ac7c8f4acd Binary files /dev/null and b/notebooks/figures/dijkstra-full.png differ diff --git a/notebooks/figures/placeholder-h.png b/notebooks/figures/placeholder-h.png new file mode 100644 index 0000000000000000000000000000000000000000..545b93294eb8eb6121601d98c4ecaa52d0850459 Binary files /dev/null and b/notebooks/figures/placeholder-h.png differ diff --git a/notebooks/figures/placeholder.png b/notebooks/figures/placeholder.png index 2e3fd26830cccc32005da06088d7e79444aad707..778fa225d7776bb94391207309bf96a1f28281bc 100644 Binary files a/notebooks/figures/placeholder.png and b/notebooks/figures/placeholder.png differ diff --git a/notebooks/figures/prim-kruskal.png b/notebooks/figures/prim-kruskal.png new file mode 100755 index 0000000000000000000000000000000000000000..04d50314930a8602f165cc219687d0b2cec54afb Binary files /dev/null and b/notebooks/figures/prim-kruskal.png differ diff --git a/notebooks/figures/spanning-tree-sol-1.png b/notebooks/figures/spanning-tree-sol-1.png new file mode 100755 index 0000000000000000000000000000000000000000..2e9a6899c50ef20aee7b54cf6524add4c6f22f46 Binary files /dev/null and b/notebooks/figures/spanning-tree-sol-1.png differ diff --git a/notebooks/figures/spanning-tree-sol-2.png b/notebooks/figures/spanning-tree-sol-2.png new file mode 100755 index 0000000000000000000000000000000000000000..a35789a5a758a38e036c67193bcc6cabe6051feb Binary files /dev/null and b/notebooks/figures/spanning-tree-sol-2.png differ diff --git a/notebooks/figures/spanning-tree.png b/notebooks/figures/spanning-tree.png new file mode 100755 index 0000000000000000000000000000000000000000..576ca95d422767d94e049dfdb0923dfc41301860 Binary files /dev/null and b/notebooks/figures/spanning-tree.png differ