<<The algorithm is beautiful>>——(2) Detailed explanation of recursive thought

content

foreword

Basic concepts of recursion

Three elements of recursion

Cake-Cut Thinking: Recursive Simple Exercises

Using Recursion Formula to Solve the Greatest Common Divisor

There's nothing better: Insertion sort recursively

Recursive solution to binary search

Multibranch Recursion: The Fibonacci Sequence 

Talking about some optimization ideas of recursion

topic combat

final summary


foreword

When I first came into contact with recursion, I believe that most of my friends must be as confused as I am, and they sighed that it was amazing, so many things have been done in such a short line of code.

In fact, don't be afraid of it, it's easy to learn. It's nothing more than thinking about how to use the concepts of recursion boundary and recursion . Decompose the original problem into several sub -problems, we only need to write the recursion and boundary, and leave the rest to the computer. , We are the boss, it is for us. We try to draw a recursive diagram to help understand the code that we do not understand, and try not to think about their stack stacking process in our minds. Our brains can push several stacks! ! Instead, he confuses himself.

Recently, I have read a lot of knowledge about recursion, and I would like to share with you my views, hoping to bring you a little help.

Basic concepts of recursion

There is a seemingly joking definition of recursion: "To understand recursion, you must first understand recursion until you can understand recursion", but this explanation of recursion is very intuitive. Recursion is to repeatedly call its own function, but every time The problem is narrowed down until the range is narrowed down to the result that the boundary data can be directly obtained, and then the corresponding solution is obtained on the way back . From this point of view, recursion is very suitable for implementing the divide and conquer idea.

Three elements of recursion

In order to better elicit the three elements, let's take a simple example: use recursion to solve the factorial of n

1. Find duplicates (sub-problems) - recursive

First give the calculation formula of n!: n!=1*2*3*...*n; this recursive form n!=n*(n-1)!, so the scale is n The problem is transformed into a problem of size n-1. If n! is represented by f(n), it can be written as f(n)=f(n-1)*n ( recursive ), so that we reach our The purpose of making the scale smaller - subproblems. (Note: Finding recursion is the most difficult step in recursion. We need to contact more questions to summarize our experience. Next, I will practice a few questions with you. We must practice more and try to change the encountered loop to recursion)

int f(n)
{
   return f(n-1)*n
}

2. Find the amount of change in repetition - parameter

The amount of change should be used as a parameter . This problem has only one variable n, which is obviously used as a parameter. The role of this point is not so important in this problem. I will explain an array sum problem below, reflecting the its worth.

3. Find the trend of parameter change - design export

The above code has a situation where it calls itself, so this is called recursion, but the above code is not standardized enough, and even there is a problem, it will keep calling itself, falling into a bottomless pit, and finally a stack overflow error occurs.

Then we need to find an "exit" for it, that is, we need to find out what the parameter is, the recursion ends, and then return the result directly.

Obviously in the above example, when n==1, f(1)=1 for export; let's improve the code below

int f(int n)
{
	if (n == 1)
	{
		return 1;
	}
	return f(n - 1) * n;
} 

At this point, the code inside our f(n) function is complete.

As we said above, if you don’t understand the code, you need to draw a recursive diagram to help it understand. Let’s take this as an example to draw a diagram with you.

In order to better grasp the three elements of recursion, let me do some exercises with you.

Cake-Cut Thinking: Recursive Simple Exercises

Array summation:

#include<iostream>
int main()
{
   int a[]={1,2,3,4,5,6,7};
   //递归函数sum
   return 0;
}

1. Find repetitions - recursive

To find a recursive formula, we will follow the original problem into several sub-problems, just like we cut a cake, first cut a piece and eat it, and use the recursion to give the rest to the next step to eat, this problem we only draw the first element out , the rest is handed over to recursive processing

int sum(int a[])
{
   return a[0]+sum(a);
}

 We're done

2. Find the amount of change in repetition - parameter

In this process of repetition, we will find that the range of the array is constantly shrinking, the starting point of the array is changing , and we find that something is constantly going to the right. But the recursive formula written above has no changing range, it has always been Recursion from the beginning. After analyzing this, we will find that we should find the amount of change in the repetition to add a parameter. I add another parameter n here to record the length of the array

int sum(int a[],int n,int begin)
{
   return a[begin]+sum(a,n,begin+1);
}

