recursive algorithm

Time:2020-12-15

catalog:
1. Simple recursive definition
2. The difference and relation between recursion and loop
3. Classical application of recursion

1. Simple recursive definition

What is recursion? (first define a relatively simple statement, which may not be true for the sake of understanding)

Recursion: infinitely call its own function, each call will change a key variable, until the key variable reaches the boundary, it will not be called again.

For example, I want you to get an n

You said I could use loops (yes, but now I’m learning recursion)

int factorial(int x,int ans)
{
    if(x==1)
       return  ans;
    factorial(x-1,ans*x);
}

How about, for the C foundation, if the grasp of the word is OK, this code should be very easy to understand. Recursion, as the name suggests, is “Di” and “Gui”. That is to say, when writing every recursive function, we should consider clearly before writing, which embodies “recursion” and “return”.

However, the recursive function is often more complex, “recursive” and “return” do not seem so obvious, which requires us to further understand the idea of recursive algorithm.

For example, I want you to find the greatest common divisor of two numbers by the method of rolling and dividing. The recursive function is as follows:

int gcd(int a,int b)
{
    return a%b==0?b:gcd(b,a%b);
}

This is a very common code, we know, in the process of learning is not to understand the least. So now take a closer look. Here, “delivery” and “return” are on the same line. Judge firsta==b?(we can imagine it as the content of “return”, if this condition is met). Of course, if this judgment is not met, then continue to “deliver”, that is to say, to continuegcd(b,a%b);

If you look at this, isn’t recursion another way to loop?

I’m half right, but recursion is a kind of thought, which can’t be explained at present. We need to compare the similarities and differences between loop and recursion

2. The difference between recursion and loop is relation

Similarities:
(1) All throughControl the boundary of a variableOr more)To change multiple variables repeatedly in order to get the required value;
(2) All of them are based on pre-designed inference to achieve a certain value; (please note that in this case, the loop should pay more attention to the process, while recursion tends to the result.)

difference:
(1) Recursion is usually the reverse thinking, and “recurrence” and “return” are not always easy to find (more difficult to understand); while the cycle from the start condition to the end condition, including the intermediate loop variables, needs to be expressed (relatively concise and clear).

To put it simply: if you can use a loop, recursion can be realized, but if you can use recursion, the loop may not be able to. Because some topics only focus on the ending condition and process of the cycle, which is not easy to express (that is to say, it is not easy to write with a cycle); and (2) only pay attention to the number of cycles without paying attention to the starting and ending conditions of the cycle (this cycle is more difficult to start with).

3. Classical application of recursion

Let’s have a look““Hanoi Tower problem”

As shown in the figure, the Hanoi Tower problem means that there are three poles a, B and C. There are several dishes on the C rod. Move all the dishes from the a rod to the C rod, and only one plate can be moved at a time. The large dishes cannot be stacked on the small ones. How many times should I move at least?

recursive algorithm

This is a common example of loops that only focus on the number of cycles. We know that it’s a bit difficult to start with loops (at the current level of authors), but recursion is easy to write.

Hanoi Tower, what the hell, I can’t?

Take your time.

First of all, we need a little thinking: to solve the problem of moving n plates from a to C, I only need to move n-1 plates from a to B, then move the bottom nth plates from a to C, and finally move n-1 plates from B to C (this is done).

Wait, so how do you move n-1 plates from a to B?

Well, that means you’re getting started with recursion.

Think the same way: to solve the problem of moving n-1 plates from a to B, I only need to move n-2 plates from a to C, then move the penultimate plate from a to B, and finally move n-2 plates from C to B (this is done).

This is the recursive “delivery”!

What about “return”? When n = = 1?

Bingo

