Contacts

We find N's Number of Fibonacci in three ways for an acceptable time: the basics of dynamic programming. Fibonacci numbers: Fibonacci Cycle and Recursion Recursion

Very often on a variety of Olympiads, tasks seem to be like this, which, as thought at first glance, can be solved with a simple busting. But if we calculate the number possible optionsI will immediately make sure of the ineffectiveness of this approach: for example, a simple recursive function below will consume substantial resources on the 30rd of Fibonacci, whereas at the Olympics, the decision time is often limited to 1-5 seconds.

Int FIBO (int n) (if (n \u003d\u003d 1 || n \u003d\u003d 2) (RETURN 1;) ELSE (Return FIBO (N - 1) + FIBO (N - 2);))

Let's think about why it happens. For example, to calculate FIBO (30), we first calculate FIBO (29) and FIBO (28). But at the same time, our program "forgets" that FIBO (28) we already calculated When searching for FIBO (29).

The main error of this approach "in the forehead" is that the same values \u200b\u200bof the function arguments are calculated repeatedly - and this is sufficient resource-intensive operations. Get rid of repetitive calculations will help us dynamic programming - This is a reception, when using which the task is divided into common and repeating subtasks, each of which is solved only 1 time - this significantly improves the efficiency of the program. This method is described in detail in, there are also examples of solving other tasks.

The easiest option to improve our function is to memorize what values \u200b\u200bwe have already calculated. To do this, you need to enter an additional array that will serve as "cache" for our calculations: before calculating the new value, we will check whether it has been calculated before. If they calculated, we will take a ready-made value from the array, and if not calculated - you will have to consider it on the basis of previous ones and memorize for the future:

Int cache; int fibo (int n) (if (cache [n] \u003d\u003d 0) (if (n \u003d\u003d 1 || n \u003d\u003d 2) (Cache [N] \u003d 1;) ELSE (Cache [N] \u003d FIBO (N - 1) + fibo (n - 2);)) Return cache [n];)

Since in this task to calculate the n-mind value, we will be guaranteed to be guaranteed (N-1) -e, it will not be difficult to rewrite the formula into an iterative form - we will simply fill out our array in a row until it comes to the desired cell:

<= n; i++) { cache[i] = cache + cache; } cout << cache;

Now we can notice that when we calculate the value f (n), the value F (N-3) is already guaranteed never will not need. That is, it is enough for us to store only two values \u200b\u200bin memory - F (n - 1) and f (n-2). Moreover, as soon as we calculated F (n), the storage F (N-2) loses any meaning. Let's try to write these reflections in the form of code:

// two previous values: int cache1 \u003d 1; int cache2 \u003d 1; // New value int cache3; for (int i \u003d 2; i<= n; i++) { cache3 = cache1 + cache2; //Вычисляем новое значение //Абстрактный cache4 будет равен cache3+cache2 //Значит cache1 нам уже не нужен?.. //Отлично, значит cache1 -- то значение, которое потеряет актуальность на следующей итерации. //cache5 = cache4 - cache3 => Through iteration will lose the relevance of Cache2, i.e. It should be Cache1 // in other words, Cache1 - F (N-2), Cache2 - F (n - 1), Cache3 - F (n). // Let n \u003d n + 1 (the number we calculate in the next iteration). Then n-2 \u003d n-3, n-1 \u003d n-2, n \u003d n-1. // In accordance with the new realities, we rewrite the values \u200b\u200bof our variables: cache1 \u003d cache2; cache2 \u003d cache3; ) Cout<< cache3;

The experienced programmer is clear that the code is higher, in general, nonsense, since Cache3 is never used (it is immediately recorded in cache2), and you can rewrite all iteration using only one expression:

