X1-ComputationalComplexity-notebook-checkpoint.ipynb (10451B)
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": 2, 17 "metadata": {}, 18 "outputs": [ 19 { 20 "name": "stdout", 21 "output_type": "stream", 22 "text": [ 23 "Time for add: 0.005766554000000035\n", 24 "Time for prod: 1.3871021639999999\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 = 200\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": 1, 75 "metadata": {}, 76 "outputs": [ 77 { 78 "name": "stdout", 79 "output_type": "stream", 80 "text": [ 81 "Running time: 1.1191449070000001\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 "t1 = time.process_time()\n", 108 "\n", 109 "print(\"Running time:\", t1-t0)" 110 ] 111 }, 112 { 113 "cell_type": "markdown", 114 "metadata": {}, 115 "source": [ 116 "# Binary search\n", 117 "\n", 118 "The following code implements a binary search.\n", 119 "\n", 120 "Complexity: $O(\\log_2(n))$" 121 ] 122 }, 123 { 124 "cell_type": "code", 125 "execution_count": 3, 126 "metadata": {}, 127 "outputs": [ 128 { 129 "name": "stdout", 130 "output_type": "stream", 131 "text": [ 132 "The correct position of e = 216197744 in L is:\n", 133 "... 216196218 216197540 e 216198673 216198962 ...\n", 134 "\n", 135 "Time for sorting: 0.26413054400000036\n", 136 "Time for searching: 9.616099999965044e-05\n" 137 ] 138 } 139 ], 140 "source": [ 141 "from random import randint\n", 142 "import time\n", 143 "\n", 144 "def binary_search(e, S, start, end):\n", 145 " if start == end:\n", 146 " return start\n", 147 " midpoint = (start+end) // 2\n", 148 " if e < S[midpoint]:\n", 149 " return binary_search(e, S, start, midpoint)\n", 150 " else:\n", 151 " return binary_search(e, S, midpoint+1, end)\n", 152 " \n", 153 "N = 1000000\n", 154 "L = [randint(0,10**9) for i in range(N)]\n", 155 "e = randint(0,10**9)\n", 156 "\n", 157 "t0 = time.process_time()\n", 158 "L.sort() # Using Python's sort()\n", 159 "t1 = time.process_time()\n", 160 "i = binary_search(e, L, 0, len(L))\n", 161 "t2 = time.process_time()\n", 162 "print(\"The correct position of e =\", e, \"in L is:\")\n", 163 "print(\"...\", L[i-2], L[i-1], \"e\", L[i], L[i+1], \"...\")\n", 164 "print(\"\")\n", 165 "print(\"Time for sorting: \", t1-t0)\n", 166 "print(\"Time for searching:\", t2-t1)\n" 167 ] 168 }, 169 { 170 "cell_type": "markdown", 171 "metadata": {}, 172 "source": [ 173 "# Sorting a list, fast version (with binary_search)\n", 174 "\n", 175 "The following code uses the function `binary_search()` above instead of `correct_position()` in our insertion sort algorithm.\n", 176 "\n", 177 "Complexity: $O(n\\log_2(n))$" 178 ] 179 }, 180 { 181 "cell_type": "code", 182 "execution_count": 69, 183 "metadata": {}, 184 "outputs": [ 185 { 186 "name": "stdout", 187 "output_type": "stream", 188 "text": [ 189 "Running time: 0.03710268399998995\n" 190 ] 191 } 192 ], 193 "source": [ 194 "from random import randint\n", 195 "import time\n", 196 "\n", 197 "def binary_search(e, S, start, end):\n", 198 " if start == end:\n", 199 " return start\n", 200 " midpoint = (start+end) // 2\n", 201 " if e < S[midpoint]:\n", 202 " return binary_search(e, S, start, midpoint)\n", 203 " else:\n", 204 " return binary_search(e, S, midpoint+1, end)\n", 205 " \n", 206 "def sort_list(L):\n", 207 " S = []\n", 208 " for e in L:\n", 209 " cp = binary_search(e, S, 0, len(S)) # Changed here\n", 210 " S.insert(cp, e)\n", 211 " return S\n", 212 " \n", 213 "N = 10000\n", 214 "L = [randint(0,10**9) for i in range(N)]\n", 215 "\n", 216 "t0 = time.process_time()\n", 217 "sort_list(L)\n", 218 "t1 = time.process_time()\n", 219 "\n", 220 "print(\"Running time:\", t1-t0)" 221 ] 222 }, 223 { 224 "cell_type": "markdown", 225 "metadata": {}, 226 "source": [ 227 "# Fast exponentiation\n", 228 "\n", 229 "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", 230 "\n", 231 "Complexity: $O(n)$ for the slow algorithm, $O(\\log_2(n))$ for the other two." 232 ] 233 }, 234 { 235 "cell_type": "code", 236 "execution_count": 30, 237 "metadata": {}, 238 "outputs": [ 239 { 240 "name": "stdout", 241 "output_type": "stream", 242 "text": [ 243 "2.71828179834636\n", 244 "2.7182817863957984\n", 245 "2.7182817983473577\n", 246 "Time for slow_power(): 3.234879998000004\n", 247 "Time for fast_power(): 9.059099999575437e-05\n", 248 "Time for Python's **: 0.00010159500000384014\n" 249 ] 250 } 251 ], 252 "source": [ 253 "import time\n", 254 "\n", 255 "def slow_power(a, n):\n", 256 " r = 1\n", 257 " for i in range(n):\n", 258 " r = r * a\n", 259 " return r\n", 260 "\n", 261 "def fast_power(a, n):\n", 262 " if n == 0:\n", 263 " return 1\n", 264 " if n%2 == 0:\n", 265 " return fast_power(a*a, n//2)\n", 266 " else:\n", 267 " return a * fast_power(a, n-1)\n", 268 "\n", 269 "a = 1.00000001\n", 270 "n = 100000000\n", 271 "\n", 272 "t0 = time.process_time()\n", 273 "print(slow_power(a, n))\n", 274 "t1 = time.process_time()\n", 275 "print(fast_power(a, n))\n", 276 "t2 = time.process_time()\n", 277 "print(a**n)\n", 278 "t3 = time.process_time()\n", 279 "\n", 280 "print(\"Time for slow_power():\", t1-t0)\n", 281 "print(\"Time for fast_power():\", t2-t1)\n", 282 "print(\"Time for Python's **: \", t3-t2)" 283 ] 284 }, 285 { 286 "cell_type": "markdown", 287 "metadata": {}, 288 "source": [ 289 "# Fast gcd\n", 290 "\n", 291 "Complexity: $O(\\log_2(n))$" 292 ] 293 }, 294 { 295 "cell_type": "code", 296 "execution_count": 31, 297 "metadata": {}, 298 "outputs": [ 299 { 300 "name": "stdout", 301 "output_type": "stream", 302 "text": [ 303 "126\n", 304 "Running time: 0.00017707599999994272\n" 305 ] 306 } 307 ], 308 "source": [ 309 "import time\n", 310 "\n", 311 "def gcd(a, b):\n", 312 " if b == 0:\n", 313 " return a\n", 314 " else:\n", 315 " return gcd(b, a%b)\n", 316 "\n", 317 "t0 = time.process_time()\n", 318 "print(gcd(155275387236018, 572335397352432))\n", 319 "t1 = time.process_time()\n", 320 "\n", 321 "print(\"Running time:\", t1-t0)" 322 ] 323 }, 324 { 325 "cell_type": "markdown", 326 "metadata": {}, 327 "source": [ 328 "# Fibonacci numbers\n", 329 "\n", 330 "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", 331 "\n", 332 "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." 333 ] 334 }, 335 { 336 "cell_type": "code", 337 "execution_count": 37, 338 "metadata": {}, 339 "outputs": [ 340 { 341 "name": "stdout", 342 "output_type": "stream", 343 "text": [ 344 "9227465\n", 345 "9227465\n", 346 "Time for F_slow: 2.3301570169999906\n", 347 "Time for F_fast: 8.848800000293977e-05\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 = 35\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 }