Pages

Tuesday, January 5, 2021

Java Fibonacci Series Recursive Optimized using Dynamic Programming

1. Overview

In this article, we will learn how to print the fibonacci series and find the nth fibonacci number using recursive approach.

Printing the Fibonacci series be done using the iterative approach using while and for loop.

In the following sections we will try to run the program for below scenarios.

  • Print the fibonacci series for the given number
  • Find the nth fibonacci number

In each approach, we will try to see the amount of time taken to process the logic and how it can be optimized using dynamic programming memoization technique.

Java Fibonacci Series Recursive Optimized using Dynamic Programming


2. Print the Fibonacci series using recursive way

Below program to display the first n Fibonacci number using recursive approach. For each input, we are going to print the time taken and compare for different inputs.


package com.javaprogramto.programs.numbers.fibonacii;

import java.time.Instant;

public class PrintFibonaciiSeriesRecursive {

	public static void main(String[] args) {

		long fibResult = 0;

		System.out.println("First 30 fibonacii series numbers : ");
		long startTime = Instant.now().toEpochMilli();

		for (int i = 1; i < 30; i++) {
			fibResult = fibonacii(i);
			System.out.print(fibResult + " ");
		}
		long endTime = Instant.now().toEpochMilli();

		System.out.println("\nExecution time " + (endTime - startTime) + " ms");

		System.out.println("\nFirst 50 fibonacii series numbers : ");
		startTime = Instant.now().toEpochMilli();

		for (int i = 1; i < 50; i++) {
			fibResult = fibonacii(i);
			System.out.print(fibResult + " ");
		}
		endTime = Instant.now().toEpochMilli();

		System.out.println("\nExecution time " + (endTime - startTime) + " ms");

	}

	// fibonacii recursive
	private static long fibonacii(long n) {

		if (n <= 2) {
			return 1;
		}
		long fibNumber = fibonacii(n - 1) + fibonacii(n - 2);

		return fibNumber;
	}
}


Output:

First 30 fibonacii series numbers : 
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 
Execution time 6 ms

First 50 fibonacii series numbers : 
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 165580141 267914296 433494437 701408733 1134903170 1836311903 2971215073 4807526976 7778742049 
Execution time 53397 ms

From the output, we can understand that to print first 30 fibonacci numbers it took just 6 milli seconds but to print the first 50, it took around 54 seconds.


Time complexity: O(2^n)

Space complexity: 

3. Print the Fibonacci series using recursive way with Dynamic Programming

In the above program, we have to reduce the execution time from O(2^n).

If you observe the above logic runs multiple duplicates inputs.

Look at the below recursive internal calls for input n which is used to find the 5th Fibonacci number and highlighted the input values that are processed by our function multiple times.

2nd Fibonacci number is calculated 3 times.

Print the Fibonacci series using recursive way with Dynamic Programming-1


3rd fibonacci number is calculated twice.


Print the Fibonacci series using recursive way with Dynamic Programming-2

If the input value is 50 there are many inputs are reprocessed for the same inputs which kills the system.

From these analysis, if we can store these values in the memory we can avoid reprocessing them and retrieve the values from memory. This process is called as memoization.

Memoization make sure alway it runs for the different inputs and not for the same inputs again. Instead, gets the values from previous results from memory.

We can use HashMap for storing the intermediate key value pairs.

The below is the optimized program that takes less time for bigger inputs.


package com.javaprogramto.programs.numbers.fibonacii;

import java.time.Instant;
import java.util.Map;

import org.apache.commons.collections4.map.HashedMap;

public class PrintFibonaciiSeriesRecursiveOptimized {

	public static void main(String[] args) {

		long fibResult = 0;
		Map<Integer, Long> memory = new HashedMap<>();

		System.out.println("First 30 fibonacii series numbers : ");
		long startTime = Instant.now().toEpochMilli();

		for (int i = 1; i < 30; i++) {
			fibResult = fibonacii(i, memory);
			memory.clear();
			System.out.print(fibResult + " ");
		}
		long endTime = Instant.now().toEpochMilli();

		System.out.println("\nExecution time " + (endTime - startTime) + " ms");

		memory.clear();
		
		System.out.println("\nFirst 50 fibonacii series numbers : ");
		startTime = Instant.now().toEpochMilli();

		for (int i = 1; i < 50; i++) {
			fibResult = fibonacii(i, memory);
			memory.clear();
			System.out.print(fibResult + " ");
		}
		endTime = Instant.now().toEpochMilli();

		System.out.println("\nExecution time " + (endTime - startTime) + " ms");

	}