3. Find the trend of parameter change - design export

If begin is equal to the index of the last element of the array, we return directly, which is the exit


int sum(int a[],int n,int i)
{
    if (i == n-1)
	{
		return a[i];
	}
	return a[i] + sum(a, n,i+1);
}

This is done. Isn’t it very simple? There must be some big bosses complaining that this topic is simple. At the beginning, let’s start with simplicity and use the three elements proficiently. It’s the same routine when encountering problems. Not much to say, another one

flip the string

int main()
{
	string sa = "wewe89r";
	cout << f(sa);
	return 0;
}

1. Find repetitions - recursive

For this question, we can still use the cake-cutting thinking to find the recursive formula. In order to make it easier to understand, I will draw a picture

int f(string str)
{
 
  return f(str.substr(1))+str.substr(0,1);   //substr(0,1)表示从下标0开始取一个字符形成的串                                    
                                             
                                             //substr(1)表示从下标1开始到结尾形成的串
}

2. Find the amount of change in repetition - parameter

The length of the string in this question is constantly changing, so we use the length of the string as a parameter, and here I use the substr function, no need to add new parameters, but the essence is to continuously reduce the length of the string .

3. Find the trend of parameter change - design export

If the length of the string is less than or equal to 1, the program is about to end, find this exit

string  f(string str) {
	int len = str.length();
	if (len <= 1)
		return str;
	return f(str.substr(1)) + str.substr(0, 1); //substr(0,1)表示从下标0开始取一个字符形成的串
}                                               //substr(1)表示从下标1开始到结尾形成的串

Well, this question is over here. I believe that everyone here has a further understanding of the three elements. After practice questions, you can think according to this model. It is not realistic to rely on a blog to deepen a thought. Yes, so you have to practice on your own.

This one is here, and I will take you to further study below.

Using Recursion Formula to Solve the Greatest Common Divisor

The greatest common divisor of positive integers a and b refers to the greatest common divisor of all common divisors of a and b. For example, the greatest common divisor of 4 and 6 is 2, and the greatest common divisor of 3 and 9 is 3. Generally, gcd(a, b) is used to represent the greatest common divisor of a and b, and the Euclidean algorithm is commonly used to solve the greatest common divisor.

Euclid's algorithm is based on the following theorem:

Suppose a and b are positive integers, then gcd(a,b)=gcd(b,a%b); (the proof is skipped)

From the above theorem, it can be found that if a<b, then the result of the theorem is that a and b are exchanged; if a>b, then the data size can always be reduced by this theorem, and the reduction is very fast. This seems to get the result quickly, and knowledge needs one more thing: the recursion boundary, that is, how far the data size can be reduced so that the result can be calculated. It is very simple, as we all know: the greatest common divisor of 0 and any integer a is a, This conclusion can be regarded as a recursive boundary. From this, we can easily think of writing it in a recursive form, because the two keys of recursion have been obtained:

1. Recursive formula: gcd(a,b)=gcd(b,a%b);

2. Recursive boundary: gcd(a,0)=a.

So get the following code:

int gcd(a,b)
{
if(a==0) return a;
else return gcd(b,a%b);
}

There's nothing better: Insertion sort recursively

 For several algorithms, I have already introduced them in detail. For those who have forgotten, you can read my blog: Seven Common Sorting Algorithms

Go directly to the code: (or follow the three elements above)

#include<iostream>
using namespace std;
void InsertSort(int* a, int n)
{
	if (n == 0)
	{
		return;
	}
	InsertSort(a, n - 1);
		int x = a[n];
		int index = n - 1;
		while (index>-1&&x < a[index])
		{
			a[index + 1] = a[index];
			index--;
		}
		a[index+1] = x;
	}
int main()
{
	int a[] = { 1,3,4,2,6,5 };
	InsertSort(a, sizeof(a)/sizeof(int)-1);
	for (auto x : a)
	{
		cout << x << " ";
	}
	return 0;
}

Recursive solution to binary search

 The full range binary search is
 equivalent to three sub-problems
* left search (recursion)
* middle ratio
* right search (recursion)

