Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

419 lines
10 KiB

  1. If you have ever taken a computer science class you probably know what
  2. the fibonacci sequence is and how to calculate it. For those who don't
  3. know: [Fibonacci](https://en.wikipedia.org/wiki/Fibonacci) is a
  4. sequence of numbers starting with 0,1 whose next number is the sum of
  5. the two previous numbers. After having multiple of my CS classes give
  6. lectures and homeworks on the Fibonacci sequence; I decided to write
  7. a blog post going over the 4 main ways of calculating the nth term of
  8. the Fibonacci sequence. In addition to providing the python code for
  9. calculating the nth perm of the sequence, a proof for their validity
  10. and an analysis of their time complexities both mathematically and
  11. empirically will be examined.
  12. # Slow Recursive Definition
  13. By the definition of the Fibonacci sequence, it is the most natural to
  14. write it as a recursive definition.
  15. ```Python
  16. def fib(n):
  17. if n == 0 or n == 1:
  18. return n
  19. return fib(n-1) + fib(n-2)
  20. ```
  21. ##Time Complexity
  22. Observing that each call has two recursive calls we can place an upper
  23. bound on this function as $O(2^n)$. However, if we solve this
  24. recurrence we can place a tight bound for time complexity.
  25. We can write a recurrence for the number of times fib is called:
  26. $$
  27. F(1) = 1\\
  28. F(n) = F(n-1) + F(n-2)\\
  29. $$
  30. Next, we replace F(n) with $a^n$ since we want to find rate of
  31. exponential growth.
  32. $$
  33. a^n = a^{n-1} + a^{n-2}\\
  34. \frac{a^n}{a^{n-2}} = \frac{a^{n-1} + a^{n-2}}{a^{n-2}}\\
  35. a^2 = a + 1\\
  36. a = \frac{1 \pm sqrt(5)}{2}\\
  37. $$
  38. From this calculation we can conclude that F(n) $\in \Theta 1.681^n$.
  39. We don't have to worry about the negative root since it would not be
  40. asymptotically relevant by the definition of $\Theta$.
  41. ## Measured Performance
  42. Here is a graph of the actual performance that I observed from this
  43. recursive definition of Fibonacci.
  44. ![Recursive Definition](media/fibonacci/RecursiveDefinition.png)
  45. # Accumulation Solution
  46. The problem with the previous recursive solution is that you had to
  47. recalculate certain terms of fibonacci a ton of times. A summation
  48. variable would help us avoid this problem. You could write this
  49. solution using a simple loop or dynamic programming , however, I chose
  50. to use recursion to demonstrate that it's recursion which made the
  51. first problem slow.
  52. ```Python
  53. def fibHelper(n, a, b):
  54. if n == 0:
  55. return a
  56. elif n == 1:
  57. return b
  58. return fibHelper(n-1, b, a+b)
  59. ```
  60. ```python
  61. def fibIterative(n):
  62. return fibHelper(n, 0, 1)
  63. ```
  64. In this code example, fibHelper is a method which accumulates the
  65. previous two terms. The fibIterative is a wrapper method which sets
  66. the two initial terms equal to 0 and 1 representing the fibonacci
  67. sequence. At first it may not be obvious that fibIterative(n) is
  68. equivalent to fib(n). To demonstrate that these two are in fact
  69. equivalent, I broke this into two inductive proofs.
  70. ## Proof for Fib Helper
  71. **Lemma:** For any n $\epsilon$ N if n $>$ 1 then
  72. $fibHelper(n, a, b) = fibHelper(n - 1, a, b) + fibHelper(n - 2, a, b)$.
  73. **Proof via Induction**
  74. **Base Case**: n = 2:
  75. $$
  76. LHS = fibHelper(2, a, b)\\
  77. = fibHelper(1, b, a + b) = a + b\\
  78. RHS = fibHelper(2 -1, a, b) + fibHelper(2-2, a, b)\\
  79. = a + b\\
  80. $$
  81. **Inductive Step:**
  82. Assume proposition is true for all n and show n+1 follows.
  83. $$
  84. RHS=fibHelper(n+1;a,b)\\
  85. = fibHelper(n;b,a+b)\\
  86. =fibHelper(n-1;b,a+b) + fibHelper(n-2;b,a+b)\\
  87. =fibHelper(n;a,b) + fibHelper(n-1;a,b)\\
  88. =LHS\\
  89. $$
  90. $\Box$
  91. ## Proof That fibIterative = Fib
  92. **Lemma:** For any n $\in$ N, $fib(n)$ = $fibIterative(n, 0, 1)$
  93. **Proof via Strong Induction**
  94. **Base Case**: n = 0:
  95. $$
  96. fibIterative(0, 0, 0) = 0\\
  97. = fib(0)
  98. $$
  99. **Base Case**: n = 1:
  100. $$
  101. fibIterative(1, 0, 0) = 1\\
  102. = fib(1)
  103. $$
  104. **Inductive Step:**
  105. Assume proposition is true for all n and show n+1 follows.
  106. $$
  107. fib(n+1) = fib(n) + fib(n-1)\\
  108. = fibHelper(n, 0, 1) + fibHelper(n+1, 0 ,1) \quad\text{I.H}\\
  109. = fibHelper(n+1, 0, 1) \quad\text{from result in previous proof}\\
  110. $$
  111. $\Box$
  112. ## Time Complexity
  113. Suppose that we wish to solve for the time complexity in terms of the
  114. number of additions needed to be computed. Based on fibHelper we can
  115. see that it performs one addition every recursive call. We can now
  116. form a recurrence for time complexity.
  117. $$
  118. T(0) = 0\\
  119. T(1) = 0\\
  120. T(n) = 1 + T(n-1)\\
  121. T(n) = n-1\\
  122. $$
  123. From this recurrence we can say that fibHelper $\in \Theta(n)$.
  124. ## Measured Performance
  125. Notice how much faster this solution is compared to the original
  126. recursive solution for Fibonacci.
  127. ![Iterative Performance](media/fibonacci/Iterative.png)
  128. # Matrix Solution
  129. We can actually get better than linear for performance for Fibonacci
  130. while still using recursion. However, to do so we need to know this
  131. fact:
  132. $$
  133. \begin{bmatrix}
  134. 1 & 1\\
  135. 1 & 0
  136. \end{bmatrix}^n =
  137. \begin{bmatrix}
  138. F_{n+1} & F_n\\
  139. F_n & F{n-1}
  140. \end{bmatrix}^n
  141. $$
  142. Without any tricks, raising a matrix to a power n times would not get
  143. us better than linear performance. However, if we use the
  144. [Exponentiation by
  145. Squaring](https://en.wikipedia.org/wiki/Exponentiation_by_squaring)
  146. method, we can expect to see logarithmic time. Since two spots in the
  147. matrix are always equal, I represented the matrix as an array with
  148. only three elements to reduce the space and computations required.
  149. ```Python
  150. def multiply(a,b):
  151. product = [0,0,0]
  152. product[0] = a[0]*b[0] + a[1]*b[1]
  153. product[1] = a[0]*b[1] + a[1]*b[2]
  154. product[2] = a[1]*b[1] + a[2]*b[2]
  155. return product
  156. ```
  157. ```python
  158. def power(l, k):
  159. if k == 1:
  160. return l
  161. temp = power(l, k//2)
  162. if k%2 == 0:
  163. return multiply(temp, temp)
  164. else:
  165. return multiply(l, multiply(temp, temp))
  166. ```
  167. ```python
  168. def fibPower(n):
  169. l = [1,1,0]
  170. return power(l, n)[1]
  171. ```
  172. ## Time Complexity
  173. For this algorithm, lets solve for the time complexity as the number
  174. of additions and multiplications required.
  175. Since we are always multiplying two 2x2 matrices, that operation is
  176. constant time.
  177. $$
  178. T_{multiply} = 9
  179. $$
  180. Solving for the time complexity of fib power is slightly more
  181. complicated.
  182. $$
  183. T_{power}(1) = 0\\
  184. T_{power}(n) = T(\left\lfloor\dfrac{n}{2}\right\rfloor) + T_{multiply}\\
  185. = T(\left\lfloor\dfrac{n}{2}\right\rfloor) + 9\\
  186. = T(\left\lfloor\dfrac{n}{2*2}\right\rfloor) + 9 + 9\\
  187. = T(\left\lfloor\dfrac{n}{2*2*2}\right\rfloor) + 9+ 9 + 9\\
  188. T_{power}(n) = T(\left\lfloor\dfrac{n}{2^k}\right\rfloor) + 9k\\
  189. $$
  190. let $k=k_0$ such that $\left\lfloor\dfrac{n}{2^{k_0}}\right\rfloor =
  191. 1$
  192. $$
  193. \left\lfloor\dfrac{n}{2^{k_0}}\right\rfloor = 1 \rightarrow 1 \leq \frac{n}{2^{k_0}} < 2\\
  194. \rightarrow 2^{k_0} \leq n < 2^{k_0 +1}\\
  195. \rightarrow k_0 \leq lg(n) < k_0+1\\
  196. \rightarrow k_0 = \left\lfloor lg(n)\right\rfloor\\
  197. T_{power}(n) = T(1) + 9*\left\lfloor lg(n)\right\rfloor\\
  198. T_{power}(n) = 9*\left\lfloor\ lg(n)\right\rfloor\\
  199. T_{fibPower}(n) = T_{power}(n)\\
  200. $$
  201. Now we can state that $fibPower(n) \in \Theta(log(n))$.
  202. ## Inductive Proof for Matrix Method
  203. I would like to now prove that this matrix identity is valid since it
  204. is not at first obvious.
  205. **Lemma:** For any n $\epsilon$ N if n $>$ 0 then
  206. $$
  207. \begin{bmatrix}
  208. 1 & 1\\
  209. 1 & 0
  210. \end{bmatrix}^n =
  211. \begin{bmatrix}
  212. F_{n+1} & F_n\\
  213. F_n & F{n-1}
  214. \end{bmatrix}^n
  215. $$
  216. Let
  217. $$
  218. A=
  219. \begin{bmatrix}
  220. 1 & 1\\
  221. 1 & 0
  222. \end{bmatrix}^n
  223. $$
  224. **Base Case:** n = 1
  225. $$
  226. A^1=
  227. \begin{bmatrix}
  228. 1 & 1\\
  229. 1 & 0
  230. \end{bmatrix}^n =
  231. \begin{bmatrix}
  232. F_{2} & F_2\\
  233. F_2 & F_{0}
  234. \end{bmatrix}^n
  235. $$
  236. **Inductive Step:** Assume proposition is true for n, show n+1 follows
  237. $$
  238. A^{n+1}=
  239. \begin{bmatrix}
  240. 1 & 1\\
  241. 1 & 0
  242. \end{bmatrix}
  243. \begin{bmatrix}
  244. F_{n+1} & F_n\\
  245. F_n & F{n-1}
  246. \end{bmatrix}^n\\
  247. = \begin{bmatrix}
  248. F_{n+1} + F_n & F_n + F_{n-1}\\
  249. F_{n+1} & F_{n}
  250. \end{bmatrix}\\
  251. = \begin{bmatrix}
  252. F_{n+2} & F_{n+1}\\
  253. F_{n+1} & F_{n}
  254. \end{bmatrix}\\
  255. $$
  256. $\Box$
  257. ## Measured Performance
  258. ![FibPower Performance](media/fibonacci/FibPower.png)
  259. As expected by our mathematical calculations, the algorithm appears to
  260. be running in logarithmic time.
  261. ## Measured Performance With Large Numbers
  262. ![FibPower Performance](media/fibonacci/FibPowerBigPicture.png)
  263. When calculating the fibonacci term for extremely large numbers
  264. despite having a polynomial time complexity, the space required to
  265. compute each Fibonacci term grows exponentially. Since our performance
  266. is only pseudo-polynomial we see a degrade in our performance when
  267. calculating large terms of the fibonacci sequence.
  268. The one amazing thing to point out here is that despite calculating
  269. the 10,000 term of Fibonacci, this algorithm is nearly 400 times
  270. faster than the recursive algorithm when calculating the 30th term of
  271. Fibonacci.
  272. # Closed Form Definition
  273. It is actually possible to calculate Fibonacci in constant time using
  274. Binet's Formula.
  275. $$
  276. F_n = \frac{(\frac{1+\sqrt{5}}{2})^n-(\frac{1-\sqrt{5}}{2})^n}{\sqrt{5}}
  277. $$
  278. ```Python
  279. def fibClosedFormula(n):
  280. p = ((1+ math.sqrt(5))/2)**n
  281. v = ((1-math.sqrt(5))/2)**n
  282. return (p-v)/math.sqrt(5)
  283. ```
  284. ## Derivation of Binet's Formula
  285. Similar to when we were calculating the time complexity of the basic
  286. recursive definition , we want to start by finding the two roots of
  287. the equation in terms of exponents.
  288. $$
  289. a^n = a^{n-1} + a^{n-2}\\
  290. \frac{a^n}{a^{n-2}} = \frac{a^{n-1} + a^{n-2}}{a^{n-2}}\\
  291. a^2 = a + 1\\
  292. 0 = a^2 - a - 1\\
  293. a = \frac{1 \pm sqrt(5)}{2}\\
  294. $$
  295. Since there are two roots to the equation, the solution of $F_n$ is
  296. going to be a linear combination of the two roots.
  297. $$
  298. F_n = c_1(\frac{1 + \sqrt{5}}{2})^n + c_2(\frac{1 - \sqrt{5}}{2})^n
  299. $$
  300. Fact: $F_1$ = 1
  301. $$
  302. F_1 = 1\\
  303. = c_1(\frac{1 + \sqrt{5}}{2}) + c_2(\frac{1 - \sqrt{5}}{2})\\
  304. = \frac{c_1}{2} + \frac{c_2}{2} + \frac{c_1\sqrt{5}}{2} - \frac{c_2\sqrt{5}}{2}\\
  305. $$
  306. Let $c_1 = \frac{1}{\sqrt{5}}$, Let $c_2 = \frac{-1}{\sqrt{5}}$
  307. $$
  308. F_n = \frac{1}{\sqrt(5)}((\frac{1+\sqrt{5}}{2})^n-(\frac{1-\sqrt{5}}{2})^n)\\
  309. = \frac{(\frac{1+\sqrt{5}}{2})^n-(\frac{1-\sqrt{5}}{2})^n}{\sqrt{5}}
  310. $$
  311. ## Time Complexity
  312. Since we managed to find the closed form of the fibonacci sequence we
  313. can expect to see constant performance.
  314. ## Measured Performance
  315. ![FibPower Performance](media/fibonacci/ConstantTimeComplexity.png)