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.

394 lines
10 KiB

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