Cache \u003d 1; cache \u003d 1; for (int i \u003d 2; I<= n; i++) { cache = cache + cache; //При i=2 устареет 0-й элемент //При i=3 в 0 будет свежий элемент (обновили его на предыдущей итерации), а в 1 -- ещё старый //При i=4 последним элементом мы обновляли cache, значит ненужное старьё сейчас в cache //Интуитивно понятно, что так будет продолжаться и дальше } cout << cache;

For those who can not understand how magic works with the residue from division, or just wants to see a more non-obvious formula, there is another solution:

Int x \u003d 1; int y \u003d 1; for (int i \u003d 2; I< n; i++) { y = x + y; x = y - x; } cout << "Число Фибоначчи: " << y;

Try to follow the execution of this program: you will be sure to correct the algorithm.

P.S. In general, there is a single formula for calculating any number of fibonacci, which does not require any iterations or recursion:

Const double SQRT5 \u003d SQRT (5); Const Double Phi \u003d (SQRT5 + 1) / 2; Int FIBO (INT N) (RETURN INT (POW (PHI, N) / SQRT5 + 0.5);)

But, as you can guess, the catch is that the price of calculating the degrees of the neuropal numbers is quite large, as is their error.

Fibonacci numbers - This is a number of numbers in which each next number is equal to the sum of the two previous ones: 1, 1, 2, 3, 5, 8, 13, .... Sometimes a row start from scratch: 0, 1, 1, 2, 3, 5, .... In this case, we will adhere to the first option.

Formula:

F 1 \u003d 1
F 2 \u003d 1
F n \u003d f n-1 + f n-2

Example of calculation:

F 3 \u003d F 2 + F 1 \u003d 1 + 1 \u003d 2
F 4 \u003d F 3 + F 2 \u003d 2 + 1 \u003d 3
F 5 \u003d F 4 + F 3 \u003d 3 + 2 \u003d 5
F 6 \u003d F 5 + F 4 \u003d 5 + 3 \u003d 8
...

Calculating the N-th number of the Fibonacci row using the WHILE cycle

  1. Assign the first first elements of the series to the FIB1 and FIB2 variables, that is, assign a unit to a variable.
  2. Request the user number of the element whose value he wants to get. Assign the variable number n.
  3. Perform the following actions n - 2 times, since the first two elements are already taken into account:
    1. Fix FIB1 and FIB2 by assigning the result of a variable for temporary storage of data, for example, FIB_SUM.
    2. Variable FIB1 Assign the FIB2 value.
    3. Variable FIB2 Assign the FIB_SUM value.
  4. Display the FIB2.

Note. If the user enters 1 or 2, the cycle body is never performed, the original FIB2 value is displayed.

fIB1 \u003d 1 FIB2 \u003d 1 N \u003d INPUT () N \u003d int (n) i \u003d 0 WHILE I< n - 2 : fib_sum = fib1 + fib2 fib1 = fib2 fib2 = fib_sum i = i + 1 print (fib2)

Compact code option:

fIB1 \u003d FIB2 \u003d 1 N \u003d INT (INPUT ( "Element number of a row of Fibonacci:")) - 2 while n\u003e 0: fib1, fib2 \u003d fib2, fib1 + fib2 n - \u003d 1 print (FIB2)

Output of Fibonacci numbers with a cycle for

In this case, not only the value of the foundation element of a series of fibonacci is displayed, but also all numbers to it inclusive. To do this, the output of the FIB2 value is placed in the cycle.

fIB1 \u003d FIB2 \u003d 1 N \u003d INT (INPUT ()) IF N< 2 : quit() print (fib1, end= " " ) print (fib2, end= " " ) for i in range (2 , n) : fib1, fib2 = fib2, fib1 + fib2 print (fib2, end= " " ) print ()

Example of execution:

10 1 1 2 3 5 8 13 21 34 55

Recursive calculation of the N-th number of the Fibonacci row

  1. If n \u003d 1 or n \u003d 2, return to the calling branch unit, since the first and second elements of the fibonacci range are equal to one.
  2. In all other cases, cause the same function with arguments n - 1 and n - 2. The result of two calls to fold and return to the caller branch of the program.

dEF FIBONACCI (N): IF N In (1, 2): Return 1 Return Fibonacci (N - 1) + Fibonacci (N - 2) Print (Fibonacci (10))

Suppose n \u003d 4. Then there will be a recursive Call Fibonacci (3) and Fibonacci (2). The second will return a unit, and the first will lead to two more challenges of the function: Fibonacci (2) and Fibonacci (1). Both calls will be returned by one, in the amount there will be two. Thus, the Fibonacci (3) call returns the number 2, which is summed up with a number 1 from the Fibonacci call (2). Result 3 returns to the main branch of the program. The fourth element of the Fibonacci range is three: 1 1 2 3.

The programmers of the Fibonacci number should already fond. Examples of their calculations are used everywhere. Everything from the fact that these numbers provide the simplest example of recursion. And they are a good example of dynamic programming. But is it necessary to calculate them so in the real project? Do not. Neither recursion nor dynamic programming is ideal options. And non-closed formula that uses floating point numbers. Now I will tell you how correctly. But first go through all the well-known solutions.

The code is designed for Python 3, although it must go on Python 2.

To begin with - I remind the definition:

F n \u003d f n-1 + f n-2

And F 1 \u003d F 2 \u003d 1.

Closed formula

Let's miss the details, but those who want to familiarize themselves with the conclusion of the formula. The idea is to assume that there is a certain X for which f n \u003d x n, and then find x.

What means

Reducing X N-2

We solve the square equation:

From where the "gold section" is growing φ \u003d (1 + √5) / 2. Substituting the initial values \u200b\u200band having done more computing, we get:

As we use to calculate F n.

From __Future__ import Division Import Math DEF FIB (N): SQRT5 \u003d Math.SQRT (5) Phi \u003d (SQRT5 + 1) / 2 RETURN INT (PHI ** N / SQRT5 + 0.5)

Good:
Quickly and just for small n
Poor:
Wanted floating comma operations. For large N, great accuracy will be required.
Evil:
The use of integrated numbers to calculate F n is beautiful from a mathematical point of view, but ugly - with a computer.

Recursion

The most obvious decision that you have already seen many times - most likely, as an example of what recursion is. I repeat it once again for completeness. In Python, it can be written in one line:

FIB \u003d LAMBDA N: FIB (N - 1) + FIB (N - 2) IF N\u003e 2 ELSE 1

Good:
Very simple implementation repeating mathematical definition
Poor:
Exponential execution time. For large n very slowly
Evil:
Stack overflow

Memory

The solution with recursion has a big problem: intersecting calculations. When FIB (N) is called, FIB (N-1) and FIB (N-2) are calculated. But when the FIB is considered (n-1), it will independently calculate FIB (N-2) - that is, FIB (N-2) will be calculated twice. If you continue the arguments, it will be seen that FIB (N-3) will be calculated three times, etc. Too many intersections.

Therefore, you just need to memorize the results in order not to count them again. The time and memory of this solution is spent linearly. In solving I use a dictionary, but you could use a simple array.

M \u003d (0: 0, 1: 1) DEF FIB (N): IF N In M: Return M [N] M [N] \u003d FIB (n - 1) + FIB (N - 2) RETURN M [N]

(In Python, it can also be done using a decorator, FUNCTOOLS.LRU_CACHE.)

Good:
Just turn recursion into a memorization solution. Turns the exponential time to execute in linear, for which it spends more memory.
Poor:
Spends a lot of memory
Evil:
It is possible to overflow stack, as in recursion

Dynamic programming

After the decision with the memorization it becomes clear that we need not all previous results, but only the last two. In addition, instead of starting with FIB (N) and go back, you can start with FIB (0) and go forward. The next code has a linear time execution, and the use of memory is fixed. In practice, the solution speed will be even higher, since there are no recursive challenges of functions and the associated operation. And the code looks easier.

This solution is often brought as an example of dynamic programming.

DEF FIB (n): a \u003d 0 b \u003d 1 for __ in range (n): a, b \u003d b, a + b Return A

Good:
Works quickly for small N, simple code
Poor:
Still linear execution time
Evil:
Yes, nothing is nothing.

Matrix algebra

And finally, the least illuminated, but the most correct solution, competently using time and memory. It can also be expanded on any homogeneous linear sequence. Idea in the use of matrices. It's easy enough to see that

And the generalization says that

Two values \u200b\u200bfor X, obtained by us earlier, of which one represented a gold cross section, are eigenvalues \u200b\u200bof the matrix. Therefore, another way of outputting a closed formula is the use of a matrix equation and a linear algebra.

So what is useful such wording? By the fact that the exhibition can be carried out for the logarithmic time. This is done through the construction of the square. The bottom line is that

Where the first expression is used for even A, the second for odd. It remains only to organize multiplications of matrices, and everything is ready. The following code is obtained. I organized a recursive implementation of POW, because it is easier to understand. Iterative version look here.

DEF POW (X, N, I, MULT): "" "Returns x to degree n. It assumes that i is a single matrix that varies with MULT, and n is a positive whole" "" IF N \u003d\u003d 0: Return I ELIF N \u003d\u003d 1: RETURN X ELSE: Y \u003d POW (X, N // 2, I, MULT) Y \u003d MULT (Y, Y) IF N% 2: Y \u003d Mult (x, y) RETURN Y DEF Identity_matrix (n): "" "Returns a single matrix n on N" "" R \u003d List (Range (N)) RETURN [FOR J IN R] DEF MATRIX_MULTIPLY (A, B): BT \u003d List (zip (* b) ) RETURN [FOR ROW_A IN A] DEF FIB (N): F \u003d POW ([,], N, Identity_Matrix (2), Matrix_multiply) Return F

Good:
Fixed memory, logarithmic time
Poor:
The code is more complicated
Evil:
Have to work with matrices, although they are not so bad

Comparison of speed

It is only a variant of dynamic programming and matrix. If they compare them by the number of characters, among N, it turns out that the matrix solution is linearly, and the solution with dynamic programming is exponentially. Practical example - FIB calculation (10 ** 6), the number that will have more than two hundred thousand characters.

N \u003d 10 ** 6
Calculate FIB_MATRIX: FIB (N) has only 208988 digits, the calculation took 0.24993 seconds.
Calculate FIB_DYNAMIC: FIB (N) is only 208988 digits, the calculation took 11.83377 seconds.

Theoretical comments

Not directly touching the code given above, this remark is still some interest. Consider the following graph:

Calculate the number of paths n from A to B. For example, for n \u003d 1 we have one way, 1. For n \u003d 2, we again have one way, 01. For n \u003d 3 we have two ways, 001 and 101 . It is quite simple to show that the number of paths n from A to B is equal to the accuracy F n. After writing off the arrangement matrix for the graph, we get the same matrix that was described above. This is a well-known result from the theory of graphs, which for a given matrix of adjacency A, the occurrence of C n is the number of paths n in the column (one of the tasks mentioned in the film "Umnitsa Will Hunting").

Why are there such designations on the roams? It turns out that when considering an infinite sequence of characters on endless in both sides of the sequence of paths on the column, you will get something called "final type shifts", which is a type of symbolic speaker system. Specifically, this final type subfimentary is known as the "shift of the golden section", and is set as a set of "forbidden words" (11). In other words, we will get infinite binary sequences in both directions and no pairs of them will be adjacent. The topological entropy of this dynamic system is equal to the golden section φ. I wonder how this number is periodically appearing in different fields of mathematics.



Did you like the article? Share it