	// fibonacii recursive
		private static long fibonacii(int n, Map<Integer, Long> memory) {
			
			if(memory.get(n) != null) {
				return memory.get(n);
			}

			if (n <= 2) {
				return 1;
			}

			long fib = fibonacii(n - 1, memory) + fibonacii(n - 2, memory);
			
			memory.put(n, fib);
			
			return fib;
		}
}

Output:

First 30 fibonacii series numbers : 
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 
Execution time 2 ms

First 50 fibonacii series numbers : 
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 165580141 267914296 433494437 701408733 1134903170 1836311903 2971215073 4807526976 7778742049 
Execution time 3 ms

From the above output, you can see the for bigger inputs just took 3ms which is fabulous. We brought down to 3 milli seconds from 54 seconds. 

This is the power of the dynamic programming technique.


4. Find the nth Fibonacci number using recursive way

The below example program to get the nth fibonacci number from the series recursively.


package com.javaprogramto.programs.numbers.fibonacii;

import java.time.Instant;

public class FindFibonaciiNumberRecursive {

	public static void main(String[] args) {
		long startTime = Instant.now().toEpochMilli();
		long finResult = fibonacii(30);
		long endTime = Instant.now().toEpochMilli();

		System.out.println("30th fiboncaii number - " + finResult + " execution time " + (endTime - startTime) + " ms");

		startTime = Instant.now().toEpochMilli();
		finResult = fibonacii(50);
		endTime = Instant.now().toEpochMilli();

		System.out.println("50th fiboncaii number - " + finResult + " execution time " + (endTime - startTime) + " ms");
	}

	
	// fibonacii recursive
	private static long fibonacii(long n) {

		if (n <= 2) {
			return 1;
		}

		return fibonacii(n - 1) + fibonacii(n - 2);
	}
}

Output:

30th fiboncaii number - 832040 execution time 5 ms
50th fiboncaii number - 12586269025 execution time 34413 ms


Take taken for 30th fibonacci number is 5 ms

50th Fibonacci number is 34 seconds.

Time Complexity - O(2^n)

Space Complexity - O(2^n)


5. Find the nth Fibonacci number using recursive way Using Dynamic Programming

Next, let us simplify the above code using memoization technique using hashmap


package com.javaprogramto.programs.numbers.fibonacii;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

public class FindFibonaciiNumberRecursiveOptimized {

	public static void main(String[] args) {
		
		Map<Integer, Long> memory = new HashMap<>();
		
		long startTime = Instant.now().toEpochMilli();
		long finResult = fibonacii(30, memory);
		long endTime = Instant.now().toEpochMilli();

		System.out.println("30th fiboncaii number - " + finResult + " execution time " + (endTime - startTime) + " ms");

		// clearing the memoization map
		memory.clear();
		
		startTime = Instant.now().toEpochMilli();
		finResult = fibonacii(50, memory);
		endTime = Instant.now().toEpochMilli();

		System.out.println("50th fiboncaii number - " + finResult + " execution time " + (endTime - startTime) + " ms");
	}

	
	// fibonacii recursive
	private static long fibonacii(int n, Map<Integer, Long> memory) {
		
		if(memory.get(n) != null) {
			return memory.get(n);
		}

		if (n <= 2) {
			return 1;
		}

		long fib = fibonacii(n - 1, memory) + fibonacii(n - 2, memory);
		
		memory.put(n, fib);
		
		return fib;
	}
}


Output:

30th fiboncaii number - 832040 execution time 0 ms
50th fiboncaii number - 12586269025 execution time 0 ms


In this approach, if we want to calculate the 5th Fibonacci number from series, it calculates the only the lest side values from the recursive tree.

Find the nth Fibonacci number using recursive way Using Dynamic Programming

So, it reduces the logic execution from 2^n to n times.

Time Complexity : O(2^n)

Space Complexity : O(n) because it still holds the n level runtime stack.


6. Conclusion

In this article, we have seen how to implement the fibonacci series and find nth fibonacci number using recursive approach and optimized way using dynamic programming technique.

GitHub

Java Programs

HashMap Examples

2 comments:

  1. Why would anyone do the first example? Calling a recursive function inside a for loop is just wrong. The for loop & recursion are meant to replace each other.

    You would just call the function passing in an output mechanism. You print each number just before you return from the function with the computed value.

    ReplyDelete
  2. Hello Joseph,

    The first example is to show the execution time for smaller nth fibonacci number and larger nth fibonacii number.

    But in the real time, it is really not suggestible to use. Here the intention is show the execution timings.

    The later section has shown the best ways to tackle the time complexity.

    ReplyDelete

Please do not add any spam links in the comments section.