Implementation and analysis of recursive and Fibonacci sequence recursive algorithms and iterative algorithms

recursion

The programming technique of calling the program itself is called recursion. Recursion
has two processes. Simply put, one is the recursive process and the other is the recursive process .
Two necessary conditions for recursion
1. There is a restriction condition . When this restriction condition is met, the recursion will no longer continue.
2. Each recursive call is getting closer and closer to this restriction
.
The essence of recursion is a function call , and its essence is to form and release a stack frame. There is a cost to calling a function, and this cost is reflected in the formation and release of the stack. On the frame: time + space. Recursion is the process of continuously forming stack frames.
Through the above, we can also understand some limitations of recursion.

  1. The resources of memory and CPU are limited, which means that reasonable recursion cannot continue indefinitely.
  2. Recursion cannot be used at any time, but must meet its own application scenarios, that is: sub-problems of the target problem can also be solved using the same algorithm. The essence is the idea of ​​divide and conquer.
  3. Core idea: reduce big things to small things + recursive export

Fibonacci Sequence

The Fibonacci sequence, also known as the golden section sequence, was introduced by mathematician Leonardoda Fibonacci using the example of rabbit reproduction, so it is also called the "rabbit sequence". is such a sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34,…

Before explaining the recursive Fibonacci sequence, we need to understand the nature of recursion

Through the above definition, we can define and use code to implement the Fibonacci sequence.
First, let’s look at the recursive version of the code.

recursive version

int Fib(int n)
{
    
    	
	if(n<=0)
	{
    
    
		return 0;
	}
	else if (1 == n || 2 == n)
	{
    
    
		return 1;
	} 
	return Fib(n - 1) + Fib(n - 2);
} 
int main()
{
    
    
	int n = 5;
	int x = Fib(n);
	printf("fib(%d): %d\n", n, x);
	return 0;
}

Insert image description hereThis is the recursive call theory diagram formed by the above formula when n=5, that is, Fib(5)

The essence of recursion mentioned above is function calling. It can be seen that when the value of n is larger (that is, the Fibonacci number required is larger), more and more functions are called, so what we are here The time cost and space cost are both very high
(time complexity is O(2^N) , space complexity is O(N) ).

It can be proved by the following code

int main()
{
    
    
	int n = 10; //n从40开始,时间就会慢慢增大得明显
	//使用win提供的GetTickCount()函数,来获取开机到现在的累计时间(单位毫秒)
	double start = GetTickCount(); 
	int x = Fib(n);
	double end = GetTickCount();
	printf("%lf ms\n", (end - start));//二者进行相减就可以算出递归所用时间
	system("pause");
	return 0;
}

When n starts from 40, the time will obviously become larger. At this time, even if n only adds 1 each time to test the time, the time will become much larger. This is because it can be seen from the recursive call theory diagram above that when F When (4) changes to F(5), F(3) under the F(4) branch and the following series will appear on the other branch of F(5). It can be seen that when F(40) becomes When it is F(41), F(39) and its series will appear on the other end branch of F(41), so even if you only add 1 each time, the change is huge.

Next, we use the idea of ​​iteration to improve time and space efficiency (to put it bluntly, iteration is a cycle)

Iterative version

int Fib(int n)
{
    
    
	int *dp = (int*)malloc(sizeof(int)*(n+1)); //多加一个是为了下标保持一致方便计算
	//[0]不用(当然,也可以用,不过这里我们从1开始,为了后续方便)
	dp[1] = 1;
	dp[2] = 1;
	int i = 3;
	while(i<=n)
	{
    
    
		dp[i] = dp[i - 1] + dp[i - 2];
		i++;
	} 
	int ret = dp[n];
	free(dp);
	return ret;
}

In fact, this is also a dynamic programming algorithm, but we can also see some drawbacks, that is, we save the Fibonacci sequence from the 1st to the nth digit in the heap space that we malloc (the space is complex Time O(n)), however, any Fibonacci number is only related to the first two numbers, so we can perform further optimization

Enhanced iterative version

int Fib(int n)
{
    
    
	int first = 1;
	int second = 1;
	int third = 1;
	while (n > 2){
    
    
		third = second + first;
		first = second;
		second = third;
		n--;
	} 
return third;
}

This iterative version of the code has been greatly optimized in both time and space complexity.

Summarize

In summary, we can also see from the example of Fibonacci that recursive algorithms are generally not very efficient, while iteration is more efficient than recursion. However, the code for recursion is relatively simpler than iteration. Strong readability
The scenarios where recursion is often used are:
1. When the problem and sub-problems have a recursive relationship (factorial).
1. Data structures with recursive properties (linked lists, trees).

Therefore, the recursive function is just a problem-solving technique. Like other techniques, it also has certain flaws. Specifically: the time overhead and memory overhead of the recursive function are very large, and may even cause the program to crash in extreme cases. ( So ​​recursive functions must return after recursion, otherwise it will eventually cause stack overflow ). So we must always consider this when using recursion in the future.

Guess you like

Origin blog.csdn.net/kklovecode/article/details/132259395