Int i; // record steps  
//I is the number of steps to be taken, and the plate numbered n is moved from the from column to the to column  
void move(int n,char from,char to){  
    Printf ("step% d: put plate% d% C - >% C, I + +, N, from, to)";  
}  
  
//Hanoi Tower recursive function  
//N is the number of "disks" to move from the starting column to the target column  
//start_ POS is the starting column, tran_ POS stands for transition column, end_ POS represents the target column  

void Hanio(int n,char start_pos,char tran_pos,char end_pos)
{  
    If (n = = 1) // obviously, when n = = 1, we just need to move the disk directly from the starting column to the target column  
        move(n,start_pos,end_pos);   
    else
    {  
        Hanio(n-1,start_ pos,end_ pos,tran_ At the beginning, n-1 plates are moved to the transition column  
        move(n,start_ pos,end_ Then move the bottom tray directly to the target column  
        Hanio(n-1,tran_ pos,start_ pos,end_ Then repeat the above steps to recursively process n-1 plates placed on the transition column  
                                                  //At this time, the original starting column is used as the transition column (because the starting column is empty)  
    }  
}

In fact, the idea of stack has been used here (i.e. the top priority is to consider changes), but sometimes recursion can be really understood as stack!

At this stage, I believe we should have understood. In fact, a loop is a process in which the control variables go from the start condition to the end condition (other variables are also changed in the process of the loop). Therefore, it is necessary to control variables, start conditions and end conditions. But recursion only tells you what “return” is and how to “pass”, no matter how the process is, as long as the calculation results.

(2) Recursion can be multiple “recursion” or “return”; while a loop is controlled by only one variable from beginning to end (even if several variables are controlled at the same time), there is only one exit, and each loop is only a “recurrence”.

Let’s take another example

Use the dichotomy idea to build a binary tree (usually recursive implementation), such asLine segment tree

//Root node serial number  
//Left boundary maintained by left node  
//Right boundary maintained by right node
void build(int root,int left,int right)
{
    if(left==right)
      return ;
    int mid=(left+right)/2;
    build(root*2,left,mid);
    build(root*2+1,mid+1,right);
}

If you are a novice, it doesn’t matter if you don’t understand it. Now the most important thing is to understand: there is only one “return” in this program, but there are two “delivery”.

So what if you have learned a little but don’t understand this part? Don’t worry. Let me explain

In fact, the two “delivery” are carried out according to the sequence, and the second “delivery” will not be executed until the first “delivery” is completed (that is, after the “return” condition is reached). In other words, when building a tree, it is usually not built at the same time, but a subtree is built first, and then another subtree is built after the subtree is completed.

Then we will ask, after a subtree is built, will the root value not change? How can we build another subtree after the root value has changed?

The root value does not change! Please note that root * 2 is written in the recursive function, and there is no assignment in fact? Why write like this? Because if you don’t write it like this, if you write it outside directly, after each child node reaches the leaf node, it needs to backtrack layer by layer (mentioned here)to flash backHowever, backtracking can cause a lot of unnecessary time complexity and reduce the efficiency of recursion (in fact, the time efficiency of recursion is a little low).

So so far, I’ve just introduced some common simple recursion, but in the next step, I need to talk about some deeper knowledge.

First of all, we should understand what backtracking is

Backtracking: a step performed in recursion because the amount of change needs to be reversed to a certain position.

Let’s start with a simple onePrime ringQuestion:

Give n consecutive positive integers from 1 to n (where n is temporarily equal to 6), and fill these n numbers in the N circles as shown in the figure below (of course, they are not repeated and omitted). The requirement is that the sum of the number above each position and the number adjacent to it is a prime number, and print and output the final conditions that meet the conditions.

recursive algorithm

First of all, understand that the starting condition is 1, fill in 1 in the first position, and then find a number in the remaining n-1 numbers that satisfies the sum of 1 and is a prime number (of course, if there are more than one, consider the first one first). Next, continue to find a number from the remaining n-2 numbers whose sum is a prime number (if there are more than one, the same as above)… If the sum of the last number and the initial number 1 is a prime number, this situation will be satisfied

But it’s not as simple as you think… (tell me what to do if the sum of the remaining number and the current number is a prime in the process of searching? Online, etc.)

This shows that such a road is a way of thinking, you have to go back! This is in line with our definition of backtracking. At this time, the amount of change needs to go backward and look for another direction. (it’s better to raise chestnuts)

for instance:

1 – > 2 – > 3 – > 4 suddenly found that 5 and 6 did not meet the requirements

Then go back and get ready to find another number that meets the requirements

Besides 4, 3 and 5 or 3 and 6 do not meet the requirements

Then go back and get ready to find another number that meets the requirements

1 – > 2 – > 5 – > 6, and then found that 6 or 6 and 4 did not meet the requirements

… (want to continue? Are you trying to kill me? Come on, I’m tired too. Just watch one or two, ah! )
Finally, it is found that one of the conditions is satisfied

1->4->3->2->5->6

We should have understood that the retrogression above is actually backtracking. (for the time being, we can’t blame you if you are wrong.)

In fact, recursion + backtracking is already the content category of DFS (depth first search).

void dfs(int x)
{
    If (x = = n + 1 & & prime (a [X-1] + a [1]) // if the conditions are met, the data can be printed out, which is called "return"
    {
        for(int i=1;i<n;i++)
          cout<<a[i]<<" ";
        cout<<a[n]<<endl;
    }
    Else // or continue to "pass"
    {
        for(int i=2;i<=n;i++)
        {
            if(!vis[i]&&prime(i+a[x-1]))
            {
                [vis] = 1 is already used
                a[x]=i;
                DFS (x + 1); // the entry of "delivery"
                Vis [i] = 0; // note that the backtracking point is here
            }
        }
    }
}

You may have understood it before, such as “delivery” and “return”, VIS [] tag array and so on. But what does the last vis [i] = 0 mean? Isn’t it redundant?

No more! I have already told you about the “working principle” of recursion by taking a tree. It first recurses infinitely, then goes back to a position above and continues to recurs in other directions after reaching a certain condition. The VIS [i] = 0 is a clear mark of the current number, which means that the recursive content is cleared from the current node (that is, backtracking). Then according to the loop, the next direction of the continued recursion.

This is also the classic idea of DFS!

Therefore, speaking of this point, not talking about DFS seems to attract everyone’s appetite. So next, let’s talk about recursion in DFS.

For example, 1312 on HDUhttp://acm.hdu.edu.cn/showproblem.php?pid=1312

Let me briefly explain the meaning. As shown in the figure below, judge the number of “.” and “@” in a graph including the @ sign. There is a restriction that if “.” is blocked by ", then " cannot be accessed.

6 9 (row and column respectively)

....#.
.....#
......
......
......
......
......
#@...#
.#..#.

45

For example, there are three “.” in this data that are not accessible because they are trapped by the boundary and “black”. Therefore, only 45 points meet the requirements.

The idea of this problem is to start from the ‘@’ point, BFS search each point, divided into four directions of up, down, left and right, respectively recursive search.

int cnt,a[4]={-1,0,1,0},b[4]={0,1,0,-1},n,m,vis[22][22];
char s[22][22];      
void dfs(int x,int y)
{
    For (int i = 0; I < 4; I + +) // loop search in four directions
    {
        int p=x+a[i];
        int q=y+b[i];
        If (P > = 0 & & P < M & & Q > = 0 & & Q < n &! Vis [P] [q] & & s [P] [q] = = ') // judge whether the recursion condition is included in the array boundary, and the point is not marked 
        {
            Vis [P] [q] = 1; // mark the point 
              CNT + +; // count variable plus one 
            DFS (P, q); // recursive search 
        }
    }
}

Please note: in this question, we can judge whether the next point to be searched is ා, so we can avoid backtracking and reduce the time complexity.

And you can see that the above code is actually a slightly more complicated recursive algorithm (each direction starting from “@” is regarded as a line segment, and the other end point of this line segment is the boundary or “ා”), so this is a recursive algorithm that can be seen as a cycle of four times, and the process of each recursive call, each direction is regarded as starting from the current point A line segment. So again and again. (CNT in the middle is used to count)

Please note that CNT is the number of recursion (because there is no backtracking, if there is a backtracking, counting is not necessarily equal to the number of recursion)

At this point, all the basic knowledge points have been talked about. Here are some personal suggestions on writing recursive algorithm:

(1) If you don’t understand the process, simulate the data several times;

(2) When recursion is written backward, it is realized as a stack (that is, it conforms to the idea of last in first out);

(3) When recursion and backtracking are combined, we need to understand the practice and difference between recursion and statistics;

(4) But when there are multiple recursion “recursion” and “return”, choose a key “Di” and “Gui” as the matching, analyze the real-time topic immediately, and pay attention to contingency.