X1-ComputationalComplexity-notebook.ipynb (10501B)
1 { 2 "cells": [ 3 { 4 "cell_type": "markdown", 5 "metadata": {}, 6 "source": [ 7 "# Nested loops\n", 8 "\n", 9 "The following two functions compute sum and product of matrices, respectively.\n", 10 "\n", 11 "By counting the nested loops it is easy to see that `add()` is $O(n^2)$ while `prod()` is $O(n^3)$." 12 ] 13 }, 14 { 15 "cell_type": "code", 16 "execution_count": 3, 17 "metadata": {}, 18 "outputs": [ 19 { 20 "name": "stdout", 21 "output_type": "stream", 22 "text": [ 23 "Time for add: 0.01961983600000039\n", 24 "Time for prod: 11.278560734\n" 25 ] 26 } 27 ], 28 "source": [ 29 "from random import randint\n", 30 "import time\n", 31 "\n", 32 "def add(A, B):\n", 33 " S = [[0] * len(A) for i in range(len(A))]\n", 34 " for i in range(len(A)):\n", 35 " for j in range(len(A)):\n", 36 " S[i][j] = A[i][j] + B[i][j]\n", 37 " return S\n", 38 "\n", 39 "def prod(A, B):\n", 40 " S = [[0] * len(A) for i in range(len(A))]\n", 41 " for i in range(len(A)):\n", 42 " for j in range(len(A)):\n", 43 " for k in range(len(A)):\n", 44 " S[i][j] = S[i][j] + A[i][k] * B[k][j]\n", 45 " return S\n", 46 "\n", 47 "N = 400\n", 48 "A = [ [randint(0,100) for i in range(N)] for j in range(N) ]\n", 49 "B = [ [randint(0,100) for i in range(N)] for j in range(N) ]\n", 50 "\n", 51 "t0 = time.process_time()\n", 52 "add(A,B)\n", 53 "t1 = time.process_time()\n", 54 "prod(A,B)\n", 55 "t2 = time.process_time()\n", 56 "\n", 57 "print(\"Time for add: \", t1-t0)\n", 58 "print(\"Time for prod:\", t2-t1)" 59 ] 60 }, 61 { 62 "cell_type": "markdown", 63 "metadata": {}, 64 "source": [ 65 "# Sorting a list, slow version\n", 66 "\n", 67 "The following code implements a slow version of the so-called *insertion sort* alogithm\n", 68 "\n", 69 "Complexity: $O(n^2)$." 70 ] 71 }, 72 { 73 "cell_type": "code", 74 "execution_count": 7, 75 "metadata": {}, 76 "outputs": [ 77 { 78 "name": "stdout", 79 "output_type": "stream", 80 "text": [ 81 "Running time: 1.2679741750000009\n" 82 ] 83 } 84 ], 85 "source": [ 86 "from random import randint\n", 87 "import time\n", 88 "\n", 89 "def correct_position(e, S):\n", 90 " for i in range(len(S)):\n", 91 " if S[i] > e:\n", 92 " return i\n", 93 " return len(S)\n", 94 "\n", 95 "def sort_list(L):\n", 96 " S = []\n", 97 " for e in L:\n", 98 " cp = correct_position(e, S)\n", 99 " S.insert(cp, e)\n", 100 " return S\n", 101 "\n", 102 "N = 10000\n", 103 "L = [randint(0,10**9) for i in range(N)]\n", 104 "\n", 105 "t0 = time.process_time()\n", 106 "sort_list(L)\n", 107 "#L.sort()\n", 108 "t1 = time.process_time()\n", 109 "\n", 110 "print(\"Running time:\", t1-t0)" 111 ] 112 }, 113 { 114 "cell_type": "markdown", 115 "metadata": {}, 116 "source": [ 117 "# Binary search\n", 118 "\n", 119 "The following code implements a binary search.\n", 120 "\n", 121 "Complexity: $O(\\log_2(n))$" 122 ] 123 }, 124 { 125 "cell_type": "code", 126 "execution_count": 8, 127 "metadata": {}, 128 "outputs": [ 129 { 130 "name": "stdout", 131 "output_type": "stream", 132 "text": [ 133 "The correct position of e = 36132116 in L is:\n", 134 "... 36130178 36131096 e 36132160 36132386 ...\n", 135 "\n", 136 "Time for sorting: 0.4964379069999971\n", 137 "Time for searching: 0.00012017000000241751\n" 138 ] 139 } 140 ], 141 "source": [ 142 "from random import randint\n", 143 "import time\n", 144 "\n", 145 "def binary_search(e, S, start, end):\n", 146 " if start == end:\n", 147 " return start\n", 148 " midpoint = (start+end) // 2\n", 149 " if e < S[midpoint]:\n", 150 " return binary_search(e, S, start, midpoint)\n", 151 " else:\n", 152 " return binary_search(e, S, midpoint+1, end)\n", 153 " \n", 154 "N = 1000000\n", 155 "L = [randint(0,10**9) for i in range(N)]\n", 156 "e = randint(0,10**9)\n", 157 "\n", 158 "t0 = time.process_time()\n", 159 "L.sort() # Using Python's sort()\n", 160 "t1 = time.process_time()\n", 161 "i = binary_search(e, L, 0, len(L))\n", 162 "t2 = time.process_time()\n", 163 "print(\"The correct position of e =\", e, \"in L is:\")\n", 164 "print(\"...\", L[i-2], L[i-1], \"e\", L[i], L[i+1], \"...\")\n", 165 "print(\"\")\n", 166 "print(\"Time for sorting: \", t1-t0)\n", 167 "print(\"Time for searching:\", t2-t1)\n" 168 ] 169 }, 170 { 171 "cell_type": "markdown", 172 "metadata": {}, 173 "source": [ 174 "# Sorting a list, fast version (with binary_search)\n", 175 "\n", 176 "The following code uses the function `binary_search()` above instead of `correct_position()` in our insertion sort algorithm.\n", 177 "\n", 178 "Complexity: $O(n\\log_2(n))$" 179 ] 180 }, 181 { 182 "cell_type": "code", 183 "execution_count": 11, 184 "metadata": {}, 185 "outputs": [ 186 { 187 "name": "stdout", 188 "output_type": "stream", 189 "text": [ 190 "Running time: 1.5683504970000008\n" 191 ] 192 } 193 ], 194 "source": [ 195 "from random import randint\n", 196 "import time\n", 197 "\n", 198 "def binary_search(e, S, start, end):\n", 199 " if start == end:\n", 200 " return start\n", 201 " midpoint = (start+end) // 2\n", 202 " if e < S[midpoint]:\n", 203 " return binary_search(e, S, start, midpoint)\n", 204 " else:\n", 205 " return binary_search(e, S, midpoint+1, end)\n", 206 " \n", 207 "def sort_list(L):\n", 208 " S = []\n", 209 " for e in L:\n", 210 " cp = binary_search(e, S, 0, len(S)) # Changed here\n", 211 " S.insert(cp, e)\n", 212 " return S\n", 213 " \n", 214 "N = 100000\n", 215 "L = [randint(0,10**9) for i in range(N)]\n", 216 "\n", 217 "t0 = time.process_time()\n", 218 "sort_list(L)\n", 219 "t1 = time.process_time()\n", 220 "\n", 221 "print(\"Running time:\", t1-t0)" 222 ] 223 }, 224 { 225 "cell_type": "markdown", 226 "metadata": {}, 227 "source": [ 228 "# Fast exponentiation\n", 229 "\n", 230 "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", 231 "\n", 232 "Complexity: $O(n)$ for the slow algorithm, $O(\\log_2(n))$ for the other two." 233 ] 234 }, 235 { 236 "cell_type": "code", 237 "execution_count": 14, 238 "metadata": {}, 239 "outputs": [ 240 { 241 "name": "stdout", 242 "output_type": "stream", 243 "text": [ 244 "2.71828179834636\n", 245 "2.7182817863957984\n", 246 "2.7182817983473577\n", 247 "Time for slow_power(): 3.5567659670000005\n", 248 "Time for fast_power(): 0.00014241699999928414\n", 249 "Time for Python's **: 9.477100000054861e-05\n" 250 ] 251 } 252 ], 253 "source": [ 254 "import time\n", 255 "\n", 256 "def slow_power(a, n):\n", 257 " r = 1\n", 258 " for i in range(n):\n", 259 " r = r * a\n", 260 " return r\n", 261 "\n", 262 "def fast_power(a, n):\n", 263 " if n == 0:\n", 264 " return 1\n", 265 " if n%2 == 0:\n", 266 " return fast_power(a*a, n//2)\n", 267 " else:\n", 268 " return a * fast_power(a, n-1)\n", 269 "\n", 270 "a = 1.00000001\n", 271 "n = 100000000\n", 272 "\n", 273 "t0 = time.process_time()\n", 274 "print(slow_power(a, n))\n", 275 "t1 = time.process_time()\n", 276 "print(fast_power(a, n))\n", 277 "t2 = time.process_time()\n", 278 "print(a**n)\n", 279 "t3 = time.process_time()\n", 280 "\n", 281 "print(\"Time for slow_power():\", t1-t0)\n", 282 "print(\"Time for fast_power():\", t2-t1)\n", 283 "print(\"Time for Python's **: \", t3-t2)" 284 ] 285 }, 286 { 287 "cell_type": "markdown", 288 "metadata": {}, 289 "source": [ 290 "# Fast gcd\n", 291 "\n", 292 "Complexity: $O(\\log_2(n))$" 293 ] 294 }, 295 { 296 "cell_type": "code", 297 "execution_count": 15, 298 "metadata": {}, 299 "outputs": [ 300 { 301 "name": "stdout", 302 "output_type": "stream", 303 "text": [ 304 "126\n", 305 "Running time: 0.0002987290000007192\n" 306 ] 307 } 308 ], 309 "source": [ 310 "import time\n", 311 "\n", 312 "def gcd(a, b):\n", 313 " if b == 0:\n", 314 " return a\n", 315 " else:\n", 316 " return gcd(b, a%b)\n", 317 "\n", 318 "t0 = time.process_time()\n", 319 "print(gcd(155275387236018, 572335397352432))\n", 320 "t1 = time.process_time()\n", 321 "\n", 322 "print(\"Running time:\", t1-t0)" 323 ] 324 }, 325 { 326 "cell_type": "markdown", 327 "metadata": {}, 328 "source": [ 329 "# Fibonacci numbers\n", 330 "\n", 331 "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", 332 "\n", 333 "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." 334 ] 335 }, 336 { 337 "cell_type": "code", 338 "execution_count": 24, 339 "metadata": {}, 340 "outputs": [ 341 { 342 "name": "stdout", 343 "output_type": "stream", 344 "text": [ 345 "222232244629420445529739893461909967206666939096499764990979600\n", 346 "Time for F_slow: 3.0404000000316955e-05\n", 347 "Time for F_fast: 0.0003859389999973928\n" 348 ] 349 } 350 ], 351 "source": [ 352 "import time\n", 353 "\n", 354 "F_memorized = [-1] * (10**6)\n", 355 "\n", 356 "def F_slow(n):\n", 357 " if n <= 1:\n", 358 " return n\n", 359 " else:\n", 360 " return F_slow(n-1) + F_slow(n-2)\n", 361 " \n", 362 "def F_fast(n):\n", 363 " if F_memorized[n] == -1:\n", 364 " if n <= 1:\n", 365 " F_memorized[n] = n\n", 366 " else:\n", 367 " F_memorized[n] = F_fast(n-1) + F_fast(n-2)\n", 368 " \n", 369 " return F_memorized[n]\n", 370 "\n", 371 "n = 300\n", 372 "\n", 373 "t0 = time.process_time()\n", 374 "#print(F_slow(n))\n", 375 "t1 = time.process_time()\n", 376 "print(F_fast(n))\n", 377 "t2 = time.process_time()\n", 378 "\n", 379 "print(\"Time for F_slow:\", t1-t0)\n", 380 "print(\"Time for F_fast:\", t2-t1)" 381 ] 382 }, 383 { 384 "cell_type": "code", 385 "execution_count": null, 386 "metadata": {}, 387 "outputs": [], 388 "source": [] 389 } 390 ], 391 "metadata": { 392 "kernelspec": { 393 "display_name": "Python 3", 394 "language": "python", 395 "name": "python3" 396 }, 397 "language_info": { 398 "codemirror_mode": { 399 "name": "ipython", 400 "version": 3 401 }, 402 "file_extension": ".py", 403 "mimetype": "text/x-python", 404 "name": "python", 405 "nbconvert_exporter": "python", 406 "pygments_lexer": "ipython3", 407 "version": "3.8.5" 408 } 409 }, 410 "nbformat": 4, 411 "nbformat_minor": 4 412 }