diff --git a/labs-solutions/06-dynamic-programming-exercises.ipynb b/labs-solutions/06-dynamic-programming-exercises.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..1403bba16ad12aaebdbcb0536977ca65bf3f68e4 --- /dev/null +++ b/labs-solutions/06-dynamic-programming-exercises.ipynb @@ -0,0 +1,696 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a701f259-d48a-43a8-aa7d-6f77c3896e5e", + "metadata": {}, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "# Lab 6: Programming strategies: Dynamic programming" + ] + }, + { + "cell_type": "markdown", + "id": "6c24ca95-9c26-427c-ac1c-0a5f185140e9", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "cd8663e3-c5a5-41ec-b888-49bac1e82ef4", + "metadata": {}, + "source": [ + "<details style=\"border: 1px\">\n", + "<summary> How to use those notebooks</summary>\n", + " \n", + "For each of the following questions:\n", + "- In the `# YOUR CODE HERE` cell, remove `raise NotImplementedError()` to write your code\n", + "- Write an example of use of your code or make sure the given examples and tests pass\n", + "- Add extra tests in the `#Tests` cell\n", + " \n", + "</details>" + ] + }, + { + "cell_type": "markdown", + "id": "d2673591-dbe2-45f2-b24d-f21ae1186770", + "metadata": {}, + "source": [ + "## Exercise 1: Fibonacci\n", + "\n", + "Write the Fibonacci sequence $T(n) = T(n-1) + T(n-2)$ using dynamic programming, in particular the _memoization_ mechanism." + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "id": "a0ca630b-5a69-4e32-8e89-91afaec02c3e", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-c248cd22cc3ba06b", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def fibonacci(n, memo = {}):\n", + " ### BEGIN SOLUTION\n", + " if n == 0:\n", + " return 0\n", + " elif n == 1:\n", + " return 1\n", + " \n", + " if n in memo:\n", + " return memo[n]\n", + " \n", + " memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)\n", + " return memo[n]\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "id": "c7baa400-bd44-4e35-a3cc-1d44e1711af5", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "55" + ] + }, + "execution_count": 117, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Example of use\n", + "fibonacci(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "3b629a14-7c08-49e1-8cce-8ad320066eec", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Tests\n", + "assert fibonacci(10) == 55" + ] + }, + { + "cell_type": "markdown", + "id": "a45c7015-d59b-4ab3-96f7-d5eaf26273fc", + "metadata": {}, + "source": [ + "## Exercise 2: Knapsack\n", + "\n", + "Propose a dynamic programming solution for the knapsack problem (as defined in the previous lab). Here is the general case (where the weights availability is 1, which means there can be any duplicate in the solution):\n", + "\n", + "$\n", + "dp[i][w] = \n", + "\\begin{cases}\n", + "0 & \\text{if } i = 0 \\text{ or } w = 0 \\\\\n", + "dp[i-1][w] & \\text{if } w_i > w \\\\\n", + "\\max(dp[i-1][w], w_i + dp[i-1][w - w_i]) & \\text{otherwise}\n", + "\\end{cases}$\n", + "\n", + "Your algorithm should return the weight the solution managed to reach, and the list of weights." + ] + }, + { + "cell_type": "code", + "execution_count": 172, + "id": "e77956b0-b286-4347-858d-81468f6a8017", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f61939dd2c0306bd", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def dynamic_knapsack(W, wt):\n", + " ### BEGIN SOLUTION\n", + " n = len(wt)\n", + "\n", + " dp = [[0 for _ in range(W + 1)] for _ in range(n + 1)]\n", + "\n", + " for i in range(n + 1):\n", + " for w in range(W + 1):\n", + " if i == 0 or w == 0:\n", + " dp[i][w] = 0\n", + " elif wt[i - 1] <= w:\n", + " dp[i][w] = max(wt[i - 1] + dp[i - 1][w - wt[i - 1]], dp[i - 1][w])\n", + " else:\n", + " dp[i][w] = dp[i - 1][w]\n", + "\n", + " max_weight = dp[n][W]\n", + "\n", + " selected_items = []\n", + " w = W\n", + " for i in range(n, 0, -1):\n", + " if dp[i][w] != dp[i - 1][w]:\n", + " selected_items.append(wt[i - 1])\n", + " w -= wt[i - 1]\n", + "\n", + " return dp\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebd9a558-c099-4557-bde8-994b4d1e569a", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 173, + "id": "d92f7880-6a1d-4b43-a37e-71ae9d92bb6d", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5\n", + "4\n", + "2\n" + ] + }, + { + "data": { + "text/plain": [ + "[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", + " [0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],\n", + " [0, 0, 2, 3, 3, 5, 5, 5, 5, 5, 5, 5],\n", + " [0, 0, 2, 3, 4, 5, 6, 7, 7, 9, 9, 9],\n", + " [0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]]" + ] + }, + "execution_count": 173, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "weights = [2, 3, 4, 5]\n", + "max_weight = 11\n", + "dynamic_knapsack(max_weight, weights)" + ] + }, + { + "cell_type": "markdown", + "id": "d27f1afa-da6d-45dd-81c9-c99a8cdf1855", + "metadata": {}, + "source": [ + "Compare to the greedy version:" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "3c67509f-a10d-4452-9637-1592d6a3aea1", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def greedy_knapsack(W, w):\n", + "\n", + " w.sort(reverse=True)\n", + " n = len(w)\n", + " remaining_items = list(range(n))\n", + "\n", + " total_weight = 0\n", + " knapsack = []\n", + "\n", + " while remaining_items and total_weight < W:\n", + " best_weight = float('inf')\n", + " best_item = None\n", + "\n", + " for i in remaining_items:\n", + " if w[i] < best_weight:\n", + " best_weight = w[i]\n", + " best_item = i\n", + "\n", + " if best_item is not None:\n", + " weight = w[best_item]\n", + " knapsack.append(weight)\n", + " total_weight += weight\n", + " remaining_items.remove(best_item)\n", + "\n", + " return total_weight, knapsack" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "a12fc960-a59e-410d-976e-9c65f822cedc", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert greedy_knapsack(5, [2, 3, 4, 5]) == (5, [2, 3])" + ] + }, + { + "cell_type": "markdown", + "id": "de7cf7d9-6a75-49e4-a143-84c43174d68e", + "metadata": {}, + "source": [ + "## Exercise 3: Longest increasing subsequence\n", + "\n", + "Given an array of integers, write a function to find the length of the longest increasing subsequence. For intance, given the sequence `[10, 9, 2, 5, 3, 7, 101, 18]` the result is `[2, 3, 7, 101]` (size `4`). Note that the increasing numbers are not necessarily immediately consecutive." + ] + }, + { + "cell_type": "markdown", + "id": "341924a7-5e56-4ead-8fb0-921cb7edf935", + "metadata": {}, + "source": [ + "Write a dynamic programming approach by following those steps:\n", + "\n", + "1. Create a `dp` array of size \\(n\\), where \\(dp[i]\\) represents the length of the longest incresing sequence ending at index \\(i\\)\n", + "2. Initialize all values in `dp` to 1, since the longest incresing sequence at each index is at least the element itself\n", + "3. For each pair of indices \\(i\\) and \\(j\\) where \\(j < i\\):\n", + " - If \\(nums[j] < nums[i]\\), update \\(dp[i] = max(dp[i], dp[j] + 1)\\)\n", + "4. The length of the longest increasing sequence is the maximum value in the `dp` array" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "e09b072d-78f3-4ce1-9203-c85cf0e464ae", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1b6f016aceaf66f0", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def longest_dynprog(L):\n", + " ### BEGIN SOLUTION \n", + " n = len(L)\n", + " if n == 0:\n", + " return 0 \n", + "\n", + " dp = [1] * n\n", + "\n", + " for i in range(1, n):\n", + " for j in range(i):\n", + " if nums[j] < L[i]:\n", + " dp[i] = max(dp[i], dp[j] + 1)\n", + "\n", + " return max(dp)\n", + " ### END SOLUTION " + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "8d29b1e7-d541-4363-94cf-e928944d019f", + "metadata": {}, + "outputs": [], + "source": [ + "L = [10, 9, 2, 5, 3, 7, 101, 18]\n", + "assert longest_dynprog(L) == 4" + ] + }, + { + "cell_type": "markdown", + "id": "358e492b-9a9b-499f-a49b-6a90c530b0fa", + "metadata": { + "tags": [] + }, + "source": [ + "Finally, you may implement a more efficient solution using binary search like explained [in this book](https://cp-algorithms.com/sequences/longest_increasing_subsequence.html).\n", + "\n", + "First write a binary search function:" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "id": "64570eb7-a5e6-4282-9b41-730227337658", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-aae9cefd2fa1258a", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def binary_search(sub, target):\n", + " ### BEGIN SOLUTION \n", + " left, right = 0, len(sub) - 1\n", + " while left <= right:\n", + " mid = (left + right) // 2\n", + " if sub[mid] < target:\n", + " left = mid + 1\n", + " else:\n", + " right = mid - 1\n", + " return left\n", + " ### END SOLUTION " + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "id": "dcd76d9f-2f4d-40fb-8ca6-0ce3acc40070", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert binary_search([1, 2, 3], 2) == [1, 2, 3].index(2)" + ] + }, + { + "cell_type": "markdown", + "id": "4165d800-4a37-4d8c-99f0-f55f995c747f", + "metadata": {}, + "source": [ + "Now write a function that uses the above binary search function to return the longest sequence:" + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "418a8882-8d04-4ef6-9718-307a4056d800", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1dea7220610c9dcb", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def longest_binary(L):\n", + " ### BEGIN SOLUTION \n", + " sub = []\n", + "\n", + " for num in L:\n", + " pos = binary_search(sub, num)\n", + " if pos == len(sub):\n", + " sub.append(num)\n", + " else:\n", + " sub[pos] = num\n", + "\n", + " return len(sub)\n", + " ### END SOLUTION " + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "id": "b6b27c2b-5758-496c-975b-a732e71d1791", + "metadata": {}, + "outputs": [], + "source": [ + "L = [10, 9, 2, 5, 3, 7, 101, 18]\n", + "assert longest_binary(nums) == 4\n" + ] + }, + { + "cell_type": "markdown", + "id": "46eedf29-dac1-4373-9f2c-98275cd749b2", + "metadata": {}, + "source": [ + "## Exercise 4: Find a target sum\n", + "\n", + "You are given a list of integers `L` and a target sum `t`. You need to return the number of different ways you can assign a + or - sign to each number in the list such that the sum equals target.\n", + "\n", + "\n", + "```\n", + "L = [1, 1, 1, 1, 1]\n", + "t = 3\n", + "```\n", + "\n", + "There are 5 possible solutions (`[1, 1, -1, 1, 1]`, etc)." + ] + }, + { + "cell_type": "markdown", + "id": "89555ffc-d9b1-4386-a278-4a583dca2bdc", + "metadata": { + "tags": [] + }, + "source": [ + "Generate a brute force approach like in the previous lab by using the `product` method." + ] + }, + { + "cell_type": "code", + "execution_count": 226, + "id": "7f98f9bb-d1c5-46c7-9ce8-6ccee5087e69", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-d4038eeafd2e1851", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from itertools import product\n", + "\n", + "def find_target_brut(L, target):\n", + " ### BEGIN SOLUTION\n", + " n = len(L)\n", + " total_ways = 0\n", + " \n", + " for signs in product([1, -1], repeat=n):\n", + " current_sum = sum(sign * num for sign, num in zip(signs, L))\n", + " \n", + " if current_sum == target:\n", + " total_ways += 1\n", + " \n", + " return total_ways\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 227, + "id": "efd683c1-1be1-4bf3-a424-f59cbfdd1160", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "L = [1, 1, 1, 1, 1]\n", + "t = 3\n", + "assert find_target_brut([1, 1, 1, 1, 1], t) == 5" + ] + }, + { + "cell_type": "markdown", + "id": "ee303057-a1c2-47cd-a9b3-8f11747e1b3e", + "metadata": {}, + "source": [ + "\n", + "Now write a dynamic programming solution by following the instructions below:\n", + "\n", + "Before proceeding, check for two conditions that would make it impossible to achieve the target:\n", + "\n", + "1. If the total sum \\( s \\) is less than the target, it's impossible to form a subset equal to the target\n", + "2. If \\( s - target \\) is odd, you cannot split the array into two subsets with integer sums that have the required difference\n", + "\n", + "Next, initialize a dynamic programming (dp) array of size \\( n + 1 \\) with all values set to zero, except for `dp[0]`, which is set to 1. This means there is one way to achieve a sum of zero (by choosing no number).\n", + "\n", + "Then, iterate through each number \\( v \\) in `L`. For each number, iterate backward through the dp array from \\( n \\) down to \\( v \\) to avoid recomputing values that rely on the current index. This ensures that we don't count the same combination more than once.\n", + "\n", + "Update the dp array by adding the number of ways to achieve a sum of \\( j \\) without including the current number \\( v \\) (i.e., `dp[j - v]`). This accumulates the number of ways to form a sum of \\( j \\) by either including or excluding the current number \\( v \\)." + ] + }, + { + "cell_type": "code", + "execution_count": 197, + "id": "49c42188-c753-46dc-a8c7-8de377b9014b", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9eacab97fa9220e4", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def find_target_dynprog(L, target):\n", + " ### BEGIN SOLUTION\n", + " sum_total = sum(L)\n", + "\n", + " if (target + sum_total) % 2 != 0 or target + sum_total < 0: # base cases\n", + " return 0\n", + " \n", + " P = (target + sum_total) // 2\n", + " \n", + " dp = [0] * (P + 1)\n", + " dp[0] = 1 # 1 way to sum 0\n", + " \n", + " for num in L:\n", + " for i in range(P, num - 1, -1):\n", + " dp[i] += dp[i - num]\n", + "\n", + " return dp[P]\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 198, + "id": "c1585467-2783-460c-b6ac-0cf2252112b8", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 198, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Example usage\n", + "find_target_dynprog([1, 1, 1, 1, 1], t) # 5" + ] + }, + { + "cell_type": "code", + "execution_count": 199, + "id": "d60099d5-335d-494b-bb8a-3cbada63bf94", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Tests\n", + "L = [1, 1, 1, 1, 1]\n", + "t = 3\n", + "assert find_target_dynprog([1, 1, 1, 1, 1], t) == 5" + ] + }, + { + "cell_type": "code", + "execution_count": 232, + "id": "500075c4-8084-41c3-aa1d-b98a7137c1e3", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-35697bfe2f8f6772", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def find_target_dynprog_rec(nums, target, index = 0, current_sum = 0, memo = {}):\n", + " ### BEGIN SOLUTION\n", + " if (index, current_sum) in memo:\n", + " return memo[(index, current_sum)]\n", + "\n", + " if index == len(nums):\n", + " return 1 if current_sum == target else 0\n", + "\n", + " add_ways = find_target_dynprog_rec(nums, target, index + 1, current_sum + nums[index], memo)\n", + " subtract_ways = find_target_dynprog_rec(nums, target, index + 1, current_sum - nums[index], memo)\n", + "\n", + " memo[(index, current_sum)] = add_ways + subtract_ways\n", + " \n", + " return memo[(index, current_sum)]\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 233, + "id": "96c3f7a7-4b5e-4ce1-8b3d-444f0045f464", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert find_target_dynprog_rec(L, t) == 5" + ] + } + ], + "metadata": { + "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/labs-solutions/07-binary-trees-exercises.ipynb b/labs-solutions/07-binary-trees-exercises.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ced9334c4ecb9948e6093d1490c6cfce17174a3f --- /dev/null +++ b/labs-solutions/07-binary-trees-exercises.ipynb @@ -0,0 +1,1284 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8985c402-e154-4eab-aea3-750a88318acb", + "metadata": {}, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "# Lab 7: Binary trees\n", + "\n", + "\n", + "**IMPORTANT:** make sure the graphviz library (to visualize graphs) runs in your notebooks, this can be achieved by running the following cells (if graphviz is not install run the `!pip install graphviz` command in a cell). If graphviz ultimatelly does not, then you may skip the cell with the `visualize_oop` functions." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "1f8b2afd-8315-41fa-b5d7-7f9566332151", + "metadata": { + "tags": [] + }, + "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 12.1.2 (20240928.0832)\n", + " -->\n", + "<!-- Pages: 1 -->\n", + "<svg width=\"62pt\" height=\"116pt\"\n", + " viewBox=\"0.00 0.00 62.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 58,-112 58,4 -4,4\"/>\n", + "<!-- 3 -->\n", + "<g id=\"node1\" class=\"node\">\n", + "<title>3</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"27\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">3</text>\n", + "</g>\n", + "<!-- 2 -->\n", + "<g id=\"node2\" class=\"node\">\n", + "<title>2</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"27\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">2</text>\n", + "</g>\n", + "<!-- 3->2 -->\n", + "<g id=\"edge1\" class=\"edge\">\n", + "<title>3->2</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M27,-71.7C27,-64.41 27,-55.73 27,-47.54\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"30.5,-47.62 27,-37.62 23.5,-47.62 30.5,-47.62\"/>\n", + "</g>\n", + "</g>\n", + "</svg>\n" + ], + "text/plain": [ + "<graphviz.graphs.Digraph at 0x1075f9ea0>" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from graphviz import Digraph\n", + "from IPython.display import display\n", + "\n", + "class Node:\n", + " def __init__(self, value, l = None, r = None):\n", + " self.value = value\n", + " self.left = l\n", + " self.right = r\n", + "\n", + "def visualize_oop(root):\n", + " def build(node, dot=None):\n", + " if dot is None:\n", + " dot = Digraph(format='png')\n", + "\n", + " if node is not None:\n", + " dot.node(str(node.value))\n", + "\n", + " if node.left is not None:\n", + " dot.edge(str(node.value), str(node.left.value))\n", + " build(node.left, dot)\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", + " return build(root)\n", + "\n", + "\n", + "visualize_oop(Node(3, Node(2)))" + ] + }, + { + "cell_type": "markdown", + "id": "7c7461c1-fe15-405b-a2c9-709e6f9c3743", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "a2428808-27d5-4d42-84b8-01632b1271dd", + "metadata": { + "tags": [] + }, + "source": [ + "## Exercise 1: Manipulate binary trees data structure\n", + "\n", + "Here is an example of tree (using the graphviz library as data structure). Look at the result." + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "83b91119-09f7-4cba-a87b-19fc4a793e91", + "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 12.1.2 (20240928.0832)\n", + " -->\n", + "<!-- Pages: 1 -->\n", + "<svg width=\"152pt\" height=\"188pt\"\n", + " viewBox=\"0.00 0.00 152.00 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 148,-184 148,4 -4,4\"/>\n", + "<!-- 0 -->\n", + "<g id=\"node1\" class=\"node\">\n", + "<title>0</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-162\" rx=\"18\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-156.95\" font-family=\"Times,serif\" font-size=\"14.00\">0</text>\n", + "</g>\n", + "<!-- 1 -->\n", + "<g id=\"node2\" class=\"node\">\n", + "<title>1</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"72\" cy=\"-90\" rx=\"18\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"72\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">1</text>\n", + "</g>\n", + "<!-- 0->1 -->\n", + "<g id=\"edge1\" class=\"edge\">\n", + "<title>0->1</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M92.74,-144.76C89.64,-136.72 85.81,-126.81 82.3,-117.69\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"85.64,-116.63 78.77,-108.56 79.11,-119.15 85.64,-116.63\"/>\n", + "</g>\n", + "<!-- 2 -->\n", + "<g id=\"node3\" class=\"node\">\n", + "<title>2</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"126\" cy=\"-90\" rx=\"18\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"126\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">2</text>\n", + "</g>\n", + "<!-- 0->2 -->\n", + "<g id=\"edge4\" class=\"edge\">\n", + "<title>0->2</title>\n", + "<path fill=\"none\" stroke=\"red\" d=\"M105.26,-144.76C108.36,-136.72 112.19,-126.81 115.7,-117.69\"/>\n", + "<polygon fill=\"red\" stroke=\"red\" points=\"118.89,-119.15 119.23,-108.56 112.36,-116.63 118.89,-119.15\"/>\n", + "</g>\n", + "<!-- 4 -->\n", + "<g id=\"node5\" class=\"node\">\n", + "<title>4</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"18\" cy=\"-18\" rx=\"18\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"18\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">4</text>\n", + "</g>\n", + "<!-- 1->4 -->\n", + "<g id=\"edge2\" class=\"edge\">\n", + "<title>1->4</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M61.33,-75.17C54.01,-65.68 44.12,-52.86 35.64,-41.86\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"38.45,-39.78 29.57,-34 32.91,-44.06 38.45,-39.78\"/>\n", + "</g>\n", + "<!-- 5 -->\n", + "<g id=\"node6\" class=\"node\">\n", + "<title>5</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"72\" cy=\"-18\" rx=\"18\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"72\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">5</text>\n", + "</g>\n", + "<!-- 1->5 -->\n", + "<g id=\"edge3\" class=\"edge\">\n", + "<title>1->5</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M72,-71.7C72,-64.41 72,-55.73 72,-47.54\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"75.5,-47.62 72,-37.62 68.5,-47.62 75.5,-47.62\"/>\n", + "</g>\n", + "<!-- 3 -->\n", + "<g id=\"node4\" class=\"node\">\n", + "<title>3</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"126\" cy=\"-18\" rx=\"18\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"126\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">3</text>\n", + "</g>\n", + "<!-- 2->3 -->\n", + "<g id=\"edge5\" class=\"edge\">\n", + "<title>2->3</title>\n", + "<path fill=\"none\" stroke=\"red\" d=\"M126,-71.7C126,-64.41 126,-55.73 126,-47.54\"/>\n", + "<polygon fill=\"red\" stroke=\"red\" points=\"129.5,-47.62 126,-37.62 122.5,-47.62 129.5,-47.62\"/>\n", + "</g>\n", + "</g>\n", + "</svg>\n" + ], + "text/plain": [ + "<graphviz.graphs.Digraph at 0x1075fbf70>" + ] + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from graphviz import Digraph\n", + "\n", + "dot = Digraph()\n", + "\n", + "dot.node_attr['shape'] = 'circle'\n", + "\n", + "dot.node('0', label='0') # Root\n", + "dot.node('1')\n", + "dot.node('2')\n", + "dot.node('3')\n", + "dot.node('4')\n", + "dot.node('5')\n", + "\n", + "dot.edge('0', '1')\n", + "dot.edge('1', '4')\n", + "dot.edge('1', '5')\n", + "\n", + "dot.edge('0', '2', color='red')\n", + "dot.edge('2', '3', color='red')\n", + "\n", + "\n", + "dot # Render the graph" + ] + }, + { + "cell_type": "markdown", + "id": "1fca57f5-4b3c-4ad8-a4c0-9d1de647abf9", + "metadata": { + "tags": [] + }, + "source": [ + "Use a `Dict` data structure to store the nodes and edges of the above tree." + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "id": "fa1bce0f-7b06-4d20-8a1b-990bcd03bda9", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0f290aab7c180fb7", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "T_dict = {\n", + "### BEGIN CODE\n", + " '0': ['1', '2'],\n", + " '1': ['4', '5'],\n", + " '2': ['3'],\n", + " '4': [],\n", + " '5': [],\n", + " '3': []\n", + "### END CODE\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "cb16382b-e00d-498a-bab2-0b251f93d147", + "metadata": {}, + "source": [ + "Use a `Tuple` data structure to store the nodes and edges of the above tree." + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "id": "a4cbc1d4-5adc-4cda-9c2a-24101bd1bed9", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-ce11e1828bd74e71", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "T_tuple = (\n", + " ### BEGIN CODE\n", + " ('0', ('1', '2')),\n", + " ('1', ('4', '5')),\n", + " ('2', ('3',)),\n", + " ('4', ()),\n", + " ('5', ()),\n", + " ('3', ())\n", + " ### END CODE\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "483af12c-f194-4377-a0f4-56f860b6d865", + "metadata": {}, + "source": [ + "Use a `List` data structure to store the nodes and edges of the above tree." + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "id": "54f96c40-40f1-4838-9861-3f08bd828503", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-2ee2e0e27be77e41", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "T_list = [\n", + " ### BEGIN CODE\n", + " ['0', ['1', '2']],\n", + " ['1', ['4', '5']],\n", + " ['2', ['3']],\n", + " ['4', []],\n", + " ['5', []],\n", + " ['3', []]\n", + " ### END CODE\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "5554f67b-ba9d-4143-8082-382fd43caf0b", + "metadata": {}, + "source": [ + "Write code to compare the various data structure and show they store the same information." + ] + }, + { + "cell_type": "code", + "execution_count": 146, + "id": "b2144bf6-00d9-40c6-973e-734fad8aa949", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-1fd6fcebcffa3cbe", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 146, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "### BEGIN CODE\n", + "list(T_dict.keys())[0] == T_list[0][0]\n", + "### END CODE" + ] + }, + { + "cell_type": "markdown", + "id": "bc2627d9-b490-4e47-bb2e-ffe6a33e1e2c", + "metadata": {}, + "source": [ + "Write a function that converts a binary tree stored as a `Dict` in to a `List`." + ] + }, + { + "cell_type": "code", + "execution_count": 193, + "id": "d7976e73-44e0-4c9c-8383-3acb6f4ed284", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-3ce83537aa56f88e", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def dict_to_list(T_dict, root='0'):\n", + "### BEGIN CODE\n", + " result = []\n", + " for node, children in T_dict.items():\n", + " result.append([node, children])\n", + " return result\n", + "### END CODE" + ] + }, + { + "cell_type": "code", + "execution_count": 194, + "id": "cdec902d-c43c-4be2-bafe-78baac70c7ee", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert dict_to_list(T_dict) == T_list" + ] + }, + { + "cell_type": "markdown", + "id": "5fd82aa3-f72e-4d73-876e-701b02165ff8", + "metadata": { + "tags": [] + }, + "source": [ + "Now write a function that converts a `List` to a `Dict`." + ] + }, + { + "cell_type": "code", + "execution_count": 205, + "id": "133cca89-39f7-43c5-8d9f-c0e7c9a89427", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8c05491d13ceb894", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def list_to_dict(T_list):\n", + "### BEGIN CODE\n", + " result = {}\n", + " for node, children in T_list:\n", + " result[node] = children\n", + " return result\n", + "### END CODE" + ] + }, + { + "cell_type": "code", + "execution_count": 206, + "id": "a06bc87a-dd09-4513-8d73-3f949374f158", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert list_to_dict(T_list) == T_dict" + ] + }, + { + "cell_type": "markdown", + "id": "57c4ed49-f64b-4dc9-80e6-2c80269ebe90", + "metadata": {}, + "source": [ + "Provide examples of conversions showing they do not lose any information." + ] + }, + { + "cell_type": "code", + "execution_count": 207, + "id": "16d722bc-625e-4750-96a4-f58aaab467b1", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f4064d762be03ca3", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "### BEGIN CODE\n", + "assert list_to_dict(dict_to_list(T_dict)) == T_dict\n", + "### END CODE" + ] + }, + { + "cell_type": "markdown", + "id": "fb0e9336-603c-49c4-9617-9ccecdbf7156", + "metadata": {}, + "source": [ + "## Exercise 2: Binary search tree (BST)\n", + "\n", + "We assume we have a binary search tree (BST). Its main property is that for every parent node, the left child contains values less than the parent node, and the right child contains values greater than the parent node." + ] + }, + { + "cell_type": "code", + "execution_count": 133, + "id": "02513514-0e1b-4c0d-a5fd-ac6b571231b7", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class Node:\n", + " def __init__(self, value):\n", + " self.value = value\n", + " self.left = None\n", + " self.right = None\n", + " def __str__(self):\n", + " return str(self.value)" + ] + }, + { + "cell_type": "markdown", + "id": "055d2cea-4e45-402d-baf8-e50939c94132", + "metadata": {}, + "source": [ + "Write an `insert` function inserts a value in a tree so it preserves the BST property (in an iterative way)." + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "6492983d-054c-4488-b8ff-57b3878f5b7e", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b636f3646835e810", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def insert_ite(root, value):\n", + " ### BEGIN SOLUTION\n", + " new_node = Node(value)\n", + " if root is None:\n", + " return new_node\n", + "\n", + " current = root\n", + " while True:\n", + " if value < current.value:\n", + " if current.left is None:\n", + " current.left = new_node\n", + " break\n", + " current = current.left\n", + " else:\n", + " if current.right is None:\n", + " current.right = new_node\n", + " break\n", + " current = current.right\n", + " return root\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "7c2a2f74-a8b8-4cd6-a881-9181efdb7a64", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.Node at 0x1075fbb50>" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = Node(2)\n", + "insert_ite(a, 3)\n", + "insert_ite(a, 4)\n", + "insert_ite(a, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 221, + "id": "57d74a5f-ba4d-45be-bb63-6df4380fc671", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "' 2 \\n 1 3 \\n 4'" + ] + }, + "execution_count": 221, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "render_tree_ascii(a)" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "10b6d8de-fb38-41e3-92b9-957f04a6f1e4", + "metadata": { + "tags": [] + }, + "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 12.1.2 (20240928.0832)\n", + " -->\n", + "<!-- Pages: 1 -->\n", + "<svg width=\"134pt\" height=\"188pt\"\n", + " viewBox=\"0.00 0.00 134.00 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 130,-184 130,4 -4,4\"/>\n", + "<!-- 2 -->\n", + "<g id=\"node1\" class=\"node\">\n", + "<title>2</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"63\" cy=\"-162\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"63\" y=\"-156.95\" font-family=\"Times,serif\" font-size=\"14.00\">2</text>\n", + "</g>\n", + "<!-- 1 -->\n", + "<g id=\"node2\" class=\"node\">\n", + "<title>1</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"27\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">1</text>\n", + "</g>\n", + "<!-- 2->1 -->\n", + "<g id=\"edge1\" class=\"edge\">\n", + "<title>2->1</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M54.65,-144.76C50.42,-136.55 45.19,-126.37 40.42,-117.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"43.68,-115.79 36,-108.49 37.46,-118.99 43.68,-115.79\"/>\n", + "</g>\n", + "<!-- 3 -->\n", + "<g id=\"node3\" class=\"node\">\n", + "<title>3</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">3</text>\n", + "</g>\n", + "<!-- 2->3 -->\n", + "<g id=\"edge2\" class=\"edge\">\n", + "<title>2->3</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M71.35,-144.76C75.58,-136.55 80.81,-126.37 85.58,-117.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"88.54,-118.99 90,-108.49 82.32,-115.79 88.54,-118.99\"/>\n", + "</g>\n", + "<!-- 4 -->\n", + "<g id=\"node4\" class=\"node\">\n", + "<title>4</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">4</text>\n", + "</g>\n", + "<!-- 3->4 -->\n", + "<g id=\"edge3\" class=\"edge\">\n", + "<title>3->4</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", + "</g>\n", + "</svg>\n" + ], + "text/plain": [ + "<graphviz.graphs.Digraph at 0x1075fb670>" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "visualize_oop(a)" + ] + }, + { + "cell_type": "markdown", + "id": "0cb5f4e8-348d-4ab0-bdd3-1fe61a6ca687", + "metadata": {}, + "source": [ + "Now in a recursive way." + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "3826d528-370f-4e29-8217-20a71947b39a", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-447db421e8ed5090", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def insert_rec(root, value):\n", + " ### BEGIN SOLUTION\n", + " if root is None:\n", + " return Node(value)\n", + " if value < root.value:\n", + " root.left = insert(root.left, value)\n", + " else:\n", + " root.right = insert(root.right, value)\n", + " return root\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "dba5b7d0-1bb6-4b46-b123-5be447b8e10c", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.Node at 0x1075f8340>" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = Node(2)\n", + "insert(a, 3)\n", + "insert(a, 4)\n", + "insert(a, 1)" + ] + }, + { + "cell_type": "markdown", + "id": "d40d2486-c901-428a-b2e3-0004ae707376", + "metadata": {}, + "source": [ + "Compare the results." + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "069fb592-4178-405b-8d8b-546bc908dc1e", + "metadata": { + "tags": [] + }, + "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 12.1.2 (20240928.0832)\n", + " -->\n", + "<!-- Pages: 1 -->\n", + "<svg width=\"134pt\" height=\"188pt\"\n", + " viewBox=\"0.00 0.00 134.00 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 130,-184 130,4 -4,4\"/>\n", + "<!-- 2 -->\n", + "<g id=\"node1\" class=\"node\">\n", + "<title>2</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"63\" cy=\"-162\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"63\" y=\"-156.95\" font-family=\"Times,serif\" font-size=\"14.00\">2</text>\n", + "</g>\n", + "<!-- 1 -->\n", + "<g id=\"node2\" class=\"node\">\n", + "<title>1</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"27\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">1</text>\n", + "</g>\n", + "<!-- 2->1 -->\n", + "<g id=\"edge1\" class=\"edge\">\n", + "<title>2->1</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M54.65,-144.76C50.42,-136.55 45.19,-126.37 40.42,-117.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"43.68,-115.79 36,-108.49 37.46,-118.99 43.68,-115.79\"/>\n", + "</g>\n", + "<!-- 3 -->\n", + "<g id=\"node3\" class=\"node\">\n", + "<title>3</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">3</text>\n", + "</g>\n", + "<!-- 2->3 -->\n", + "<g id=\"edge2\" class=\"edge\">\n", + "<title>2->3</title>\n", + "<path fill=\"none\" stroke=\"black\" d=\"M71.35,-144.76C75.58,-136.55 80.81,-126.37 85.58,-117.09\"/>\n", + "<polygon fill=\"black\" stroke=\"black\" points=\"88.54,-118.99 90,-108.49 82.32,-115.79 88.54,-118.99\"/>\n", + "</g>\n", + "<!-- 4 -->\n", + "<g id=\"node4\" class=\"node\">\n", + "<title>4</title>\n", + "<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n", + "<text text-anchor=\"middle\" x=\"99\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">4</text>\n", + "</g>\n", + "<!-- 3->4 -->\n", + "<g id=\"edge3\" class=\"edge\">\n", + "<title>3->4</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", + "</g>\n", + "</svg>\n" + ], + "text/plain": [ + "<graphviz.graphs.Digraph at 0x1075faec0>" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "visualize_oop(a)" + ] + }, + { + "cell_type": "markdown", + "id": "1608511b-df49-43fd-a946-606cf6bb57a4", + "metadata": {}, + "source": [ + "Now write a function that search for a given `value` is in the BST. " + ] + }, + { + "cell_type": "code", + "execution_count": 229, + "id": "bb343233-16f8-4441-8e18-9407611d8d10", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-4569f50d2fd946d3", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def search(root, value):\n", + " ### BEGIN SOLUTION\n", + " if root is None or root.value == value:\n", + " return root\n", + " \n", + " if value < root.value:\n", + " return search(root.left, value)\n", + " \n", + " return search(root.right, value)\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 232, + "id": "05aaa6a3-a57c-47b3-849f-722ee0546ab0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert search(a, 3)\n", + "assert search(a, 4)\n", + "assert not search(a, 5)\n", + "assert search(a, 1)" + ] + }, + { + "cell_type": "markdown", + "id": "07ca3602-96fd-43d7-b7ac-54f92124a11d", + "metadata": { + "tags": [] + }, + "source": [ + "## Exercise 3: Calculate properties of binary trees" + ] + }, + { + "cell_type": "code", + "execution_count": 216, + "id": "2e65ec3d-8e90-4e25-befb-d779713fa3f2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class Node:\n", + " def __init__(self, value, l = None, r = None):\n", + " self.value = value\n", + " self.left = l\n", + " self.right = r" + ] + }, + { + "cell_type": "markdown", + "id": "3bd05d7e-7e0c-4042-9aac-616960849de9", + "metadata": {}, + "source": [ + "Calculate the height of a binary tree in a recursive way:" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "c323b9af-98f6-45d6-9ee2-cffde39a2a16", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-b19dae2393fee8a2", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def height_rec(node):\n", + " ### BEGIN SOLUTION\n", + " if node is None:\n", + " return -1\n", + " left_height = height_rec(node.left)\n", + " right_height = height_rec(node.right)\n", + " return max(left_height, right_height) + 1\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "9527fa59-87cf-4216-a7dc-ad0aa3330d4c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert height_rec(Node(2)) == 0\n", + "assert height_rec(Node(2, Node(3))) == 1\n", + "assert height_rec(Node(2, Node(3, Node(4)))) == 2" + ] + }, + { + "cell_type": "markdown", + "id": "7b46712d-472b-4997-90b4-acda29a17fb9", + "metadata": {}, + "source": [ + "In an interative way:" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "6cd2bdcf-d4ad-4103-88ed-ced3fb45f4e8", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-8cc9bfd56a6fcde6", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def height_ite(root):\n", + " ### BEGIN SOLUTION\n", + " if root is None:\n", + " return -1\n", + "\n", + " level = 0\n", + " current_level = [root]\n", + "\n", + " while current_level:\n", + " next_level = []\n", + " for node in current_level:\n", + " if node.left:\n", + " next_level.append(node.left)\n", + " if node.right:\n", + " next_level.append(node.right)\n", + " current_level = next_level\n", + " level += 1\n", + "\n", + " return level - 1\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "dac217fa-c9db-46ca-91a3-a834c3d0aa38", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert height_ite(Node(2)) == 0\n", + "assert height_ite(Node(2, Node(3))) == 1\n", + "assert height_ite(Node(2, Node(3, Node(4)))) == 2" + ] + }, + { + "cell_type": "markdown", + "id": "dea3b5d5-dc00-4569-9343-1735263939e6", + "metadata": {}, + "source": [ + "A binary tree is considered balanced if the height of the left and right subtrees of any node differ by no more than 1. You may use the previously created `height` function." + ] + }, + { + "cell_type": "code", + "execution_count": 108, + "id": "d15492f9-0f52-4f1c-b782-fced561764d0", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-89fcd7b808be54c9", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def is_balanced_rec(root):\n", + " ### BEGIN SOLUTION\n", + " if root is None:\n", + " return True\n", + "\n", + " left_height = height_rec(root.left)\n", + " right_height = height_rec(root.right)\n", + "\n", + " if abs(left_height - right_height) > 1:\n", + " return False\n", + "\n", + " return is_balanced_rec(root.left) and is_balanced_rec(root.right)\n", + " ### END SOLUTION\n" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "id": "0d7542de-86a9-4e38-ba51-87f55d6f7909", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "assert is_balanced_rec(Node(2))\n", + "assert not is_balanced_rec(Node(2, Node(3, Node(4))))" + ] + }, + { + "cell_type": "markdown", + "id": "6c613015-7ab9-48a9-9c58-a9ee0fcb8c7c", + "metadata": {}, + "source": [ + "## Exercise 4: Implement a (complete) binary tree using a list" + ] + }, + { + "cell_type": "markdown", + "id": "0c98803c-4d6d-4c8a-b496-bcc49fa48232", + "metadata": { + "tags": [] + }, + "source": [ + "We will now implement a **complete binary tree**. This binary tree will be implemented using an array (since it is a **complete** tree where all levels are filled, except possibly the last). The binary tree has nodes with an index \\(i\\), with a left child and a right child. The array and the tree are connected as follows:\n", + "\n", + "- The root is at position $i = 0$ (this value will be returned by the function `get_root`).\n", + "- The parent is at position $\\lfloor (i - 1)/ 2 \\rfloor$ (function `get_parent`).\n", + "- The left child is at position $2 \\times i + 1$ (function `get_left_child`).\n", + "- The right child is at position $2 \\times i + 2$ (function `get_right_child`).\n", + "\n", + "```\n", + " 1\n", + " / \\\n", + " 2 5\n", + " / \\ /\n", + " 3 4 6 \n", + " \n", + "\n", + "The corresponding list:\n", + "[1, 2, 5, 3, 4, 6]\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 129, + "id": "2d989ada-2637-4610-ad82-e450bdb21909", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-adc7011beb829f3b", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "class BinaryTree():\n", + " ### BEGIN SOLUTION\n", + " def __init__(self, values = []):\n", + " self.__values = values\n", + "\n", + " # add a not to the array\n", + " def add(self, value):\n", + " self.__values.append(value)\n", + "\n", + " # return a tuble (valeur, indice) for node i (without suppression)\n", + " def peek(self, i):\n", + " return self.__values[i], i\n", + "\n", + " # return True if empty\n", + " def is_empty(self):\n", + " return len(self.__values) == 0\n", + "\n", + " # return the root (index 0) \n", + " def get_root(self):\n", + " if len(self.__values) > 0:\n", + " return self.__values[0]\n", + " \n", + " def get_parent(self, i = 0):\n", + " if (i-1)//2 < 0:\n", + " return (None, None)\n", + " else:\n", + " return (self.__values[(i-1)//2], (i-1)//2)\n", + " \n", + " # return a tuple (value, index) for left child (index 2 * i + 1)\n", + " def get_left_child(self, i = 0):\n", + " left_i = 2 * i + 1\n", + " if left_i > len(self.__values) - 1:\n", + " return None, left_i\n", + " else:\n", + " return self.__values[left_i], left_i\n", + "\n", + " # return a tuple (value, index) for right child (index 2 * i + 2)\n", + " def get_right_child(self, i = 0):\n", + " right_i = 2 * i + 2\n", + " if right_i > len(self.__values) - 1:\n", + " return None, right_i\n", + " else:\n", + " return self.__values[right_i], right_i\n", + " \n", + " def size(self): \n", + " return len(self.__values)\n", + " \n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 130, + "id": "e9098c68-368f-4716-9208-819bfa7cfe10", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Test empty tree\n", + "tree_empty = BinaryTree()\n", + "assert tree_empty.size() == 0\n", + "assert tree_empty.get_root() == None\n", + "assert tree_empty.get_parent()[0] == None\n", + "assert tree_empty.get_left_child(0)[0] == None \n", + "assert tree_empty.get_right_child(0)[0] == None " + ] + }, + { + "cell_type": "code", + "execution_count": 131, + "id": "c7d0d23b-b039-4015-b650-fbb700d359a8", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "L = [1, 2, 5, 3, 4, 6]\n", + "tree_values = BinaryTree(L)\n", + "assert tree_values.size() == len(L) # 6\n", + "assert tree_values.get_root() == L[0] # 3\n", + "assert tree_values.get_left_child(0)[0] == L[2*0+1] # 2\n", + "assert tree_values.get_right_child(0)[0] == L[2*0+2] # 5\n", + "assert tree_values.get_parent(1)[0] == L[0] # 5" + ] + }, + { + "cell_type": "markdown", + "id": "957d18d6-3c3a-463f-8726-bad3b27a367b", + "metadata": {}, + "source": [ + "**BONUS Questions**\n", + "- Implement the other methods we have seen previously (get height, find values etc.)\n", + "- Compare with other data structures (`Dict`, `Tuple`, ..)" + ] + } + ], + "metadata": { + "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/labs-solutions/08-binary-trees-traversal-exercises.ipynb b/labs-solutions/08-binary-trees-traversal-exercises.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..c6b0a4a6ac175bc71fb636d18752895426135dbc --- /dev/null +++ b/labs-solutions/08-binary-trees-traversal-exercises.ipynb @@ -0,0 +1,773 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e14a9e35-e497-4765-8089-95d0db59f82d", + "metadata": { + "tags": [] + }, + "source": [ + "# UE5 Fundamentals of Algorithms\n", + "# Lab 8: Binary trees traversals" + ] + }, + { + "cell_type": "markdown", + "id": "da448b8a-05e3-4ee6-b3b8-6fa4c41f55b6", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "d3c4bf88-8101-42bb-a14d-9e5607b55d57", + "metadata": {}, + "source": [ + "We will use the following binary tree data structure (unless specified otherwise):" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "2b179632-5d9e-4abd-95c1-abfea9d455df", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "class Node:\n", + " def __init__(self, value):\n", + " self.value = value\n", + " self.left = None\n", + " self.right = None" + ] + }, + { + "cell_type": "markdown", + "id": "1d18a7cb-9003-4d84-a68d-dfbd8202713b", + "metadata": {}, + "source": [ + "## Exercise 1: Check if a binary tree is balanced" + ] + }, + { + "cell_type": "markdown", + "id": "ac3a3e11-4a8d-416c-8b55-3c6a4137a457", + "metadata": {}, + "source": [ + "Write an **iterative** function that checks if a binary tree is balanced, i.e. the difference between the heights of the left and right subtrees is at most 1.\n", + "\n", + "Tips:\n", + "- Use af dfs traversal approach (using a stack) using a post-order approach (left, right, and root node)\n", + "- For each node, calculate and memorize the current height in a dictionnary\n", + "- Check if the current node is balanced, if not return `False`\n", + "- Mark the node as visited and store its height\n", + "- Return `True` if all the nodes are visited (without an un-balanced situation)" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "51a49306-6cb7-4e84-9257-081793657848", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-9be6329f66aa0822", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def is_balanced_ite(root):\n", + " ### BEGIN SOLUTION\n", + " if root is None:\n", + " return True\n", + "\n", + " # stack for post-order traversal\n", + " stack = []\n", + " \n", + " # heights dictionnary\n", + " node_heights = {}\n", + "\n", + " current = root\n", + " last_visited = None\n", + "\n", + " while stack or current:\n", + " if current:\n", + " stack.append(current)\n", + " current = current.left\n", + " else:\n", + " peek_node = stack[-1]\n", + " if peek_node.right and last_visited != peek_node.right:\n", + " current = peek_node.right\n", + " else:\n", + " # heights of left and right children\n", + " left_height = node_heights.get(peek_node.left, -1)\n", + " right_height = node_heights.get(peek_node.right, -1)\n", + "\n", + " # balance for current node\n", + " if abs(left_height - right_height) > 1:\n", + " return False\n", + "\n", + " # height of the current node\n", + " node_heights[peek_node] = max(left_height, right_height) + 1\n", + "\n", + " # mark the node as visited\n", + " last_visited = stack.pop()\n", + "\n", + " return True\n", + " ### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "id": "556f6692-1e4f-4f55-adbc-cf373402673d", + "metadata": {}, + "source": [ + "Compare to the following recursive version:" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "b7fb2e89-b045-4a46-851e-153c64e0de60", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def get_height(node):\n", + " if node is None:\n", + " return -1\n", + " left_height = get_height(node.left)\n", + " right_height = get_height(node.right)\n", + " return max(left_height, right_height) + 1\n", + "\n", + "def is_balanced(root):\n", + " # un abre vide (None) est équilibré\n", + " if root is None: \n", + " return True\n", + " return is_balanced(root.right) and is_balanced(root.left) and abs(get_height(root.left) - get_height(root.right)) <= 1\n", + "\n", + "#get_height(racine)\n", + "#is_balanced(racine)" + ] + }, + { + "cell_type": "markdown", + "id": "00e2ddec-e259-4f8b-a414-a11051672173", + "metadata": {}, + "source": [ + "Write tests with both balanced and unbalanced trees." + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "f90da531-0173-429a-a415-923e7b2bf275", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-62f1e3032591a590", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "### BEGIN SOLUTION\n", + "balanced = Node(1)\n", + "balanced.left = Node(2)\n", + "balanced.right = Node(3)\n", + "balanced.left.left = Node(4)\n", + "balanced.left.right = Node(5)\n", + "assert is_balanced(balanced)\n", + "\n", + "unbalanced = Node(1)\n", + "unbalanced.left = Node(2)\n", + "unbalanced.right = Node(3)\n", + "unbalanced.left.left = Node(4)\n", + "unbalanced.left.right = Node(5)\n", + "unbalanced.right.left = Node(6)\n", + "unbalanced.right.left.left = Node(7)\n", + "assert not is_balanced(unbalanced)\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "id": "09013922-8606-4298-91e5-02da49a8ced2", + "metadata": {}, + "source": [ + "## Exercise 2: DFS traversal orders\n", + "\n", + "Given the following tree, which type of dfs traversal order you may use to return the number in ascending order?\n", + "\n", + "```\n", + " 1\n", + " / \\\n", + " 2 5\n", + " / \\\n", + "3 4\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "afa39767-9b92-4eb7-8a2c-7b59715153da", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def inorder_traversal(root):\n", + " if root is not None:\n", + " inorder_traversal(root.left)\n", + " print(root.value, end=' ')\n", + " inorder_traversal(root.right)" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "e88833c1-3cf0-4751-bf79-d7ba410a3dd2", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def preorder_traversal(root):\n", + " if root is not None:\n", + " print(root.value, end=' ')\n", + " preorder_traversal(root.left)\n", + " preorder_traversal(root.right)" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "id": "fdff0e4c-e6f1-4185-b7dc-92aa3f8f7ccb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def postorder_traversal(root):\n", + " if root is not None:\n", + " postorder_traversal(root.left)\n", + " postorder_traversal(root.right)\n", + " print(root.value, end=' ')" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "78fbbaa9-a66f-4edc-ad6d-35364efcf83a", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-525d556fb7dad98a", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 2 3 4 5 " + ] + } + ], + "source": [ + "### BEGIN SOLUTION\n", + "tree = Node(1)\n", + "tree.left = Node(2)\n", + "tree.right = Node(5)\n", + "tree.left.left = Node(3)\n", + "tree.left.right = Node(4)\n", + "preorder_traversal(tree)\n", + "order = \"Pre-order traversal: 1, 2, 4, 5, 3\"\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "id": "0ab0e8cb-0f07-4df4-a1e1-8934b9fa9de2", + "metadata": {}, + "source": [ + "Now write the corresponding tree for the 2 other traversal functions to return values in ascending order (i.e. provide a new tree but do not change the functions)" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "498cd602-5e9e-456b-b66a-4b289442170f", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-56de47103a5dc76a", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 2 3 4 5 " + ] + } + ], + "source": [ + "### BEGIN SOLUTION\n", + "tree = Node(3)\n", + "tree.left = Node(2)\n", + "tree.right = Node(4)\n", + "tree.left.left = Node(1)\n", + "tree.right.right = Node(5)\n", + "inorder_traversal(tree)\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "13dd6b6f-e843-4a92-af0e-2c860d95cf40", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-f407001e56e50590", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 2 3 4 5 " + ] + } + ], + "source": [ + "### BEGIN SOLUTION\n", + "tree = Node(5)\n", + "tree.right = Node(4)\n", + "tree.left = Node(3)\n", + "tree.left.right = Node(2)\n", + "tree.left.left = Node(1)\n", + "postorder_traversal(tree)\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "id": "098f76aa-1597-4394-a7d7-564e5e1949cf", + "metadata": {}, + "source": [ + "## Exercise 3: Check if two binary trees are identical\n", + "\n", + "```\n", + " 1 1\n", + " / \\ / \\\n", + " 2 3 2 3\n", + "```\n", + "\n", + "For the example above, it may return `True`" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "49aa6fce-c267-4ac1-af06-085ab33cfb4f", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-20f87de8b31f2a3c", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def check_equal(p, q):\n", + "### BEGIN SOLUTION\n", + " if p == None and q == None:\n", + " return True\n", + " elif p == None or q == None: \n", + " return False\n", + " elif p.value == q.value:\n", + " return check_equal(p.left, q.left) and check_equal(p.right, q.right)\n", + " else:\n", + " return False\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "d34397b0-e505-410f-9edb-75d6f9f35b32", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "p = Node(1)\n", + "q = Node(1)\n", + "assert check_equal(p, q)" + ] + }, + { + "cell_type": "markdown", + "id": "8d65c14b", + "metadata": {}, + "source": [ + "## Exercise 4: Check if a binary tree has a cycle\n", + "\n", + "Write a function to determine if a binary tree contains a cycle (i.e. when a node is revisited during traversal). Write a recursive version first." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "3e5a8a2d", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-80c8ab8f6a53a30e", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def has_cycle(node, visited=None, parent=None):\n", + "### BEGIN SOLUTION\n", + " if visited is None:\n", + " visited = set()\n", + "\n", + " if node is None:\n", + " return False\n", + "\n", + " if node in visited:\n", + " return True\n", + "\n", + " visited.add(node)\n", + "\n", + " if node.left is not parent: \n", + " if has_cycle(node.left, visited, node):\n", + " return True\n", + "\n", + " if node.right is not parent: \n", + " if has_cycle(node.right, visited, node):\n", + " return True\n", + "\n", + " return False\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "id": "3e5b5304-976b-429d-9d94-469195224234", + "metadata": {}, + "source": [ + "Write an iterative version now." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bcab37fd-214b-4bdb-ae63-1e4b93411f3d", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-0f979cb783306d06", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def has_cycle_iterative(node):\n", + "### BEGIN SOLUTION\n", + " if not node:\n", + " return False\n", + "\n", + " visited = set()\n", + " stack = [node]\n", + "\n", + " while stack:\n", + " current = stack.pop()\n", + "\n", + " if current in visited:\n", + " return True\n", + "\n", + " visited.add(current)\n", + "\n", + " # Add left and right children to the stack if not visited\n", + " if current.left and current.left not in visited:\n", + " stack.append(current.left)\n", + " if current.right and current.right not in visited:\n", + " stack.append(current.right)\n", + "\n", + " return False\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "83f55a68", + "metadata": { + "tags": [] + }, + "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)\n", + "\n", + "assert not has_cycle(root)\n", + "\n", + "# add a cycle\n", + "root.left.left.right = root\n", + "\n", + "assert has_cycle(root)" + ] + }, + { + "cell_type": "markdown", + "id": "1aa5b552-aaa6-4f97-921e-fcc0135ad485", + "metadata": {}, + "source": [ + "## Exercise 5: Check if a binary tree is connected\n", + "\n", + "Write a function to determine if a binary is connected to all the nodes of the tree. Warning: will now use a dictionnary data structure to store the tree." + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "id": "8ba8a250-70f7-4ec8-9deb-eea0b3f34ca3", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "T_dict = {\n", + " '0': ['1', '2'],\n", + " '1': ['4', '5'],\n", + " '2': ['3'],\n", + " '4': [],\n", + " '5': [],\n", + " '3': []\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "id": "0460d5e5-d9b1-4418-a58b-0e00a481ed67", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-bc5d0252ee379402", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def is_connected(T_dict):\n", + "### BEGIN SOLUTION\n", + " all_nodes = set(T_dict.keys())\n", + "\n", + " if not all_nodes:\n", + " return True\n", + "\n", + " root = '0'\n", + " visited = set()\n", + " stack = [root]\n", + "\n", + " while stack:\n", + " node = stack.pop()\n", + " if node not in visited:\n", + " visited.add(node)\n", + " stack.extend(T_dict.get(node, []))\n", + "\n", + " return visited == all_nodes\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "id": "d8298892-488a-482c-98bd-2a16d02f29a6", + "metadata": {}, + "source": [ + "Write tests for bot a connected and a non-connected tree." + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "id": "55ab4951-be83-48ee-a6c5-85d6e73bdf05", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-135d5364fe4568d7", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "### BEGIN SOLUTION\n", + "T_dict = {\n", + " '0': ['1', '2'],\n", + " '1': ['4', '5'],\n", + " '2': ['3'],\n", + " '4': [],\n", + " '5': [],\n", + " '3': []\n", + "}\n", + "\n", + "assert is_connected(T_dict)\n", + "T_dict['2'] = []\n", + "assert not is_connected(T_dict)\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "markdown", + "id": "c06c33fb-661b-4a73-84f7-c19c5c157fae", + "metadata": {}, + "source": [ + "## Exercise 6: Calculate the diameter of a binary tree\n", + "\n", + "To find the diameter of a binary tree, you need to compute the longest path between any two nodes in the tree. This path may or may not pass through the root. Here is a way to calculate the diameter:\n", + "\n", + "1. Calculate the height of the left and right subtrees for each node\n", + "2. Calculate the diameter at each node as the height of left subtree + right subtree\n", + "3. Traverse the tree and keep track of the max diameter encountered\n", + "\n", + "Note: we will used the `Node` data structure." + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "c2595aa9-d477-48c8-9aaa-9bc331930877", + "metadata": { + "nbgrader": { + "grade": false, + "grade_id": "cell-e54053e5277ec8ad", + "locked": false, + "schema_version": 3, + "solution": true, + "task": false + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def height(root):\n", + " if root is None:\n", + " return 0\n", + " \n", + " return 1 + max(height(root.left), height(root.right))\n", + "\n", + "def diameter(root):\n", + "### BEING SOLUTION\n", + " if root is None:\n", + " return 0\n", + "\n", + " lheight = height(root.left)\n", + " rheight = height(root.right)\n", + "\n", + " ldiameter = diameter(root.left)\n", + " rdiameter = diameter(root.right)\n", + "\n", + " return max(lheight + rheight, ldiameter, rdiameter)\n", + "\n", + "### END SOLUTION" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "e21f200a-ff9b-46af-b504-876c47b80f48", + "metadata": { + "tags": [] + }, + "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)\n", + "assert diameter(root) == 3 # 3" + ] + } + ], + "metadata": { + "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 +}