From d6c61d988bfa4255baf9cdae42db59ebee38363f Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Tue, 25 May 2021 17:10:49 +0200 Subject: Added files --- .../X1-ComputationalComplexity-notebook.ipynb | 412 +++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 src/Lecture7/notebook/X1-ComputationalComplexity-notebook.ipynb (limited to 'src/Lecture7/notebook/X1-ComputationalComplexity-notebook.ipynb') diff --git a/src/Lecture7/notebook/X1-ComputationalComplexity-notebook.ipynb b/src/Lecture7/notebook/X1-ComputationalComplexity-notebook.ipynb new file mode 100644 index 0000000..18be171 --- /dev/null +++ b/src/Lecture7/notebook/X1-ComputationalComplexity-notebook.ipynb @@ -0,0 +1,412 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Nested loops\n", + "\n", + "The following two functions compute sum and product of matrices, respectively.\n", + "\n", + "By counting the nested loops it is easy to see that `add()` is $O(n^2)$ while `prod()` is $O(n^3)$." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Time for add: 0.01961983600000039\n", + "Time for prod: 11.278560734\n" + ] + } + ], + "source": [ + "from random import randint\n", + "import time\n", + "\n", + "def add(A, B):\n", + " S = [[0] * len(A) for i in range(len(A))]\n", + " for i in range(len(A)):\n", + " for j in range(len(A)):\n", + " S[i][j] = A[i][j] + B[i][j]\n", + " return S\n", + "\n", + "def prod(A, B):\n", + " S = [[0] * len(A) for i in range(len(A))]\n", + " for i in range(len(A)):\n", + " for j in range(len(A)):\n", + " for k in range(len(A)):\n", + " S[i][j] = S[i][j] + A[i][k] * B[k][j]\n", + " return S\n", + "\n", + "N = 400\n", + "A = [ [randint(0,100) for i in range(N)] for j in range(N) ]\n", + "B = [ [randint(0,100) for i in range(N)] for j in range(N) ]\n", + "\n", + "t0 = time.process_time()\n", + "add(A,B)\n", + "t1 = time.process_time()\n", + "prod(A,B)\n", + "t2 = time.process_time()\n", + "\n", + "print(\"Time for add: \", t1-t0)\n", + "print(\"Time for prod:\", t2-t1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sorting a list, slow version\n", + "\n", + "The following code implements a slow version of the so-called *insertion sort* alogithm\n", + "\n", + "Complexity: $O(n^2)$." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running time: 1.2679741750000009\n" + ] + } + ], + "source": [ + "from random import randint\n", + "import time\n", + "\n", + "def correct_position(e, S):\n", + " for i in range(len(S)):\n", + " if S[i] > e:\n", + " return i\n", + " return len(S)\n", + "\n", + "def sort_list(L):\n", + " S = []\n", + " for e in L:\n", + " cp = correct_position(e, S)\n", + " S.insert(cp, e)\n", + " return S\n", + "\n", + "N = 10000\n", + "L = [randint(0,10**9) for i in range(N)]\n", + "\n", + "t0 = time.process_time()\n", + "sort_list(L)\n", + "#L.sort()\n", + "t1 = time.process_time()\n", + "\n", + "print(\"Running time:\", t1-t0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Binary search\n", + "\n", + "The following code implements a binary search.\n", + "\n", + "Complexity: $O(\\log_2(n))$" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The correct position of e = 36132116 in L is:\n", + "... 36130178 36131096 e 36132160 36132386 ...\n", + "\n", + "Time for sorting: 0.4964379069999971\n", + "Time for searching: 0.00012017000000241751\n" + ] + } + ], + "source": [ + "from random import randint\n", + "import time\n", + "\n", + "def binary_search(e, S, start, end):\n", + " if start == end:\n", + " return start\n", + " midpoint = (start+end) // 2\n", + " if e < S[midpoint]:\n", + " return binary_search(e, S, start, midpoint)\n", + " else:\n", + " return binary_search(e, S, midpoint+1, end)\n", + " \n", + "N = 1000000\n", + "L = [randint(0,10**9) for i in range(N)]\n", + "e = randint(0,10**9)\n", + "\n", + "t0 = time.process_time()\n", + "L.sort() # Using Python's sort()\n", + "t1 = time.process_time()\n", + "i = binary_search(e, L, 0, len(L))\n", + "t2 = time.process_time()\n", + "print(\"The correct position of e =\", e, \"in L is:\")\n", + "print(\"...\", L[i-2], L[i-1], \"e\", L[i], L[i+1], \"...\")\n", + "print(\"\")\n", + "print(\"Time for sorting: \", t1-t0)\n", + "print(\"Time for searching:\", t2-t1)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sorting a list, fast version (with binary_search)\n", + "\n", + "The following code uses the function `binary_search()` above instead of `correct_position()` in our insertion sort algorithm.\n", + "\n", + "Complexity: $O(n\\log_2(n))$" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running time: 1.5683504970000008\n" + ] + } + ], + "source": [ + "from random import randint\n", + "import time\n", + "\n", + "def binary_search(e, S, start, end):\n", + " if start == end:\n", + " return start\n", + " midpoint = (start+end) // 2\n", + " if e < S[midpoint]:\n", + " return binary_search(e, S, start, midpoint)\n", + " else:\n", + " return binary_search(e, S, midpoint+1, end)\n", + " \n", + "def sort_list(L):\n", + " S = []\n", + " for e in L:\n", + " cp = binary_search(e, S, 0, len(S)) # Changed here\n", + " S.insert(cp, e)\n", + " return S\n", + " \n", + "N = 100000\n", + "L = [randint(0,10**9) for i in range(N)]\n", + "\n", + "t0 = time.process_time()\n", + "sort_list(L)\n", + "t1 = time.process_time()\n", + "\n", + "print(\"Running time:\", t1-t0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fast exponentiation\n", + "\n", + "The following cell contains two functions for computing $a^n$ ($n$ non-negative integer): a slow one that runs in $O(n)$ and a fast one that runs in $O(\\log_2(n))$. We compare these two also with Python's built-in operator `**`.\n", + "\n", + "Complexity: $O(n)$ for the slow algorithm, $O(\\log_2(n))$ for the other two." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.71828179834636\n", + "2.7182817863957984\n", + "2.7182817983473577\n", + "Time for slow_power(): 3.5567659670000005\n", + "Time for fast_power(): 0.00014241699999928414\n", + "Time for Python's **: 9.477100000054861e-05\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "def slow_power(a, n):\n", + " r = 1\n", + " for i in range(n):\n", + " r = r * a\n", + " return r\n", + "\n", + "def fast_power(a, n):\n", + " if n == 0:\n", + " return 1\n", + " if n%2 == 0:\n", + " return fast_power(a*a, n//2)\n", + " else:\n", + " return a * fast_power(a, n-1)\n", + "\n", + "a = 1.00000001\n", + "n = 100000000\n", + "\n", + "t0 = time.process_time()\n", + "print(slow_power(a, n))\n", + "t1 = time.process_time()\n", + "print(fast_power(a, n))\n", + "t2 = time.process_time()\n", + "print(a**n)\n", + "t3 = time.process_time()\n", + "\n", + "print(\"Time for slow_power():\", t1-t0)\n", + "print(\"Time for fast_power():\", t2-t1)\n", + "print(\"Time for Python's **: \", t3-t2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fast gcd\n", + "\n", + "Complexity: $O(\\log_2(n))$" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "126\n", + "Running time: 0.0002987290000007192\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "def gcd(a, b):\n", + " if b == 0:\n", + " return a\n", + " else:\n", + " return gcd(b, a%b)\n", + "\n", + "t0 = time.process_time()\n", + "print(gcd(155275387236018, 572335397352432))\n", + "t1 = time.process_time()\n", + "\n", + "print(\"Running time:\", t1-t0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fibonacci numbers\n", + "\n", + "In the following cell there are two functions that compute the $n$-th Fibonacci number. They are almost the same, but the second one memorizes the results in a list to avoid computing them multiple times, and it is much much faster.\n", + "\n", + "Complexity: $O\\left(\\left(\\frac{1+\\sqrt 5}{2}\\right)^n\\right)\\sim O(1.6^n)$ for the slow version, $O(n)$ for the fast version." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "222232244629420445529739893461909967206666939096499764990979600\n", + "Time for F_slow: 3.0404000000316955e-05\n", + "Time for F_fast: 0.0003859389999973928\n" + ] + } + ], + "source": [ + "import time\n", + "\n", + "F_memorized = [-1] * (10**6)\n", + "\n", + "def F_slow(n):\n", + " if n <= 1:\n", + " return n\n", + " else:\n", + " return F_slow(n-1) + F_slow(n-2)\n", + " \n", + "def F_fast(n):\n", + " if F_memorized[n] == -1:\n", + " if n <= 1:\n", + " F_memorized[n] = n\n", + " else:\n", + " F_memorized[n] = F_fast(n-1) + F_fast(n-2)\n", + " \n", + " return F_memorized[n]\n", + "\n", + "n = 300\n", + "\n", + "t0 = time.process_time()\n", + "#print(F_slow(n))\n", + "t1 = time.process_time()\n", + "print(F_fast(n))\n", + "t2 = time.process_time()\n", + "\n", + "print(\"Time for F_slow:\", t1-t0)\n", + "print(\"Time for F_fast:\", t2-t1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} -- cgit v1.3