#include<iostream>
using namespace std;
int binarySearch(int* a, int left, int right, int key)
{
	while (left < right)
	{
		int mid = left + ((right - left) >> 1);
		int number = a[mid];
		if (number < key)
		{
			return binarySearch(a, mid + 1, right, key);
		}
		else if (number > key)
		{
			return binarySearch(a, left, mid - 1, key);
		}
		else
			return mid;
	}
}
int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9,10 };
	int find=binarySearch(a, 0, sizeof(a)/sizeof(int)-1, 5);
	cout << find << endl;
	return 0;
}

Multibranch Recursion: The Fibonacci Sequence 

The Fibonacci sequence satisfies the sequence of f(0)=1, f(1)=1, f(n)=f(n-1)+f(n-2) (n>=2), the front of the sequence Several items are 1,1,2,3,5,8,,,. Since the recursive boundary is already familiar with f(0)=1 and f(1)=1 from the definition, and the recursive formula is f(n)=f(n-1)+f(n-2)(n>=2 ), so we can write the program for the nth item by following the way of solving the factorial of n:

int f(int n)
{ 
if(n==1||n==2)
 return 1;
else
 return f(n-1)+f(n-2);
}

We also know that although writing code in this way is concise and easy to understand, it is very inefficient. Where is the inefficiency? Assuming n = 15, draw the recursion tree: 

How to understand this recursion tree, when you want to calculate f(15) , you need to calculate f (14) and f (13 ), to calculate f(14) , you need to calculate  f(13) and f(12 ), and so on . Finally , when f(1) is encountered, the result is known, and   the result can be returned directly, and the recursive tree no longer grows downward.  f(2)

It is obvious that the f(13) and f(12) equations are repeatedly calculated when performing recursive calculations. The larger the number of calculations, the more repeated calculations. This is a terrible thing, so we have to find a way to optimize it. .

Talking about some optimization ideas of recursion

Once the problem is clarified, in fact, half of the problem has been solved. Since the reason for the time-consuming is repeated calculation, then we can create a "memorandum". After calculating the answer to a sub-question, don't rush to return, first write it down in the "memo" and then return; each time a sub-question is encountered Go to the "Memo" and check it out. If you find that this problem has been solved before, just use the answer directly, and don't waste time calculating it.

Usually an array is used as this "memo", of course you can also use a hash table (dictionary), the idea is the same.


 int f(int n)
 { 
    if(n ==0||n==1)
    { 
      return 1; 
    } 
     //先判断有没计算过 
     if(arr[n] != -1)
    { 
       // 已经计算过,不用再计算了
       return arr[n];
    }
     else
     {
      // 没有计算过,递归计算,并且把结果保存到 arr数组里 
        arr[n] = f(n-1) + f(n-1); 
        reutrn arr[n]; 
      } 
 }

 So far, the recursive solution with memoization is as efficient as the iterative dynamic programming solution. In fact, this solution is almost the same as the common dynamic programming solution, except that this solution is a "top-down" "recursive" solution. Our more common dynamic programming code is a "bottom-up" "recursive" solution. push" to solve.

What is " top-down "? Note that the recursion tree (or graph) we just drew extends from top to bottom, from a larger-scale original problem, for example  f(20), gradually decomposes the scale downward until  it f(1) matches  f(2) these two base cases, and then layer by layer Returning the answer is called "top-down".

What is " bottom-up "? In turn, we start directly from the bottom, the simplest, the smallest problem size, the sum of the known results  (base case) f(1) and  f(2)push up until we reach the answer we want  f(20). This is the idea of ​​"recursion", which is also the reason why dynamic programming generally gets rid of recursion, but completes the calculation by loop iteration.

Self-improvement:

public int f(int n)
 {
    if(n == 1||n==2)
     return 1; 
    int f1 = 1;
    int f2 = 2; 
    int sum = 0; 
    for (int i = 3; i <= n; i++)
     { 
     sum = f1 + f2;
      f1 = f2;
       f2 = sum; 
     } 
     return sum; 
}

Of course, there are also some recursive acceleration - pruning , recursion and backtracking operations to optimize recursion.

topic combat

The following two more classic recursive exercises, you can practice your hands

jumping steps

Tower of Hanoi

final summary

The above is my understanding of recursion. I hope it can play a role in attracting others. If there is anything you don’t understand, you can leave a message in the comment area and discuss it together. In a word, in a word, it is necessary to be familiar with the three elements of recursion I mentioned above Practice summarization and mastery.

Guess you like

Origin blog.csdn.net/m0_58367586/article/details/123752617